이것 저것 개발 공부/MyBatis
[MyBatis] MyBatis 동적 쿼리(Dynamic Query) 활용 방법 및 예제
crushed-taro
2025. 3. 13. 21:08
728x90
반응형
[MyBatis] MyBatis XML 기반 Mapper 설정 | 매핑 방법 및 활용 가이드
[MyBatis] MyBatis XML 기반 Mapper 설정 | 매핑 방법 및 활용 가이드
[MyBatis] MyBatis Java 기반 Mapper 설정 | 어노테이션 활용 및 설정 방법 [MyBatis] MyBatis Java 기반 Mapper 설정 | 어노테이션 활용 및 설정 방법[MyBatis] MyBatis XML 설정 가이드 | 환경 구성 및 설정 방법 총정리
crushed-taro.tistory.com
1. MyBatis Dynamic Query 개요
1. MyBatis Dynamic Query란
- 일반적으로 검색 기능이나 다중 입력 처리 등을 수행할 경우, SQL을 실행하는 DAO를 여러 번 호출하거나 batch 기능을 이용하여 버퍼에 담아서 한번에 실행 시키는 방식으로 쿼리를 구현했다.
- MyBatis에서는 이를 동적으로 제어할 수 있는 구문을 제공하여 쿼리를 좀 더 쉽게 구현할 수 있는 기능을 지원한다.
2. MyBatis XML 지원 구문 종류
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
2. MyBatis Dynamic Quert (XML) 구문
1. if
- 동적 쿼리를 구현할 때 가장 기본적으로 사용되는 구문이다.
- (test 속성으로 작성된) 특정 조건을 만족할 경우 내부의 구문을 쿼리에 포함한다.
- 사용 예시
<select id="searchBoard" resultType="arraylist">
SELECT * FROM BOARD
WHERE writer = 'admin'
<if test="title != null">
AND title like #{title}
</if>
</select>
- 다중 if 구문
- 필요로 하는 조건이 1개 이상이면, if 구문을 여러 개 사용할 수도 있다.
- 사용 예시
<select id="searchBoard" resultType="arraylist">
SELECT * FROM BOARD
WHERE writer = 'admin'
<if test="title != null">
AND title like #{title}
</if>
<if test="location != null">
AND location like #{location}
</if>
</select>
2. choose (when, otherwise)
- Java의 if-else, switch 구문과 JSTL의 choose 구문과 유사하며 주어진 구문 중 한 가지 만 수행할 때 사용한다.
- 사용법
- <choose> 안의 <when>은 if 또는 case의 역할을, <otherwise>는 else / default의 역할을 한다.
- <when>태그는 <if>태그와 형식이 흡사하며, 다중 사용할 수 있으나 하나의 조건 만족에 대해서만 구문을 추가한다.
- <otherwise>태그는 <when>태그의 조건 중 어떤 것도 만족하지 않는 경우이다.
<choose>
<when test=" 조건식 ">
쿼리 구문
</when>
<when test=" 조건식 ">
쿼리 구문
</when>
. . .
<otherwise>
쿼리 구문
</otherwise>
</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>
- 특이 SQL
<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>
</select>
- if 조건 중 어느 것도 만족하지 못했을 때 ⇒ 오류
SELECT * FROM BOARD
WHERE
- 첫 번째 if 조건은 만족하지 못하고 두 번째 if 조건만 만족할 때 ⇒ 오류
SELECT * FROM BOARD
WHERE
AND title LIKE ' OOO '
3. trim (where, set)
- <trim>: 쿼리의 구문의 특정 부분을 없앨 때 쓰인다.
- 태그 안의 구문에 처음 시작할 단어와 시작 시 제거해야 할 단어를 명시하여 쿼리를 완성하도록 한다.
- <trim>태그의 속성으로 다음 네 가지를 사용할 수 있다.
- prefix : 처리 후 엘리먼트의 내용이 있으면 가장 앞에 붙여주는 내용 기술
- prefixOverrides : 처리 후 엘리먼트 내용 중 가장 앞에 속성값에 해당하는 문자를 자동 삭제
- suffix : 처리 후 엘리먼트의 내용이 있으면 가장 뒤에 붙여주는 내용 기술
- suffixOverrides : 처리 후 엘리먼트 내용 중 가장 뒤에 속성값에 해당하는 문자를 자동 삭제
- 사용 예시
<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>
- 각각의 조건이 만족할 경우 <trim>태그 내의 구문을 생성하고, 내용이 하나라도 있으면 구문의 가장 앞에 WHERE를 붙인다. 만약 구문의 시작이 AND 또는 OR이면 해당 문자를 떼고 WHERE를 붙여 쿼리를 완성한다. 엘리먼트 내용이 없는 경우 아무것도 생성되지 않는다.
- <where> : 기존 쿼리의 WHERE 절을 동적으로 구현할 때 사용한다.
- <where> 태그는 단순히 WHERE만을 추가하지만, 만약 태그 안의 내용이 'AND' 나 'OR'로 시작할 경우 'AND'나 'OR'를 제거한다.
- 단, 엘리먼트 내에 작성하는 구문에 WHERE를 작성하지 않아야 한다. 그렇지 않으면 WHERE이 2개 생성(WHERE WHERE)될 수 있다.
- where엘리먼트 내 모두 쿼리문이 추가되지 않는 상황이면 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>
- <set> : 기존 쿼리의 UPDATE SET 절을 동적으로 구현할 때 사용한다.
- 고정으로 모든 컬럼을 변경하지 않고, 주어진 일부 값에 대해서만 변경할 수 있는 동적 update 쿼리 구현을 돕는다.
- 쿼리 실행 시 엘리먼트 내 구문 앞에 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 구문으로 변경하면 다음과 같다.
- where와 흡사하나 surffixOverrides 속성을 ',' 로 설정하여 구문의 마지막에 제거할 값을 명시한다.
<update id="updateUser">
update USER
<trim prefix="SET" surffixOverrides=",">
<if test="username != null">
username=#{username},
</if>
<if test="password != null">
password=#{password},
</if>
</trim>
where id=#{id}
</update>
4. foreach
- Java의 for문과 같은 역할을 하는 것으로, 동적 쿼리를 구현할 때 collection에 대한 반복 처리를 제공한다.
- 속성
속성명 | 설명 |
item | 반복될 때 접근 가능한 각 객체 변수 (반복을 수행할 때마다 꺼내올 값의 이름) |
index | 반복 횟수를 가리키는 변수 |
collection | 반복에 쓰일 Collection 객체 (반복을 수행할 대상) |
open | 첫 반복 시 포함할 여는 문자열 즉, <foreach> 엘리먼트 구문의 가장 앞에 올 문자 ex) ' ( ' abc, efg, … ) |
separator | 반복되는 객체를 나열할 때 item 사이에 사용할 구분자 ex) ( abc ' ,' … ) |
close | 마지막 반복 시 포함할 닫는 문자열 즉, <foreach> 엘리먼트 구문의 마지막에 올 문자 ex) ( abc, efg, …' ) ' |
- 사용 예시
<select id="searchBadwords" resultType="arraylist">
SELECT * FROM BOARD
WHERE TITLE IN
<foreach
item="item"
index="index"
collection="list"
open="("
separator=","
close=")">
#{item}
</foreach>
</select>
<!-- 결과 SQL 생성 -->
SELECT * FROM BOARD
WHERE TITLE IN ( 'XXX' , '사행성', '욕설', … )
- parameter 객체로 값을 받아오지 않고, static 필드 혹은 static 메소드에 접근하여 직접 반환 받아 사용하는 것도 가능하다. 이렇게 static 필드 혹은 메소드에 직접 접근해 사용하면 mapper 인터페이스와 service에서 파라미터 넘겨주지 않아도 된다.
- static 필드 접근 시 collection 속성 : @풀클래스명@필드명
- static 메소드 접근 시 collection 속성 : @풀클래스명@메소드명()
- 사용 예시
<foreach collection="@com.section01.xml.Application@createRandomMenuCodeList()" item="menuCode" open="(" separator=", " close=")">
#{ menuCode }
</foreach>
5. bind
- 특정 문장을 미리 생성하여 쿼리에 적용해야 할 경우 사용한다.
- '_parameter' 를 통해 전달 받은 값에 접근하여 구문을 생성한다.
- 사용 예시
<select id="searchBoard" resultType="arraylist">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BOARD
WHERE title LIKE #{pattern}
</select>
3. MyBatis Dynamic Query (JAVA Provider) 구문
1. JAVA Provider란
- MyBatis에서 제공하는 기술로 어노테이션과 Java configuration을 이용하여 동적 쿼리를 작성할 수 있다.
- 인터페이스에서 Provider Annotation을 통해 Provider 클래스를 type으로 설정하고 method로 쿼리ID를 설정하여 매핑한다.
2. @Provider
- 매퍼 인터페이스의 메소드에 어노테이션을 작성해야 한다.
- 어노테이션은 수행할 쿼리문에 따라 @SelectProvider, @InsertProvider, @UpdateProvider, @DeleteProvider로 작성한다.
- 어노테이션의 type 속성에는 Provider의 클래스 타입을, method에는 수행할 쿼리ID를 적는다.
- 사용 예시
@ResultMap("menuResultMap")
@SelectProvider(type=SelectBuilderProvider.class, method="searchMenuByCondition")
List<MenuDTO> searchMenuByCondition(SearchCriteria searchCriteria);
- 파라미터 타입이 Map이나 getter가 있는 DTO가 아닌 기본 자료형 값인 경우, 앞에 @Param 어노테이션을 이용할 수 있다.
- 사용 예시
@DeleteProvider(type=SqlBuilderProvider.class, method="deleteMenu")
int deleteMenu(@Param("code") int code);
public String deleteMenu() {
// 내용
return sql.toString();
}
- 단, param 어노테이션을 사용했다면 Provider 메소드의 매개변수 선언부는 비어있어야 한다.
- SQL 객체를 생성하고 메소드를 활용해 필요한 구문을 붙인 뒤, toString() 메소드를 사용하여 쿼리 문자열로 반환하는 구조로 작성한다.
3. Provider class 작성 예시
- SELECT Provider
public String searchMenuByCondition(SearchCriteria searchCriteria) {
SQL sql = new SQL();
sql.SELECT("A.MENU_CODE") // select 대상 컬럼
.SELECT("A.MENU_NAME")
.SELECT("A.MENU_PRICE")
.SELECT("A.CATEGORY_CODE")
.SELECT("A.ORDERABLE_STATUS")
.FROM("TBL_MENU A"); // select 대상 테이블
if("category".equals(searchCriteria.getCondition())) {
sql.JOIN("TBL_CATEGORY B ON (A.CATEGORY_CODE = B.CATEGORY_CODE)") // join 구문
.WHERE("A.ORDERABLE_STATUS = 'Y'")
.AND()
.WHERE("B.CATEGORY_NAME = #{ value }");
} else if("name".equals(searchCriteria.getCondition())) {
sql.WHERE("A.ORDERABLE_STATUS = 'Y'"
, "A.MENU_NAME LIKE CONCAT('%', #{ value }, '%')"); // 조건절
}
return sql.toString();
}
- WHERE() 내에서 가변인자를 이용하면 자동 AND로 처리하기 때문에 OR를 사용해야 하는 경우 OR()를 사용해야 한다.
- INSERT Provider
public String registMenu(MenuDTO menu) {
SQL sql = new SQL();
sql.INSERT_INTO("TBL_MENU") // insert 대상 테이블
.VALUES("MENU_NAME", "#{ name }") // (insert 대상 컬럼, #{ 필드명 })
.VALUES("MENU_PRICE", "#{ price }")
.VALUES("CATEGORY_CODE", "#{ categoryCode }")
.VALUES("ORDERABLE_STATUS", "#{ orderableStatus }");
return sql.toString();
}
- UPDATE Provider
public String modifyMenu(MenuDTO menu) {
SQL sql = new SQL();
sql.UPDATE("TBL_MENU"); // update 대상 테이블
if(menu.getName() != null && !"".equals(menu.getName())) {
sql.SET("MENU_NAME = #{ name }"); // set 구문 작성
}
if(menu.getPrice() > 0) {
sql.SET("MENU_PRICE = #{ price }");
}
if(menu.getCategoryCode() > 0) {
sql.SET("CATEGORY_CODE = #{ categoryCode }");
}
if(menu.getOrderableStatus() != null && !"".equals(menu.getOrderableStatus())) {
sql.SET("ORDERABLE_STATUS = #{ orderableStatus }");
}
sql.WHERE("MENU_CODE = #{ code }"); // 조건절 작성
return sql.toString();
}
- DELETE Provider
public String deleteMenu(int code) {
SQL sql = new SQL();
sql.DELETE_FROM("TBL_MENU") // delete 대상 테이블
.WHERE("MENU_CODE = #{ code }"); // 조건절 작성
return sql.toString();
}
728x90
반응형