MyBatis 동적 SQL에서 가장 널리 활용되는 것이 if가 아닐까 생각한다.
MyBatis에서 결국 동적 SQL은 "어떤 값이 NULL일 때는 조건문에 추가시키지 말고, 값이 존재할 때만 조건문에 추가시켜주세요" 같은 요청을 처리할 때 많이 활용되고 이를 처리하기 위한 가장 적절한 동적 SQL이기 때문이다.
WHERE문에 많이 활용되기는 하지만 ORDER BY, GROUP BY 등에서도 <if>
를 활용할 수 있다.
<select id="findActiveBlogLike" resultType="Blog">
SELECT
*
FROM
BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
분명 MyBaits로 짰는데 신기하게도 무슨 말인지 바로 이해할 수 있을 것 같다.
만약 <if>
태그에서 test 부분에 조건문이 true를 반환한다면 태그 사이에 존재하는 Query문을 활용하고, 만약 false라면 태그 사이에 존재하는 Query문을 활용하지 않는 것이다.
위 코드를 통해 설명해보자면, title이라는 값이 null이 아닐 경우 "AND title like #{title}"문을 WHERE 조건에 추가시켜 Data의 title 중 입력값 title을 포함하는 데이터만 Select 한다는 의미가 될 것이다.
또한 입력값 author과 author.name이 동시에 null이 아니라면 입력값 author.name이 포함된 데이터를 추가로 Select 한다는 의미가 될 것이다.
여기에서 알아야 할 점은 "#{title}"처럼 활용하지 않는다는 것이다. 원래 MyBatis는 객체의 데이터를 가져오기 위해서는 Parameter를 활용해 #{}나 ${}를 활용해야만 했다.
하지만 <if>
, 그리고 아래에 나오는 모든 동적 SQL 태그에서는 Parameter 처리를 자동으로 해주기 때문에 단순히 멤버 변수의 이름과 일치하게만 코드를 짜주면 알아서 처리해준다는 것을 알고 있자. (오히려 #{}, ${}을 추가하면 에러가 발생한다!)
자바에 Switch문이 존재한다면, MyBatis에서는 choose Element가 존재한다.
if문과 사용 방법은 비슷하지만, Switch 문과 유사하기 때문에 조건에 따라 선택하는 Query를 다르게 할 수 있다는 장점을 가진다.
태그를 통해 Switch문임을 알리고, 태그를 활용해 case를 지정해준다.
이 때 태그의 test Parmeter를 통해 test = true일 경우에 해당 태그 사이에 존재하는 Query를 활용하게 된다.
마지막으로 를 통해 Switch문의 default문을 생성할 수 있다
<select id="findActiveBlogLike" resultType="Blog">
SELECT
*
FROM
BLOG
WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
<where>
만약 WHERE문 이후 if문만 존재한다면 AND나 OR로 시작해버리는 조건문이 생성될 수도 있고, 아예 조건이 없는 Query가 생겨 에러가 발생할 수 있다.
예를 들어 WHERE <if test="false"> title = #{title} </if>
로 Query문을 짰다면 최종적으로 Query는 "SELECT * FROM X WHERE"이라는 미완성된 Query가 될 것이고, 이는 당연히 에러를 발생시킬 것이다.
또한 WHERE title <if test="false"> title = #{title} </if> <if test="true"> AND name = #{name}</if>
로 존재한다면 SELECT * FROM X WHERE AND name=홍길동
라는, WHERE AND라는 이상한 값이 Query에 포함될 것이다.
<where>
Element는 태그에 의한 컨텐츠가콘텐츠가 1개라도 리턴되면 WHERE문을 추가되고, 콘텐츠가 하나도 리턴되지 않으면 WHERE문을 추가시키지 않는다.
또한 WHERE문을 형성했을 때 WHERE 이후에 AND나 OR이 존재하면 이 값을 제거하고 Query를 형성한다
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
<where>
이 SELECT문에 활용되는 값이라면, <update>
에서 활용할 수 있는 <set>
도 존재한다. set 태그는 값을 모두 주입하는 것이 아닌 일부분만 주입하고 싶을 경우 활용하는 태그이다.
<where>
은 AND와 OR이 문제였다면 UPDATE에서는 쉼표(,)가 문제가 된다. <set>
태그는 Query문 끝에 있는 쉼표를 자동으로 처리하여 정상적인 Query문이 되도록 바꿔준다
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
trim은 User가 직접 설정하여 위와 같이 특정 구문을 삭제할 수 있게 만드는 것이다.
이 trim을 통해 <where>
과 <set>
도 구현할 수 있다.
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
위와 같이 trim을 활용하면 prefix가 AND나 OR로 들어올 경우 제거하고, prefix는 WHERE이기 때문에 WHERE문으로 활용한다는 것이다. 즉 <where>
과 같은 역할을 할 수 있는 것이다
<trim prefix="SET" suffixOverrides=",">
...
</trim>
또한 위와 같이 trim을 활용하면 prefix가 SET이므로 UPDATE문의 SET 절을 추가함을 알 수 있고, suffixOverrides가 ","이기 때문에 가장 마지막에 ","가 들어올 경우 이를 제거하여 정상적인 Query문을 형성해주는 <set>
태그라는 것을 알 수 있다.
<trim>
은 <where>
이나 <set>
위치에 대신 입력만 해주면 그대로 활용할 수 있게 된다
개인적으로 나는 많이 활용하는데, 그렇게까지 중요시 생각지는 않는 것 같다.
주로 List형식으로 한 번에 데이터를 넣어줘 Query를 실행하고 싶을 때 많이 활용하며 Java collection에 대한 반복처리를 수행해줘야 할 때 많이 활용한다.
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM
POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
item을 통해 어떤 id 값으로 활용할 것인지를 정하고, index를 통해 list index를 받을 수 있으며 collection을 통해 어떤 객체를 가지고 올 것인지 명시할 수 있다.
또한 Separator를 통해 for문의 각각 시행에 대해 구분자를 나타낼 수 있으며, open과 close를 활용해 1번의 for문의 시작과 끝 부분을 설정해줄 수 있다.
마지막으로 nullable "true"로 설정하면 NULL 값이 올 수 있게 설정할 수도 있다