ibatis 설정 파일은 크게 두부분으로 나뉘는데 ibatis 자체의 기능을 설정하는 파일과 실제 POJO와 Table의 관계를 정의하는 파일로 나뉜다.(전자를 ibatis.xml이라고 하고 후자를 slqMap.xml 이라고 표기함)

ibatis.xml에서는 db 풀링, 트랜잭션 처리, 특정 POJO의 값을 DB의 데이터형으로 치환해주는 type handler, sqlMap.xml파일에 대한 참조를 설정한다. 로딩 시 ibatis.xml 에 정의된 각각의 element들을 읽어서 설정 정보를 반영한 후 본격적으로 sqlMap.xml 파일들을 읽어들여서 나중에 실행시 사용될 parameterMap과 resultMap 정보를 해석, 보관해둔다.

1. 초기 기동 시 발생하는 예외

초기 기동시에는 id나 class와 같은 각 element의 attribute값이 부정확해서 발생하는 예외가 많고 대부분 이런 예외는 처리가 쉽다.

<sqlMap..>, <parameterMap..>, <resultMap..>, <typeAlias..>

요소에서 id나 class같은 attribute를 잘못 설정했을 경우 예외가 발생하는데 대략적인 모습은 아래와 같다.

java.lang.RuntimeException: Error occurred. 
Cause: com.ibatis.common.xml.NodeletException: Error parsing XML. 
Cause: java.lang.RuntimeException: Error parsing XPath '/sqlMapConfig/sqlMap'. 
Cause: java.io.IOException: Could not find resource properties/Users.xml
    at com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser.parse(SqlMapConfigParser.java:49)
    at com.ibatis.sqlmap.client.SqlMapClientBuilder.buildSqlMapClient(SqlMapClientBuilder.java:63)
    at com.ynseo.accountbook.servlet.listener.DAOInitListener.initDAO(DAOInitListener.java:46)
    at com.ynseo.accountbook.servlet.listener.DAOInitListener.contextInitialized(DAOInitListener.java:34)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3843)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4350)
    at org.apache.catalina.core.StandardContext.reload(StandardContext.java:3099)
    at org.apache.catalina.loader.WebappLoader.backgroundProcess(WebappLoader.java:404)
    at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1309)
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1601)
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1610)
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1610)
    at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1590)
    at java.lang.Thread.run(Unknown Source)
Caused by: com.ibatis.common.xml.NodeletException: Error parsing XML. 
Cause: java.lang.RuntimeException: Error parsing XPath '/sqlMapConfig/sqlMap'. 
Cause: java.io.IOException: Could not find resource properties/Users.xml
    at com.ibatis.common.xml.NodeletParser.parse(NodeletParser.java:53)
    at com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser.parse(SqlMapConfigParser.java:46)
    ... 13 more
<sqlMap resources="User.xml" >에서 User.xml을 Users.xml로 잘못 표기했을때 나오는 에러메세지이다. 초기 기동시 발생하는 에러는 비교적 에러 정보가 정확하므로 문제를 해결하는데 별 어려움이 없다. exception tracing 메세지 상단의 "Cause : " 부분에 예외의 원인이 잘 나와있다.

2. 요청 실행 중 발생하는 에러

하지만 파악하기 힘든 대부분의 예외가 요청의 실행 중에 발생한다.

주로 java.util.Map 타입 파라미터 인스턴스로 inline-parameter-map(고유명사임..)을 생성할때 곤란한 예외가 많이 나온다.

inline-parameter-map을 알려면 우선 inline이 아닌 일반 parameter-map(이것도 고유명사..)부터 설명해야할 듯...

iBATIS는 쿼리를 조립하고 실행한 수 실행 결과를 특정 POJO로 변환하기 위해서 반드시 parameterMap과 resultMap을 필요로 한다.

parameter-map은 sqlMap.xml 파일에서

    <parameterMap id="PM-User" class="com.ynseo.bean.User" ..>
     ... <parameter ..../>
    </parameterMap>

으로 설정한 후 실제 sql 문을 정의한 statemnt, select, insert, update, delete 에서

    <insert id="insert" parameterMap="PM-User" ... >
    .... sql here ....
    </insert>

와 같이 참조되는 요소를 말한다.

"PM-User"로 참조되는 parameter map은 초기 기동 시에 xml 파일을 파싱, 검사하면서 설정 정보가 로드되기때문에 요청 실행시에 에러가 발생할 여지는 많이 줄어든다.(최종적으로 조립된 SQL 구문 자체에 문제가 없다면)

parameter-map은 항상 특정 클래스와 연관된다.

POJO와 Entity의 1:1 대응, 또는 POJO의 상속 그래프를 Entity에 관계지을 목적으로 고안되었다. 그렇기때문에 특정 클래스로부터 parameter-map을 유도해내는 것도 가능하다.

