1. 검색 / 필터링 조건의 정의
📍 대부분의 서비스에서는 검색 기능을 제공. 단순히 제목이나 내용 등을 검색하는 경우도 있고, 복잡한 검색 조건을 필터링 filtering 하는 경우도 있음
📍 검색 search는 'A 혹은 B 혹은 C'와 같이 찾고자 하는 경우 검색 조건들은 주로 OR 조건으로 연결되는 경우가 많음
➡️ 예를 들어 '제목 or 내용 or 작성자'가 xxx인 경우와 같은 데이터가 있을수도 있고, 없을 수도 있음
📍 필터링 filtering은 'A인 동시에 B에도 해당'한다는 개념. 필터링은 주로 특정한 범위나 범주의 값으로 걸러내는 방식
➡️ 예를 들어 '완료된 일 중에서 특정 날짜까지 끝난 Todo'는 'A & B'와 같이 AND라는 개념의 필터링이 적용
다음과 같은 검색과 필터링 조건을 구성하고 처리
- 제목 title과 작성자 writer 는 키워드 keyword를 이용하는 검색 처리
- 완료 여부를 필터링 처리
- 특정한 기간을 지정 from, to 한 필터링 처리
- 검색과 필터링에 필요한 데이터는 다음과 같이 구분
* 제목, 작성자 검색에 사용하는 문자열 - keyword
* 완료 여부에 사용되는 boolean 타입 - finished
* 특정 기간 검색을 위한 LocalDate 변수 2개 - from, to
2. 검색 / 필터링 조건의 결정
검색 기능을 개발할 때는 우선 검색 기능의 경우의 수를 구분하는 작업이 필요
- 검색 / 필터링의 종류가 '완료 여부, 제목, 작성자, 기간'들의 조합으로 구성
- 검색 종류를 types라고 지정해서 '제목(t), 작성자(w)'로 구분해서 검색의 실제 값은 검색 종류에 따라 키워드 keyword를 이용
- 검색은 목록 기능에 사용하는 PageRequestDTO에 필요한 변수들을 추가해서 구성
PageRequestDTO에 types, keyword, finished, from, to 변수를 새롭게 추가
private String[] types; // 검색 경우의 수 1) title 2) writer 3) title, writer
private String keyword; // 검색어
private boolean finished;
private LocalDate from;
private LocalDate to;
3. types에 따른 동적 쿼리
Mybatis에는 실행 시에 쿼리를 만들 수 있는 여러 태그들을 제공
1) foreach, if
🤓 <foreach>는 반복 처리를 위해 제공
🤓 <foreach>의 대상은 배열이나 List, Map, Set과 같은 컬렉션 계열이나 배열을 이용
실습을 위해 {"t", "w"}와 같은 types를 PageRequestDTO에 설정하고 테스트를 진행
기존의 TodoMapperTests 클래스에 새로운 메서드를 추가
@Test
public void testSelectSearch() {
PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
.page(1)
.size(10)
.types(new String[]{"t", "w"})
.keyword("AAAA")
.build();
List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
voList.forEach(vo -> log.info(vo));
}
📍 TodoMapper의 selectList()는 PageRequestDTO를 파라미터로 받고 있기 때문에 변경 없이 바로 사용 가능하므로 TodoMapper.xml만 수정하면 됨
TodoMapper.xml에서는 <select id="selectList"> 태그에 MyBatis의 <foreach>를 적용
<select id="selectList" resultType="com.example.spring_ex_01_2404.domain.TodoVO">
select * from tbl_todo
<foreach collection="types" item="type">
#{type}
</foreach>
order by tno desc limit #{skip}, #{size}
</select>
👾 현재 PageRequestDTO의 types는 {"t", "w"}이므로 테스트 코드를 실행하면 다음과 같은 코드가 만들어지는 것을 확인
(쿼리문이 아직 완성된 상태가 아니라서 에러가 발생)
👾 sql 부분만 보면 다음과 같이 sql 이 실행
select * from tbl_todo ? ? order by tno desc limit ?, ?
✓ 't'와 'w'가 전달되었기 때문에 'from tbl_todo' 뒤에 두 개의 "?"가 생성된 것을 확인.
<if> 적용
<select id="selectList" resultType="com.example.spring_ex_01_2404.domain.TodoVO">
select * from tbl_todo
<foreach collection="types" item="type" open="(" close=")" separator=" OR">
<if test="type == 't'.toString()">
title like concat('%', #{keyword}, '%')
</if>
<if test="type =='w'.toString()">
writer like concat('%', #{keyword}, '%')
</if>
</foreach>
order by tno desc limit #{skip}, #{size}
</select>
👾 open과 close를 이용해서 '()'와 배열을 처리하면서 중간에는 OR을 추가해서 다음과 같은 쿼리가 생성
select * from tbl_todo (title like concat('%', ?, '%') OR
writer like concat('%', ?, '%'))
order by tno desc limit ?, ?
2) <where>
📍 쿼리에 where 키워드가 빠져있음. 이것은 만일에 types가 없는 경우에는 쿼리문에 where를 생성하지 않기 위함.
📍 <where>는 태그 안쪽에서 문자열이 생성되어야만 where 키워드를 추가.
📍 이를 이용해서 types가 null이 아닌 경우를 같이 적용하면 다음과 같이 작성.
<select id="selectList" resultType="com.example.spring_ex_01_2404.domain.TodoVO">
select * from tbl_todo
<where>
<if test="types != null and types.length > 0">
<foreach collection="types" item="type" open="(" close=")" separator=" OR">
<if test="type == 't'.toString()">
title like concat('%', #{keyword}, '%')
</if>
<if test="type =='w'.toString()">
writer like concat('%', #{keyword}, '%')
</if>
</foreach>
</if>
</where>
order by tno desc limit #{skip}, #{size}
</select>
3) <trim>과 완료 여부 / 만료일 필터링
완료 여부 (f)와 만료 기간 (d)에 대한 처리
📍 완료 여부는 PageRequestDTO의 finished 변수 값이 true인 경우에만 'finished = 1'과 같은 문자열이 만들어지도록 구성
✓ 주의점은 앞의 검색 조건(제목, 작성자)이 있는 경우에는 'and finished = 1'의 형태로 만들어져야 하고,
그렇지 않은 경우에는 바로 'finished = 1'이 되어야 함
📍 MyBatis의 <trim>이 이런 경우에 사용. <where>과 유사하게 동작하면서 필요한 문자열을 생성하거나 제거할 수 있음
<if test = "finished">
<trim prefix="and">
finished = 1
</trim>
</if>
</where>
order by tno desc limit #{skip}, #{size}
</select>
👾 finished 값을 이용해서 SQL 문을 생성해 내는데 <trim>을 적용. prefix를 적용하게 되면 상황에 따라서 'and'가 추가.
테스트 코드 수정해서 확인
@Test
public void testSelectSearch() {
PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
.page(1)
.size(10)
.types(new String[]{"t", "w"})
.keyword("스프링")
.finished(true)
.build();
List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
voList.forEach(vo -> log.info(vo));
}
같은 방식으로 만료일 dueDate 을 처리하면 다음과 같음
<if test="from != null and to != null">
<trim prefix="and">
dueDate between #{from} and #{to}
</trim>
</if>
4) <sql> 과 <include>
MyBatis의 동적 쿼리 적용은 단순히 목록 데이터를 가져오는 부분과 전체 개수를 가져오는 부분에도 적용되어야 함
📍전체 개수를 가져오는 TodoMapper의 getCount()에 파라미터에 PageRequestDTO 타입을 지정한 이유 역시 동적 쿼리를 적용하기 위함
📍 MyBatis에는 <sql> 태그를 이용해서 동일한 SQL 조각을 재사용할 수 있는 방법을 제공
➡️ 동적 쿼리 부분을 <sql>로 분리하고 동일하게 동적 쿼리가 적용될 부분은 <include>를 이용해서 작성
TodoMapper.xml에서 <sql id='search'>로 동적 쿼리 부분을 분리
<sql id="search">
<where>
<if test="types != null and types.length > 0">
<foreach collection="types" item="type" open="(" close=")" separator=" OR">
<if test="type == 't'.toString()">
title like concat('%', #{keyword}, '%')
</if>
<if test="type =='w'.toString()">
writer like concat('%', #{keyword}, '%')
</if>
</foreach>
</if>
<if test = "finished">
<trim prefix="and">
finished = 1
</trim>
</if>
<if test="from != null and to != null">
<trim prefix="and">
dueDate between #{from} and #{to}
</trim>
</if>
</where>
</sql>
<select id="selectList" resultType="com.example.spring_ex_01_2404.domain.TodoVO">
select * from tbl_todo
<include refid="search"></include>
order by tno desc limit #{skip}, #{size}
</select>
<select id="getCount" resultType="int">
select COUNT(tno) FROM tbl_todo
<include refid="search"></include>
</select>
테스트 코드에서는 TodoMapper의 selectList()와 getCount()를 호출해서 결과를 확인
@Test
public void testSelectSearch() {
PageRequestDTO pageRequestDTO = PageRequestDTO.builder()
.page(1)
.size(10)
.types(new String[]{"t", "w"})
.keyword("스프링")
//.finished(true)
.from(LocalDate.parse("2022-04-28"))
.to(LocalDate.parse("2024-04-30"))
.build();
List<TodoVO> voList = todoMapper.selectList(pageRequestDTO);
voList.forEach(vo -> log.info(vo));
log.info(todoMapper.getCount(pageRequestDTO));
}
[ 내용 참고 : IT 학원 강의 ]
'Spring & Spring Boot' 카테고리의 다른 글
[Spring] 간단한 게시판 만들기 | 등록 · 목록 · 조회 (0) | 2024.05.01 |
---|---|
[Spring] 검색 조건을 위한 화면 처리 (0) | 2024.04.30 |
[Spring] 목록 데이터를 위한 DTO와 서비스 계층 (0) | 2024.04.29 |
[Spring] 페이징 처리를 위한 TodoMapper (0) | 2024.04.28 |
[Spring] Todo의 삭제 | 수정 기능 개발 (1) | 2024.04.28 |