[Spring] MyBatis Dynamic Query

배창민·2025년 10월 20일
post-thumbnail

MyBatis Dynamic Query


1) 개요

  • DAO 다중 호출, 배치 없이 XML 태그/Provider API로 SQL을 동적 생성.
  • 자주 쓰는 XML 태그: if, choose(when, otherwise), trim(where, set), foreach, bind.
  • 자바 기반 동적 쿼리: @SelectProvider/@InsertProvider/@UpdateProvider/@DeleteProvider.

2) XML 기반 동적 쿼리

2-1. if — 조건부 구문 삽입

<select id="searchBoard" resultType="arraylist">
  SELECT * FROM BOARD
   WHERE writer = 'admin'
  <if test="title != null">
    AND title LIKE #{title}
  </if>
</select>
  • 여러 조건은 다중 if로 조합 가능.

2-2. choose — 단일 가지 선택

<select id="searchBoard" resultType="arraylist">
  SELECT * FROM BOARD
   WHERE COMMENT != ''
  <choose>
    <when test="title != null">
      AND title LIKE #{title}
    </when>
    <when test="writer != null">
      AND writer LIKE #{writer}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

2-3. trim / where / set — 접두/접미 제어로 문법 오류 방지

문제 패턴

SELECT * FROM BOARD
WHERE
<if test="writer != null">writer = #{writer}</if>
<if test="title  != null">AND title  LIKE #{title}</if>
  • 모든 if가 거짓 ⇒ WHERE만 남아 오류
  • 첫 if 거짓, 둘째만 참 ⇒ WHERE AND 오류

trim으로 해결

<select id="searchBoard" resultType="arraylist">
  SELECT * FROM BOARD
  <trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="writer != null">
      writer = #{writer}
    </if>
    <if test="title != null">
      AND title LIKE #{title}
    </if>
  </trim>
</select>

where로 간단 해결

<select id="searchBoard" resultType="arraylist">
  SELECT * FROM BOARD
  <where>
    <if test="writer != null">
      writer = #{writer}
    </if>
    <if test="title != null">
      AND title LIKE #{title}
    </if>
  </where>
</select>
  • where는 내부가 AND/OR로 시작하면 자동 제거.

set — 동적 업데이트 컬럼

<update id="updateUser">
  UPDATE USER
  <set>
    <if test="username  != null">username  = #{username},</if>
    <if test="password  != null">password  = #{password},</if>
  </set>
  WHERE id = #{id}
</update>
  • 마지막 쉼표 자동 제거.
  • trim으로 대체 가능:
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

2-4. foreach — 컬렉션 반복(IN 절 등)

속성설명
collection반복 대상(list, array, map 등)
item각 반복 원소 변수명
index인덱스(선택)
open / separator / close앞/구분자/뒤 문자열
<select id="searchBadwords" resultType="arraylist">
  SELECT * FROM BOARD
   WHERE TITLE IN
  <foreach collection="list" item="word" open="(" separator="," close=")">
    #{word}
  </foreach>
</select>
  • 정적 필드/메서드 직접 접근도 가능
    collection="@풀클래스경로@필드" 또는 @풀클래스경로@메서드()
<foreach collection="@com.example.Application@createCodes()" item="code"
         open="(" separator=", " close=")">
  #{code}
</foreach>

2-5. bind — 미리 가공한 값 바인딩

<select id="searchBoard" resultType="arraylist">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'"/>
  SELECT * FROM BOARD
   WHERE title LIKE #{pattern}
</select>

3) Java Provider 기반 동적 쿼리

3-1. 매퍼 인터페이스 매핑

@ResultMap("menuResultMap")
@SelectProvider(type = SelectBuilderProvider.class, method = "searchMenuByCondition")
List<MenuDTO> searchMenuByCondition(SearchCriteria criteria);

@DeleteProvider(type = SqlBuilderProvider.class, method = "deleteMenu")
int deleteMenu(@Param("code") int code);
  • 기본형 파라미터는 @Param으로 이름 부여.
  • @Param을 쓰면 Provider 메서드 시그니처는 매개변수 없이 작성하고 #{code}로 참조.