sqlMap.xml에 parameterMap="..." attribute를 정의하지 않고 위처럼 parameterClass 값을 통해서 요청 실행시에 생성된 parameter-map을 특별히 inline parameter map이라고 한다.

parameterMap을 명시적으로 정의했는지, 아니면 주어진 클래스 정보를 이용해서 유도했는지의 차이가 있다.

아래의 sqlMap.xml 요소를 예로 들어보자.
    <insert id="insert" parameterClass="com.app.bean.OrderBean">
        insert into Items ( ...column list ...)
        values ( #orderId#, #orderAmount#, #orderQuantity#, ....)
    </insert>
위의 경우

parameterClass="com.app.bean.OrderBean"

가 주어졌기때문에 Reflection을 통해서 parameter-map을 조립한다.

주어진 insert into.. 쿼리 구문에서 정의된 #orderId#, #orderAmount#, #orderQuantity# 등이 OrderBean의 getter/setter 메소드로 접근 가능한 property로서 일치하는지를 확인한다. #....#로 정의한 property가 POJO의 property로서 접근 가능하지 않으면 초기 기동 시 예외가 발생한다.

inline-parameter-map에서 예외가 발생하는 경우를 몇가지 거론하면 이렇다.

xml 파일에는 #orderId# 라고 적어놓았지만 실제 OrderBean에는 getId(), setId(String id) 로 구현되어서 "userId 를 찾을 수 없습니다" 라는 예외가 뜨거나 getter/setter 메소드 중 하나가 없어서 "값을 설정할 수 없다"는 식이다.

또는 타입을 잘못 적는 경우가 있다.

id를 java.lang.String 타입으로 했다가 나중에 Integer로 바꾼 경우에 여전히 sqlMap.xml 에는

     #userId,javaType=string,jdbcType=INTEGER#

으로 남아있게된다. 이런 경우 실제로 저 구문을 실행하면 Integer를 String으로 casting할 때 CastingException이 던져진다.

너무 뻔한것 같지만 흔히 하는 실수이다. 특히 최근의 IDE는 Refactoring 기능을  제공하는데 xml 파일의 설정 정보까지 바꾸어주지는 못하므로 빈번한 Refactoring으로 property getter/setter를 건드린 후 나중에 실행시점에 가서 이런 예외가 많이 터진다.

이런 예외도 찾기 쉬운 에러에 속한다.( "Cause: "부분에 상세하게 나오므로..)

3. parameterClass="java.util.Map"

가장 손이 많이 가는 경우는 java.util.Map을 파라미터 전달 수단으로 사용할 때이다.

1999년 12월달의 데이터 조회, 2004년 3월 둘째주 데이터 중 거래 가격이 3천만원 미만인 데이터 조회 등등... 검색 조건이 복잡해지는 경우 parameter-map을 이용하면 별도의 파리미터 전달용 클래스를 만들어야 하고 조회 조건이 다양해지면 그때그때 클래스를 또 정의하든 고치든 parameter-map에 대응하는 클래스를 관리해야 하므로 번거롭다.(쿼리 하나를 위해서 별도의 클래스를 만든다는건 좀 그렇다...)

파라미터를 java.util.Map에 담아서 전달하면 파라미터 클래스를 만드는 비용을 줄일 수 있어서 많이 사용되는데 일단 예외가 하나 터지면 확인할 곳이 너무 많다.

1. xml파일에 정의한 #orderAmount#를 못찾는건가? 왜 못찾지? Map에 담겨있지 않나? 오타가 났나?
2. 오타는 아닌데.. javaType jdbcType을 못찾는건가? 일단 적어보자.
3. (디버그 한 후 ) 왜 엉뚱한 typeHandler가 잡히지? javaType이 틀렸나?
4. javaType은 맞는데 jdbcType이 틀렸나?
5. #....# 은 맞는 것 같은데 역시 jdbcType이 틀렸나?
6. 아닌데 INTEGER 맞는데...
7. SQL 구문 자체가 틀렸나?

java.util.Map이 파라미터 전달 수단으로 사용되면 실제로 Map 인스턴스가 전달되기 전까지 아무것도 결정할 수가 없다. 위에서 parameterClass="com.app.bean.OrderBean" 인 경우 Reflection을 통해서 초기에 #....# 부분에 대해 유효성 검사를 하지만 parameterClass="java.util.Map" 으로 전달될 경우 실제 요청이 와서야 #....# 부분을 검사한다.(Map에 뭐가 담겨서 요청이 들어올지 모르므로 초기 기동시에 무언가를 검사한다는게 불가능하다)

즉, 예외 발생의 경우가 "클라이언트의 요청"이라는 한 시점에 몰려있는데 던져지는 예외는 SQLException 한가지로 천편일률적인 점이 예외를 수정하는데 어려움이 된다. (예외메세지도 'orderAmount' 프로퍼티 부근을 확인하라는 게 전부인 경우가 많다.)

웹애플이 model2 로 구성된다면 command 부분에서부터 ibatis 앞단에 해당하는 DAO, 그리고 sqlMap.xml 까지 모두다 검사해봐야한다.

아래의 POJO를 예로 들어보자.

age가 20대인 User 들만 검색하는 경우 설정할 parameter를 java.util.Map 으로 설정해서 조회할 수 있다.


sqlMap.xml 에서는 주어진 java.util.Map 인스턴스로부터 "age0", "age1"를 key값으로 해서 실제 값을 추출한다.

((sqlMap.xml))
<select id="Users.loadByAge" parameterClass="java.util.Map" resultMap="RM_User">
    select
        ...column_list_here...
    from Users
    where userAge between #age0,javaType=int,jdbcType=INTEGER#
              and #age1,javaType=int,jdbcType=INTEGER#
</select>

여기서는 parameter[age0 : 20]과 parameter[age1 : 29]가 얻어진다.

각각의 parameter에대해 javaType을 확인한 후 관련 typeHandler를 가져온다.(IntegerTypeHandler)

그리고 jdbcType을 통해서 PreparedStatement.setXXXX 를 처리할 메소드를 얻게된다.이전에 얻은 typeHandler를 이용해서 preparedStatement 의 ? 를 21, 29로 치환한다.

위와 같은 과정을 통해서 DB에 쿼리를 날리게된다.

즉, 예외를 해결하려면 저 모든 과정을 다 살펴보아야 한다는 뜻이다.(그래서 손이 많이 간다.)

4. 예외를 빨리 해결하려면...

sqlMap.xml에서 parameterClass을 항상 적는다.

inline-parameter-map을 생성하는 경우 <statement>, <select>, <insert>, <update>, <delete> 요소에서 프로그램 코드가 정확하다면 parameterClass를 생략해도 Reflection을 통해서 parameter-map 을 조립할 수 있다. 하지만 예외가 발생할 경우 들여다보아야할 부분이 하나 더 늘어나는 셈이므로 parameterClass를 반드시 적는다.

parameterClass를 명확히 함으로써 만일 DAO 에서 잘못된 타입의 인스턴스를 파라미터로 전달할 경우 Casting과정에서 명확한 예외 메세지가 던져진다.


다음은 inline-parameter-map 의 syntax에서 참고할 내용이다.

inline-parameter-map을 조립하는데 사용되는 property에는 옵션들이 몇가지 있다.

javaType, jdbcType, nullValue, handler, numericScale

1). #...#에서 jdbcType은 항상 적는다.

