Part 15. 검색 처리
15.3 검색 조건 처리를 위한 Criteria의 변화
- 페이징 처리에 사용했던 Criteria의 의도는 단순히 'pageNum'과 'amount'라는 파라미터를 수집하기 위해서다.
- 페이징 처리에 검색 조건 처리가 들어가면 Criteria 역시 변화가 필요하다.
- 검색 조건을 처리하기 위해서는 검색 조건(type)과 검색에 사용하는 키워드가 필요하므로 기존의 Criteria를 확장할 필요가 있다.
- 확장 방법으로는 상속 방법을 이용하거나 직접 Criteria 클래스를 수정하는 방식을 생각해 볼 수 있는데, 예제에서는 직접 Criteria 클래스를 수정한다.
< Criteria 클래스 수정 >
package org.zerock.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Criteria {
private int pageNum;
private int amount;
private String type;
private String keyword;
public Criteria() {
this(1,10);
}
public Criteria(int pageNum, int amount) {
this.pageNum = pageNum;
this.amount = amount;
}
public String[] getTypeArr() {
return type == null? new String[] {}: type.split("");
}
}
- Criteria 클래스는 type과 keyword라는 변수를 추가한다.
- getter/setter는 Lombok을 통해 생성하고, getTypeArr은 검색 조건이 각 글자(T, W, C)로 구성되어 있으므로 검색 조건을 배열로 만들어 한 번에 처리하기 위함이다.
- getTypeArr()을 이용해 MyBatis의 동적 태그를 활용할 수 있다.
15.3.1 BoardMapper.xml에서 Criteria 처리
- BoardMapper.xml은 기존의 getListWithPaging()을 수정해 동적 SQL을 처리한다.
< BoardMapper.xml의 검색 및 페이징 처리 >
<select id="getListWithPaging" resultType="org.zerock.domain.BoardVO">
<![CDATA[
select
bno, title, content, writer, regdate, updatedate
from
(
select /*+INDEX_DESC(tbl_board pk_board) */
rownum rn, bno, title, content, writer, regdate, updatedate
from
tbl_board
where
]]>
<trim prefix="(" suffix=") AND " prefixOverrides="OR">
<foreach item='type' collection="typeArr">
<trim prefix="OR">
<choose>
<when test="type == 'T'.toString()">
title like '%'||#{keyword}||'%'
</when>
<when test="type == 'C'.toString()">
content like '%'||#{keyword}||'%'
</when>
<when test="type == 'W'.toString()">
writer like '%'||#{keyword}||'%'
</when>
</choose>
</trim>
</foreach>
</trim>
<![CDATA[
rownum <= #{pageNum} * #{amount}
)
where rn > (#{pageNum} -1) * #{amount}
]]>
</select>
- 검색 조건이 3가지이므로 총 6가지의 조합이 가능하지만, 각 문자열을 이용해 검색 조건을 결합하는 형태로 하면 3개의 동적 SQL 구문만으로 처리를 할 수 있다.
- < foreach >를 이용해 검색 조건들을 처리하는데 typeArr이라는 속성을 이용한다.
- MyBatis는 원하는 속성을 찾을 때 getTypeArr()과 같이 이름에 기반을 두어 검색하기 때문에 Criteria에서 만들어둔 getTypeArr() 결과인 문자열의 배열이 < foreach >의 대상이 된다(MyBatis는 엄격하게 Java Beans의 규칙을 따르지 않고, get/set 메서드만을 활용하는 방식이다.).
- < choose > 안쪽의 동적 SQL은 'OR title... OR content... OR writer...' 와 같은 구문을 만들어내게 된다.
- 따라서 바깥쪽에서는 < trim > 을 이용해 맨 앞에서 생성되는 'OR'을 없애준다.
- 위의 동적 SQL은 상황에 따라 다음과 같은 SQL을 생성한다.
- 동적 SQL은 경우에 따라 여러 종류의 SQL이 생성될 수 있으므로 제대로 동작하는지 반드시 여러 번의 확인을 거쳐야만 한다.
- 기존에 BoardMapperTests를 만들어 두었으니 이를 이용해 테스트 코드를 작성한다.
< src/test/java 밑의 BoardMapperTests 클래스 >
@Test
public void testSearch() {
Criteria cri = new Criteria();
cri.setKeyword("새로");
cri.setType("TC");
List<BoardVO> list = mapper.getListWithPaging(cri);
list.forEach(board -> log.info(board));
}
- testSearch()는 Criteria 객체의 input과 keyword를 넣어 원하는 SQL이 생성되는지 확인하기 위함이다.
- 중요한 것은 실행 결과가 아니라 실행할 때 만들어지는 SQL이다.
- 아래와 같이 각 상황에 맞게 SQL이 올바르게 만들어지는지 확인해야 한다.
< sql > < include >와 검색 데이터의 개수 처리
- 동적 SQL을 이용해 검색 조건을 처리하는 부분은 해당 데이터의 개수를 처리하는 부분에서도 동일하게 적용되어야만 한다.
- 이 경우 가장 간단한 방법은 동적 SQL을 처리하는 부분을 그대로 복사해서 넣어줄 수 있지만, 만일 동적 SQL을 수정하는 경우에는 매번 목록을 가져오는 SQL과 데이터 개수를 처리하는 SQL쪽을 같이 수정해야 한다.
- MyBatis는 < sql >이라는 태그를 이용해 SQL의 일부를 별도로 보관하고, 필요한 경우에 include시키는 형태로 사용할 수 있다.
< BoardMapper.xml의 목록과 데이터 개수 처리 >
<sql id="criteria">
<trim prefix="(" suffix= ") AND " prefixOverrides="OR">
<foreach item='type' collection="typeArr">
<trim prefix="OR">
<choose>
<when test="type == 'T'.toString()">
title like '%'||#{keyword}||'%'
</when>
<when test="type == 'C'.toString()">
content like '%'||#{keyword}||'%'
</when>
<when test="type == 'W'.toString()">
writer like '%'||#{keyword}||'%'
</when>
</choose>
</trim>
</foreach>
</trim>
</sql>
<select id="getListWithPaging" resultType="org.zerock.domain.BoardVO">
<![CDATA[
select
bno, title, content, writer, regdate, updatedate
from
(
select /*+INDEX_DESC(tbl_board pk_board) */
rownum rn, bno, title, content, writer, regdate, updatedate
from
tbl_board
where
]]>
<include refid="criteria"></include>
<![CDATA[
rownum <= #{pageNum} * #{amount}
)
where rn > (#{pageNum} -1) * #{amount}
]]>
</select>
<select id="getTotalCount" resultType="int">
select count(*) from tbl_board
where
<include refid="criteria"></include>
bno > 0
```
- < sql > 태그는 id라는 속성을 이용해 필요한 경우에 동일한 SQL의 일부를 재사용할 수 있게 한다.