[Spring Boot] mybatis 동적쿼리(trim) 사용

Posted by 김성철

Mybatis - trim 사용하기

https://velog.io/@joyfuljoyful/MyBatis-%EB%8F%99%EC%A0%81%EC%BF%BC%EB%A6%AC-trimwhereset  
https://java119.tistory.com/90  
  
스프링에서 Mybatis를 사용하다보면 동적으로 쿼리를 작성해야 할 때가 잇음  
  
그때 조건문에 동적으로 들어가야할 값이 아래와 같다고 치자  
=================================================================================================================  
SELECT * FROM  
WHERE A=##{A}  
AND B=##{B}  
AND C=##{C}  
=================================================================================================================  
  
A에는 값이 무조건 있고  
B , C 에 값이 있을 수 있고 전부다 없을 수 있다고 한다면 이걸 IF문으로만 작성했을 경우 아래와 같다  
=================================================================================================================  
SELECT * FROM  
WHERE A=##{A}  
<if test='A!=null'>  
	AND B=##{B}  
</if>  
<if test='A!=null'>  
	AND C=##{C}  
</if>  
=================================================================================================================  
  
근데 만약 A에도 값이 있을 수도 있고 없을 수도 있다면 A에도 IF문을 적용해야 한다.  
=================================================================================================================  
SELECT * FROM  
<if test='A!=null'>  
	WHERE A=##{A}  
</if>  
<if test='B!=null'>  
	AND B=##{B}  
</if>  
<if test='C!=null'>  
	AND C=##{C}  
</if>  
=================================================================================================================  
  
위와같이 작성한다면 A가 없을 경우 쿼리문은 아래와 같이 잘못된 쿼리문이되어 실행조차 안된다  
=================================================================================================================  
SELECT * FROM  
  
AND B=##{B}  
AND C=##{C}  
=================================================================================================================  
  
여기서 혹시나 그냥 널체크하지말고 해도대지않나?  
싶을텐데 아래의 조건은 완전 다른조건이다. 들어가는 순간 쿼리에서 구하고자 하는 값은 완전히 바뀐다  
  
SELECT * FROM WHERE B=null  
SELECT * FROM  
  
if문으로 좀더 분기해서 where 이 들어가도록 쿼리문 짜면 안되나? 라고 생각할수 있음  
if문으로 분기한게 아래의 쿼리임, 복잡한거 봐라..  
=================================================================================================================  
SELECT * FROM  
  
<if test='A!=null'>  
	WHERE A=##{A}  
</if>  
<if test='B!=null and A!=null'>  
	AND B=##{B}  
</if>  
<if test='B!=null and A==null'>  
	WHERE B=##{B}  
</if>  
  
<if test='C!=null AND B!=null'>  
	AND C=##{C}  
</if>  
<if test='C!=null AND B==null AND A!=null'>  
	AND C=##{C}  
</if>  
<if test='C!=null AND B==null AND A==null'>  
	WHERE C=##{C}  
</if>  
=================================================================================================================  
  
이와같은 방안을 해결하기 위해 사용하는 방법중 where 1=1 조건이 있다  
해당 조건은 무조건 true 이므로 그 뒤에 올 조건문들은 AND 또는 OR로 시작하면 된다  
(연산자에서 사용할 AND , OR 의 순서는 알아서 잘 짜자...)  
이방법으로 쿼리를 짜면 아래와 같다  
=================================================================================================================  
SELECT * FROM  
WHERE 1=1  
AND A=##{A}  
AND B=##{B}  
AND C=##{C}  
=================================================================================================================  
  
위의 쿼리를 Mybatis의 IF문을 적용하면 아래의 쿼리문이 완성된다  
IF문 안에 들어가는 내용들이 전부다 null이든, 하나만 있던 정상적인 쿼리가 출력된다  
A , B , C 가 전부다 null 일 경우에는 	SELECT * FROM WHERE 1=1 이라는 쿼리로 실행이 되며 오류가 발생하지 않음  
=================================================================================================================  
SELECT * FROM  
WHERE 1=1  
<if test='A!=null'>  
	AND A=##{A}  
</if>  
<if test='B!=null'>  
	AND B=##{B}  
</if>  
<if test='C!=null'>  
	AND C=##{C}  
</if>  
  
=================================================================================================================  
  
뭐 혹시나 UPDATE , DELETE 에서 WHERE 1=1 때문에 모든 데이터가 실행되버리면 어캐해요? 라고생각할수 있음  
아래의 쿼리문을 보자  
=================================================================================================================  
UPDATE 테이블명 SET  
D = ##{D}  
WHERE 1=1  
<if test='A!=null'>  
	AND A=##{A}  
</if>  
<if test='B!=null'>  
	AND B=##{B}  
</if>  
<if test='C!=null'>  
	AND C=##{C}  
</if>  
  
=================================================================================================================  
  
위의 쿼리문을 실행할때 A , B , C 가 전부다 null 이라고 가정한다면  
UPDATE 테이블명 SET D = ##{D} WHERE 1=1 이 실행되면서 모든 데이터가 변경되버리는 대참사가 발생하잖아!! 라고 생각 할거임  
  