문서에서는 column에 null이 들어갈 수 있는 경우에 jdbcType을 반드시 적어넣으라고 나와있는데 이는 POJO의 property값이 null인 경우 DB쪽에 null의 타입이 무엇인지 알려주어야 하기때문이다.(INTEGER가 NULL 인지, VARCHAR2가 NULL인지..)

문서대로라면 id처럼 절대 null이 들어갈 수 없는 property에 대해서는 jdbcType을 안 넣어도 상관없지만 이 값을 명확히 함으로써 예외가 발생할 때 문제 지점을 좁힐 수 있다. 따라서 이 값은 null의 발생 여부와 상관없이 항상 적어넣는게 좋다.


2). typeHandler는 가급적 생략

1과 2에서 javaType과 jdbcType이 정확하다면 관련 typeHandler는 올바르게 설정된다. javaType, jdbcType을 명확히 적어넣었을때 typeHandler로 적어넣는 것은 일종의 중복으로 보여진다.


3). SQLException 발생시 errorCode를 분명히 기입한다.

예외의 원인이 잘못 생성된 쿼리때문이라면 errorCode를 출력하는게 문제를 해결하는 가장 빠른 길이다. 반대로 생각하면 errorCode가 출력되지 않았다는건 예외의 원인이 쿼리가 아니라 ibatis 설정이나 요청을 보내는 DAO쪽에서 잘못된 파라미터 인스턴스를 보냈다고 볼 수 있다.



* oracle에서 주의 할 점은 쿼리 끝에 ";" 을 붙이지 않도록 한다. MYSQL에서는 문제가 없는데 유독 ORACLE 에서 쿼리 끝에 ';' 가 있으면 ORA-00911 에러가 던져진다.

';' 때문에 에러 발생


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

iBATIS 2.x vs iBATIS 3.0  (0) 2010.04.30
[iBATIS] typeHandler 를 찾지 못하는 경우  (0) 2009.06.14
iBATIS Inline parameter Map  (3) 2008.09.10
Posted by yeori
,