3-2. Provider 클래스 예시

SELECT

public String searchMenuByCondition(SearchCriteria c) {
  SQL sql = new SQL()
      .SELECT("A.MENU_CODE", "A.MENU_NAME", "A.MENU_PRICE", "A.CATEGORY_CODE", "A.ORDERABLE_STATUS")
      .FROM("TBL_MENU A");

  if ("category".equals(c.getCondition())) {
    sql.JOIN("TBL_CATEGORY B ON (A.CATEGORY_CODE = B.CATEGORY_CODE)")
       .WHERE("A.ORDERABLE_STATUS = 'Y'")
       .AND().WHERE("B.CATEGORY_NAME = #{value}");
  } else if ("name".equals(c.getCondition())) {
    sql.WHERE("A.ORDERABLE_STATUS = 'Y'",
              "A.MENU_NAME LIKE CONCAT('%', #{value}, '%')");
  }
  return sql.toString();
}

INSERT

public String registMenu(MenuDTO m) {
  return new SQL()
      .INSERT_INTO("TBL_MENU")
      .VALUES("MENU_NAME", "#{name}")
      .VALUES("MENU_PRICE", "#{price}")
      .VALUES("CATEGORY_CODE", "#{categoryCode}")
      .VALUES("ORDERABLE_STATUS", "#{orderableStatus}")
      .toString();
}

UPDATE

public String modifyMenu(MenuDTO m) {
  SQL sql = new SQL().UPDATE("TBL_MENU");
  if (m.getName() != null && !m.getName().isEmpty()) sql.SET("MENU_NAME = #{name}");
  if (m.getPrice() > 0)                               sql.SET("MENU_PRICE = #{price}");
  if (m.getCategoryCode() > 0)                        sql.SET("CATEGORY_CODE = #{categoryCode}");
  if (m.getOrderableStatus() != null && !m.getOrderableStatus().isEmpty())
                                                     sql.SET("ORDERABLE_STATUS = #{orderableStatus}");
  sql.WHERE("MENU_CODE = #{code}");
  return sql.toString();
}

DELETE

public String deleteMenu() {      // @Param("code") 사용 시 인자 없음
  return new SQL()
      .DELETE_FROM("TBL_MENU")
      .WHERE("MENU_CODE = #{code}")
      .toString();
}

4) 체크리스트 & 베스트 프랙티스

  • WHERE/SET 선두 AND/OR/쉼표 문제: where, set, trim(prefixOverrides|suffixOverrides) 활용.
  • 문자열 치환 주의: #{} 사용 권장. ${}는 그대로 치환되어 인젝션 위험.
  • 빈값 처리: != null만이 아니라 빈 문자열 체크까지 함께.
  • IN 절: foreach로 안전하게 구성.
  • 공통 가공 값: bind로 미리 생성.
  • Provider: 복잡한 조건 분기, 조립형 SQL에 적합. 인터페이스엔 @*Provider, 구현은 org.apache.ibatis.jdbc.SQL.
  • 파라미터 명: Provider에서 기본형은 @Param으로 명시, DTO/Map은 필드/키로 접근.

5) 스니펫 모음

  • 동적 WHERE
<where>
  <if test="a != null">A = #{a}</if>
  <if test="b != null">AND B = #{b}</if>
</where>
  • 동적 SET
<set>
  <if test="x != null">X = #{x},</if>
  <if test="y != null">Y = #{y},</if>
</set>
  • IN 절
<foreach collection="ids" item="id" open="(" separator="," close=")">
  #{id}
</foreach>
  • 안전한 LIKE
<bind name="kw" value="'%' + _parameter.keyword + '%'"/>
WHERE COL LIKE #{kw}

핵심 요약

  • XML 태그로 조건/반복/접두접미를 다루고, Provider로 자바 코드 조립형 SQL을 구현.
  • 문법 오류 방지는 where/set/trim, IN 절은 foreach, 값 가공은 bind.
  • 바인딩은 #{} 우선, 동적 구조는 명확하게.
profile
개발자 희망자

0개의 댓글