근데 UPDATE , DELETE  쿼리문을 할때는 WHERE 1=1 문제가 아니라 쿼리문을 보내기 전에 JAVA 코드로 검증먼저 하는게 옳바른거임  
1=1 문제따지기전에 아래의 쿼리처럼 IF문으로 분기해서 작성했어도 위와같이 UPDATE 테이블명 SET D = ##{D} WHERE 1=1 실행됨  
  
UPDATE , DELETE 하는데 WHERE 부터 IF가 드가는건 말이 안됨  
UPDATE , DELETE 에서의 조건에는 유니크한값 또는 키값이 들어가야지...뭐 여러개 업데이트 친다고하면 명확한 조건이 기본적으로 들어가야함  
  
=================================================================================================================  
UPDATE 테이블명 SET  
D = ##{D}  
<if test='A!=null'>  
	WHERE A=##{A}  
</if>  
<if test='B!=null and A!=null'>  
	AND B=##{B}  
</if>  
<if test='B!=null and A==null'>  
	WHERE B=##{B}  
</if>  
  
<if test='C!=null AND B!=null'>  
	AND C=##{C}  
</if>  
<if test='C!=null AND B==null AND A!=null'>  
	AND C=##{C}  
</if>  
<if test='C!=null AND B==null AND A==null'>  
	WHERE C=##{C}  
</if>  
=================================================================================================================  
  
위와같이 작성했을때 크게 문제가 되는 부분은 없음  
그러나 Mybatis 에서는 동적쿼리에 사용할수 있는 기능들이 더있음  
<trim> <where><set> 이 있음  
위에서 작성한 WHERE 1=1 대신에 <where> 를 넣으면 됨  
그리고 A!=null 이 걸리면 AND 가 실행되서 WHERE AND 가 되는거 아니냐고 물어볼수 있는데  
WHERE AND가 되지 않고 알아서 문법에 맞게 치환 해줌  
=================================================================================================================  
  
SELECT * FROM  
<where>  
	<if test='A!=null'>  
		AND A=##{A}  
	</if>  
	<if test='B!=null'>  
		AND B=##{B}  
	</if>  
	<if test='C!=null'>  
		AND C=##{C}  
	</if>  
</where>  
  
=================================================================================================================  
  
아래는 실제로 사용했던 쿼리임  
trim 을 사용해서 where 문을 작성했었음  
=================================================================================================================  
  
    SELECT  
        stock_code ,parsing_date_detail ,parsing_date ,stock_name ,stock_category_code  
        ,stock_category_name ,present_price ,yesterday_price ,current_price ,high_price  
        ,upper_limit_price ,low_price ,lower_limit_price ,trading_volume ,trading_value  
        ,direction ,direction_code ,price_gap ,foreign_trade ,institution_trade ,parsing_memo  
    FROM parsing_data  
  
    <trim prefix="WHERE" suffixOverrides="AND">  
        <if test="stockCode!=null"> stock_code = ##{stockCode} AND </if>  
        <if test="stockName!=null"> stock_name = ##{stockName} AND </if>  
        <if test="stockCategoryCode!=null"> stock_category_code = ##{stockCategoryCode} AND </if>  
        <if test="stockCategoryName!=null"> stock_category_name = ##{stockCategoryName} AND </if>  
    </trim>  
  
=================================================================================================================  
  
prefix 는 trim 안에서 생성되는 쿼리문의 제일 앞에 붙여주고, suffixOverrides 는 생성되는 쿼리문의 제일뒤에 있는 단어를 제거함  
위의 쿼리문을 토대로 예시를 작성해봄  
=================================================================================================================  
SELECT * FROM parsing_data  
  
<trim prefix="WHERE" suffixOverrides="AND">  
	<if test="A!=null"> A = ##{A} AND </if>  
	<if test="B!=null"> B = ##{B} AND </if>  
	<if test="C!=null"> C = ##{C} AND </if>  
	<if test="D!=null"> D = ##{D} AND </if>  
</trim>  
  
=================================================================================================================  
  
정확히 어떻게 작동하나면 전부다 NULL이 아닐경우엔 아래의 쿼리가 실행됨  
trim 에서 생성된 문자들  
A=##{A} AND B=#{B} AND C=#{C} AND D=#{D}AND 에서 제일뒤에 있는 AND를 지우고,  
제일앞에는 WHERE 를 붙여주는거임  
=================================================================================================================  
SELECT * FROM parsing_data  
WHERE  
A=##{A} AND  
B=##{B} AND  
C=##{C} AND  
D=##{D}  
=================================================================================================================  
  
<set> 도 비슷함 값이 있으면 해당문 바로앞에 SET 을 붙여줘서 생성함  
=================================================================================================================  
UPDATE 테이블명  
<set>  
	<if test="E!=null"> E = ##{E}  </if>  
</set>  
WHERE A=##{A}  
  
=================================================================================================================  
  
=================================================================================================================  
UPDATE 테이블명  
SET E=##{E}  
WHERE A=##{A}  
  
=================================================================================================================