MyBatis 관련 삽질 모음
mybatis을 사용하면서 겪은 에러나 알게 된 내용들을 여기에 정리해두고자 한다. 앞으로 새로운 내용이 생길 때마다 여기에 업데이트하게 될 것 같다.
ORA-00928: 누락된 SELECT 키워드
이 에러는 정말 다양한 상황에서 발생할 수 있는 것 같다. 이 에러로 검색해보면 원인이 참 다양하다. 다양한 상황에서 발생할 수 있는만큼, 살짝은 불친절한 메시지가 아닌가 한다. 이 에러가 떴다면 실제로 실행된 쿼리에서 실수를 샅샅이 찾아야 한다. 사실 간단한 SQL을 작성해서 바로 실행해본다면 실수를 금방 깨달을 수 있지만, 길고 복잡한 SQL 구문이나 mybatis mapper에 작성한 코드라면 에러를 바로 눈치채기 쉽지 않다. SQL 문법 실수를 한 것이 아닌지 가장 먼저 의심해보고, 동적 쿼리인 경우 실제로 실행된 쿼리를 확인해봐야 한다. 내 경우에는 SQL문에는 문제가 없었지만, 동적으로 들어가게 되는 데이터가 실수로 null을 집어넣고 있어서 완성된 쿼리가 이상한 모양이었다.
Oracle java.sql.SQLException: 부적합한 열 유형
이 예외를 검색했을 때 딱히 도움되는 답을 얻지 못했다. 도대체 뭘까...하고 쳐다보다가 내가 코멘트 처리를 잘못 했음을 깨달았다. mapper.xml에서 command
+ /
로 코멘트 처리한 부분이 문제였다. 이렇게 하면 --
가 앞에 붙으면서 회색으로 변한다. 그래서 에디터 상에서 눈으로 볼 때는 코멘트 처리가 잘 된 것처럼 보였지만, 실제로는 유효한 코드로 읽어들인 것이다. xml에서의 코멘트는 <!--
와 -->
로 감싸져 있어야 한다.
There is no getter for property named XXX
혹시 mapper 파일에서 파라미터를 잘 못 읽는다면, 해당 파라미터 앞에 @Param
애너테이션을 붙이면 해결된다.
Invalid bound statement (not found)
이거 오류를 검색하면 메서드명 실수나 경로 이야기가 많이 나왔는데, 내 경우에는 <mapper namespace="blah">
에 넣은 경로가 잘못 되어 있었다. 다른 컴포넌트에 있는 xml 파일을 복붙해오다 보니 이 부분도 바뀌어야한다는 것을 깜빡했다.
bulk merge into 올바르게 사용하기
oracle에서 upsert(자료가 없으면 insert, 이미 존재하면 update)를 할 때에는 merge into
구문을 사용한다. 내 경우에는 이걸 bulk로 진행하고 싶어서 mybatis의 <foreach>
기능을 사용했는데, 이 과정에서 삽질을 좀 많이 했다.
올바른 사용 방법은 이 블로그에서 친절하게 설명이 되어 있다. <foreach>
가 의외의 위치에 있다는 생각이 들 수 있지만, merge into
문법을 잘 이해하고 나면 이상할 것이 없다. using
을 이용해서 가짜 테이블(T1)을 만들고, 그 결과를 on
에 사용한다. on
은 매칭되는 row가 있는지 확인하기 위한 조건이다. 만약 methodName
메서드의 파라미터만을 이용해서 on
조건을 만들 수 있다면 using dual on
의 문법을 사용하면 된다.
<update id="methodName" parameterType="java.util.List">
MERGE INTO SOME_TABLE R1
USING (
<foreach collection="parameterName" item="item" open="" close="" index="index" separator="UNION">
SELECT
#{item.property1} as property1,
#{item.property2} as property2,
#{item.property3} as property3,
FROM DUAL
</foreach>
) T1
ON (R1.matching_column1 = T1.property1)
WHEN MATCHED THEN
UPDATE
SET
R1.matching_column1 = T1.property1,
R1.matching_column2 = T1.property2,
R1.matching_column3 = T1.property3
WHEN NOT MATCHED THEN
INSERT (
matching_column1,
matching_column2,
matching_column3
)
VALUES (
T1.property1,
T1.property2,
T1.property3
)
</update>
내가 한 실수는 <foreach>
에 있었다. <foreach>
의 각 요소를 살펴보자면 다음과 같다.
- collection: 무언가 순회할 수 있는 데이터를 파라미터로 넘길텐데, 그 이름을 적으면 된다.
- item: 여기에 부여한 이름으로 #{item}처럼 접근할 수 있다.
- index: 여기에 부여한 이름으로 #{index}처럼 접근할 수 있다.
- open, close, separator:
<foreach>XXX</foreach>
처럼 되어 있다면 XXX가 반복될텐데, 그 시작과 끝, 그리고 각 요소 사이에 어떤 코드를 넣을지를 명시하는 것이다. 위 코드의 경우 각 select문의 결과를 union 하고 싶기 때문에 separator에 union을 명시했다. 위 코드에서는 open과 close에 공백이나 괄호를 넣어도 완성되는 SQL문에는 문제가 없을 것이다.
이건 다른 이야기이지만, mybatis를 사용하면 이렇게 사람이 실수할 구석이 많다는 것 자체가 별로인 것 같다. 실수 하나 할 때마다 생산성이 갉아먹히는 기분이다. 실수를 작성하면서 바로 알 수 있도록 IDE에서 기능이 지원되면 좋을 것 같고(이런 찾아보면 플러그인이 분명 있을 것 같다), mybatis를 사용한다면 더 쉽고 빠르게 테스트를 해볼 수 있는 방법을 찾아야할 것 같다(지금은 스프링 테스트를 하거나 서버를 아예 실행해보는 데 더 좋은 방법이 없을까). 만약 ORM을 사용할 수 있는 기능이라면 ORM을 적극적으로 활용하는 것이 좋을 것 같다. 만약 반드시 SQL을 사용해야 한다면 mybatis 말고 더 편리한 SQL mapper 기술을 사용하는 것도 좋을 것 같다.