사용자 삽입 이미지

오늘 겪은 문제는 inline parameter map syntax 부분에서 발생했는데 다음과 같은 에러 메세지가 출력되었다.

com.ynseo.dao.DAOException: fail to update item
    at com.ynseo.accountbook.db.ItemDAO.update(ItemDAO.java:229)
    <<생략>>>........................
Caused by: com.ibatis.common.jdbc.exception.NestedSQLException:  
--- The error occurred in properties/Item.xml. 
--- The error occurred while applying a parameter map. 
--- Check the Items.update-InlineParameterMap. 
--- Check the parameter mapping for the 'amount' property. 
--- Cause: java.lang.NullPointerException
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:110)
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.update(SqlMapExecutorDelegate.java:457)
    at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.update(SqlMapSessionImpl.java:90)
    at com.ibatis.sqlmap.engine.impl.SqlMapClientImpl.update(SqlMapClientImpl.java:66)
    at com.ynseo.accountbook.db.ItemDAO.update(ItemDAO.java:222)
    ... 17 more
Caused by: java.lang.NullPointerException
    at com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap.setParameter(ParameterMap.java:166)
    at com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap.setParameters(ParameterMap.java:126)
    at com.ibatis.sqlmap.engine.execution.SqlExecutor$Batch.addBatch(SqlExecutor.java:592)
    at com.ibatis.sqlmap.engine.execution.SqlExecutor.addBatch(SqlExecutor.java:103)
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.sqlExecuteUpdate(MappedStatement.java:213)
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:94)
    ... 21 more

amount property가 문제를 일으킨다네..

관련 query는 다음과 같다.

    <update id="update" parameterClass="item">
        UPDATE Items
        SET
            itemAmount = #amount#,
            itemComment = #comment#,
            itemDate = #date#,
            itemType= #type#
        WHERE itemId = #itemId#
    </update>

그리고 parameterClass에 해당하는 Item 클래스는 다음과 같다.

+ class Item implements Serializable ...
    -Money amount;
    ....


+ class Money implements Serializable ...
    final private long amount;
    public Money (){
        this(0);
    }
    ....


getter/setter 모두 확실하다.

typeAlias 도 문제없고

    <typeAlias alias="money" type="com.ynseo.accountbook.Money"/>

Money 클래스를 DB의 INTEGER 값으로 매핑하는 MoneyTypeHandler 클래스도 제대로 작동한다.(다른 sqlMap 에서는 Money를 제대로 변환해주고 있다.)

아.... 딸랑 amount property를 확인해보라는 메세지는 너무 광범위하다.

정말 싫어라하는 디버그를 이용해서 문제를 찾아봤다.
사용자 삽입 이미지
역시 문제는 amount 부분에 있었지만 정확하게 말하면 ParameterMap.class 에서 typehanlder 가 null로 잡혀 들어가는게 문제였다.

디버그 창을 보니 typehandler 뿐만 아니라 jdbcType, javaType 정보도 모조리 null 이다. 뭐냐 이거.. -_-;;

메뉴얼을 참고해서 다음과 같이 xml 파일을 수정하니 제대로 실행이 된다.

<update id="update" parameterClass="item">
        UPDATE Items
        SET
            itemAmount = #amount,javaType=money,jdbcType=INTEGER#,
            itemComment = #comment#,
            itemDate = #date#,
            itemType= #type#
        WHERE itemId = #itemId#
    </update>

위에서 money는 Money 클래스의 full qualified name 의 alias 이다.


<iterate> 와같은 dynamic sql을 사용할때도 마찬가지이다.

예를 들어서 Key라는 도메인 클래스의 인스턴스들을 java.util.List로 전달해서 여러개의 값을 조회할 경우를 생각할 수 있다.


        SELECT  col1, col2, ... coln
        FROM    <tbl_name>
        WHERE col1 IN( val1, val2, ... valn)


val1,...valn 이  Key 클래스의 인스턴스들로서 List에 포함되어 전달될 경우...

        List tags = new List();
        tags.add(new Key("2001"));
        tags.add(new Key("2007"));

        return sqlMap.queryForList("Items.loadByTag", tags);

sqlMap 파일에서 전달되는 java.util.List 프로퍼티에 대한 javaType과 jdbcType을 명시해준다.

<select id="loadByTag"  resultMap="RM_Item" cacheModel="CM_Item">
        SELECT *
        FROM ItemTag
   
    <iterate prepend="where tagId in" open="(" close=")" conjunction=",">
           #[],javaType=key,jdbcType=INTEGER#
   
    </iterate>
</select>

위의 구문은 다음과 같이 변경된다.

    SELECT *
    FROM ItemTag
    where tagId in  (   2001, 2007  )


 메뉴얼에서 java.util.List 를 parameter로 전달할때에는 <iterate> 안에서 반드시 #[]# 을 명시하라고 되어있다.

Note : It is very important to include the squre brackets "[]" at the end of the property name when using the Iterate element. These brackets distinguish this object as a collection to keep the parser from simply outputting the collection as a string

#[]# 를 입력해야 파서가 위 구문을 [0], [1], [2]... 와 같이 변형해서 전달받은 parameter의 java.util.List 로부터 각각의 element를 추출한다.

추가적으로 기술하자면 절대로 "property" 값을 입력하지 말아야 한다(java.util.List를 parameter로 전달할 경우 반드시 비워둘 것!!).

이 값을 입력할 경우 parser는 parameter의 타입을 엉뚱하게 해석한다.(덧붙이자면 명시된 property 값을 이용해서 java.util.List에 접근하게 되고 List에서는 property값이 아무런 의미가 없기때문에 예외가 던져진다. property를 붙이는 경우는 따로 설명..)


이번 디버그의 교훈은 Inline parameter map을 사용할때 primitive data type이 아닐 경우 꼭 javaType과 jdbcType을 명시해주어야 한다는 것. 이 두가지 정보만 제대로 적어주면 iBatis 에서 typeHandler를 찾아서 값을 읽고 받아올 수 있다.




'Dev > iBATIS' 카테고리의 다른 글

iBATIS 2.x vs iBATIS 3.0  (0) 2010.04.30
[iBATIS] typeHandler 를 찾지 못하는 경우  (0) 2009.06.14
iBatis Inline parameter map Exception 다루기  (0) 2008.10.26
Posted by yeori
,