MyBatis의 가장 큰 장점 중 하나는 XML로 동적 SQL을 유연하게 작성할 수 있다는 점입니다.
조건에 따라 WHERE
절이 달라지고, IN
목록이 바뀌고, SET
절이 동적으로 조립되어야 하는 상황은 실무에서 매우 흔하죠.
이 글에서는 MyBatis에서 가장 자주 사용되는 동적 SQL 태그들과
실전 예제를 통해 간결하고 안전하게 사용하는 방법을 소개합니다.
기본 예제 구조
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserList" resultType="User">
SELECT * FROM user
<where>
<if test="username != null">
AND username = #{username}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
</mapper>
자주 쓰는 동적 SQL 태그
1. <if>
– 조건 분기 처리
<if test="status != null">
AND status = #{status}
</if>
- null 또는 특정 조건에 따라 SQL 일부를 추가합니다.
- test 속성에는 SpEL(스프링 표현식) 문법이 들어갑니다.
2. <where>
– 조건절을 자동으로 조립
<where>
<if test="username != null">
AND username = #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
- 첫 번째 조건이 AND로 시작해도 자동으로 제거됨 (WHERE로 처리)
- 조건이 하나도 없으면 WHERE 자체도 생략됨
3. <set>
– UPDATE 쿼리용
<update id="updateUser">
UPDATE user
<set>
<if test="username != null"> username = #{username}, </if>
<if test="email != null"> email = #{email}, </if>
</set>
WHERE id = #{id}
</update>
- 마지막에 오는 , 자동 제거 처리됨
- 필드가 동적으로 바뀌는 UPDATE에 유용
4. <choose>
, <when>
, <otherwise>
– if/else if/else
구문
<choose>
<when test="role == 'admin'">
AND level = 'high'
</when>
<when test="role == 'user'">
AND level = 'normal'
</when>
<otherwise>
AND level = 'guest'
</otherwise>
</choose>
- 하나의 조건만 적용되도록 할 때 사용
- 여러 if와 달리 동시에 여러 조건이 적용되지 않음
5. <foreach>
– IN 절, 다중 값 처리
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
전체 예시:
<select id="selectUsersByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
- idList는 Java에서 전달된 List 타입
- open/close/separator로 괄호 및 구분자 자동 처리
6. <trim>
– SQL 조각 수동 제어
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="status != null"> AND status = #{status} </if>
<if test="email != null"> AND email = #{email} </if>
</trim>
- prefixOverrides를 통해 앞의 AND 또는 OR 제거 가능
- <where> 태그보다 더 세밀하게 제어할 수 있음
실무 예제: 조건 검색 + 페이징
<select id="selectPostList" resultType="Post">
SELECT * FROM post
<where>
<if test="category != null"> AND category = #{category} </if>
<if test="keyword != null">
AND (title LIKE CONCAT('%', #{keyword}, '%')
OR content LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
ORDER BY created_at DESC
LIMIT #{limit} OFFSET #{offset}
</select>
- 유저가 검색 조건 없이도 호출 가능
- keyword는 title/content 둘 다 검색
- 페이징 처리 지원
자주 발생하는 실수
실수 | 설명 | 해결 방법 |
AND가 앞에 붙어서 문법 오류 | 조건이 없을 경우 AND가 남음 | <where> 또는 <trim> 사용 |
<foreach> 안에 item 이름 안 맞음 | #{userId} 대신 #{item} 써야 | item="userId" 정확히 설정 |
null 체크 누락 | 조건 없이 조회하거나 NPE 발생 | 항상 <if test="!= null"> 확인 |
마무리 정리
태그 | 설명 |
<if> | 조건 분기 |
<where> | 조건절 자동 정리 |
<set> | UPDATE 컬럼 조립 |
<foreach> | IN 또는 반복 처리 |
<choose> | if / else if / else |
<trim> | WHERE, SET 등의 수동 버전 |
MyBatis의 동적 SQL은 정말 강력한 기능이지만,
잘못 쓰면 SQL이 꼬이고 디버깅이 어려워질 수 있습니다.
따라서 규칙을 정해 두고, XML 구조를 최대한 명확하게 유지하는 것이 중요합니다.
이 글에 소개된 예제 패턴들만 익혀도 실무에서 대부분의 복잡한 SQL을 처리할 수 있습니다.
댓글