페이징 까지 구현했으니 이제 중요한 기능중에 하나인 검색 기능을 추가하겠습니다.
저희는 페이징 처리하는 부분에서 많은 기능들을 처리하였는데요~
Criteria, PageMaker VO에서 makeQuery 메서드 기억하시나요?
makeQuery 부분에 두 가지 속성을 추가하겠습니다!
- searchType : 검색 타입
- keyword : 검색어
이제 구현해보겠습니다!
Criteria, PageMaker 수정
검색을 했을 때 검색 결과에 따른 정보를 게시물을 삭제, 등록, 수정, 조회 후에도 그대로 유지하려면
URI에 searchType, keyword를 달고 다녀야합니다!
페이징처리에서 사용했던 page와 perPageNum처럼요!
package kr.co.web.domain;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
public class Criteria {
private int page;
private int perPageNum;
private String searchType;
private String keyword;
public Criteria() {
// TODO Auto-generated constructor stub
this.page = 1;
this.perPageNum = 10;
this.searchType = null;
this.keyword = null;
}
// getter, setter 생략
public String makeQuery() {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance()
.queryParam("page", this.page)
.queryParam("perPageNum", this.perPageNum);
if(searchType != null) {
uriComponentsBuilder
.queryParam("searchType", this.searchType)
.queryParam("keyword", this.keyword);
}
return uriComponentsBuilder.build().encode().toString();
}
@Override
public String toString() {
return "Criteria [page=" + page + ", perPageNum=" + perPageNum + "]";
}
}
makeQuery메서드에서 if(searchType != null) 이게 조건을 준 이유는요
사용자가 검색을 하지도 않았는데 굳이 searchType과 keyword를 가지고 갈 필요가 없기때문에 조건을 주었습니다.
PageMaker의 makeQuery도 수정하겠습니다!
package kr.co.web.domain;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
public class PageMaker {
private int displayPageCnt = 10; // 화면에 보여질 페이지 번호 수
private int totalDataCount; // 실제 게시물 수
private int startPage; // 현재 페이지 기준 시작 페이지 번호
private int endPage; // 현재 페이지 기준 끝 페이지 번호
private boolean prev; // 이전 버튼 활성화 여부
private boolean next; // 다음 버튼 활성화 여부
private Criteria cri; //page(현재 페이지), perPageNum(페이지 당 보여질 게시물의 수)
private int finalPage; // 맨끝
private int firstPage; // 맨처음
public PageMaker(Criteria cri) {
this.cri = cri;
}
public int getDisplayPageCnt() {
return displayPageCnt;
}
public void setDisplayPageCnt(int displayPageCnt) {
this.displayPageCnt = displayPageCnt;
}
public int getTotalDataCount() {
return totalDataCount;
}
public void setTotalDataCount(int totalDataCount) {
this.totalDataCount = totalDataCount;
calcData();
}
//startPagem endPage, prev, next, firstPagem finalPage 계산
public void calcData() {
int page = this.cri.getPage();
int perPageNum = this.cri.getPerPageNum();
// 현재 페이지가 2면 2/10 = 0.2 Math.ceil객체를 이용하여 무조건 올림 하면 1 * displayPageCnt = 10
this.endPage = (int)(Math.ceil(page / (double)displayPageCnt) * displayPageCnt);
// endPage - 화면에 보여줄 페이지수를 한뒤 + 1을 하면 된다.
this.startPage = (this.endPage - displayPageCnt) + 1;
// 전체 게시물 수를 통한 endPage
// 데이터가 89 개면 9페이지까지 출력
int tempEndPage = (int)(Math.ceil(totalDataCount / (double)perPageNum));
if(this.endPage > tempEndPage) {
this.endPage = tempEndPage;
}
this.prev = startPage == 1? false : true;
this.next = (endPage * perPageNum) <= totalDataCount ? true : false;
// 맨끝
// +1하는 이유는 저렇게 계산하면 맨끝에서 2번째 페이지가 출력되기때문.
this.finalPage = (int)(Math.ceil(totalDataCount / perPageNum)+1);
// 맨처음
this.firstPage = 1;
}
public String makeQuery(int page) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance()
.queryParam("page", page)
.queryParam("perPageNum", this.cri.getPerPageNum());
if(this.cri.getSearchType() != null) {
uriComponentsBuilder
.queryParam("searchType", this.cri.getSearchType())
.queryParam("keyword", this.cri.getKeyword());
}
return uriComponentsBuilder.build().encode().toString();
}
}
boardMapper.xml 수정
검색을 하기 위해 다음과 같이 select 박스를 구현하겠습니다.
listPage.jsp
<div class="container">
<select id="searchTypeSel" name="searchType">
<option value="">검색조건</option>
<option value="t">제목</option>
<option value="c">내용</option>
<option value="w">작성자</option>
<option value="tc">제목+내용</option>
<option value="all">전체조건</option>
</select>
<input type="text" id="keyword" name="keyword" value="${pageMaker.cri.keyword}" placeholder="검색어를 입력하세요"/>
<button id="searchBtn" class="btn btn-primary">검색</button>
</div>
만약 제목에 슥이 라는 게시물을 검색하려면 다음과 같이 쿼리문을 작성하여 mysql 서버에 있는 blog 테이블에서 데이터를 조회해와야 합니다.
select *
from board
where title like CONCAT ('%', '슥이', '%')
order by board_number desc, create_date desc
limit #{dataStart}, #{perPageNum}
이렇게 검색의 타입마다 쿼리문을 작성하려면 복잡하기도 하고 노가다 입니다.
이럴 때 MyBatis에서 제공하는 if문을 사용하면 검색 조건에 따른 동적 sql문을 손쉽게 추가해줄 수 있습니다.
기존의 listPage 쿼리를 수정해보겠습니다.
<!-- board 페이징 조회 -->
<select id="listPage" resultType="BoardVO">
select *
from board
<if test="searchType != null">
<if test="searchType == 't'.toString()">
where title like CONCAT('%', #{keyword}, '%')
</if>
<if test="searchType == 'c'.toString()">
where contents like CONCAT('%', #{keyword}, '%')
</if>
<if test="searchType == 'w'.toString()">
where name like CONCAT('%', #{keyword}, '%')
</if>
<if test="searchType == 'all'.toString()">
where title like CONCAT('%', #{keyword}, '%')
or contents like CONCAT('%', #{keyword}, '%')
or name like CONCAT('%', #{keyword}, '%')
</if>
</if>
order by board_number desc, create_date desc
limit #{dataStart}, #{perPageNum}
</select>
이렇게 작성했는데 생각해보니 검색된 결과에 따라 페이지 번호도 달라지기 때문에 전체 게시물 수를 구하는
totalCount 쿼리도 수정해주어야 하는데 위와 같이 추가하기에는 코드가 중복이 되기 때문에
<sql>과 <include>를 사용하겠습니다.
<sql>이란 쉽게 설명을 하자면 원터치 텐트라고 생각하시면 됩니다.
<include>는 원터치 텐트를 던지면 알아서 텐트가 쳐지듯이 include태그를 작성하면 펴진다 라고 이해하시면 됩니다!
<!-- 검색 -->
<sql id="searchCondition">
<if test="searchType != null">
<if test="searchType == 't'.toString()">
where title like CONCAT('%', #{keyword}, '%')
</if>
<if test="searchType == 'c'.toString()">
where contents like CONCAT('%', #{keyword}, '%')
</if>
<if test="searchType == 'w'.toString()">
where name like CONCAT('%', #{keyword}, '%')
</if>
<if test="searchType == 'all'.toString()">
where title like CONCAT('%', #{keyword}, '%')
or contents like CONCAT('%', #{keyword}, '%')
or name like CONCAT('%', #{keyword}, '%')
</if>
</if>
</sql>
<!-- board 페이징 조회 -->
<select id="listPage" resultType="BoardVO">
select *
from board
<include refid="searchCondition"></include>
order by board_number desc, create_date desc
limit #{dataStart}, #{perPageNum}
</select>
<!-- 전체 데이터 구하기 -->
<select id="totalCount" resultType="int">
select count(board_number)
from board
<include refid="searchCondition"></include>
</select>
그리고 boardDAO의 listPage 메서드와 totalCount 메서드 둘 다 Criteria를 매개변수로 받았기 때문에
boardMapper까지 searchType과 keyword가 무사히 전달된 것입니다.