Spring - 9 (게시물 검색 기능 / 타입 검색 기능)
하단의 검색창을 만들고 타입과 키워드를 입력하면 해당 정보에 맞는 게시물이 나오도록 기능을 구현한다.
먼저 domain패키지의 Criteria 클래스에 검색타입 변수들을 만들어준다.
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Criteria {
// Criteria : 페이징 처리 기준을 갖는 밸류 오브젝트
private int pageNum; // 현재 페이지 번호.
private int amount; // 페이지당 게시물수
private String keyword; // 검색 키워드
private String type; // 검색 타입
private String[] typeArr;
public Criteria() {
this(1,10); // 아래쪽 전달값 2개 생성자 호출.
}
public Criteria(int pageNum, int amount) {
this.pageNum = pageNum;
this.amount = amount;
}
public void setType(String type) {
this.type = type;
this.typeArr = type.split("");
}
}
@Setter 어노테이션이 있지만, 배열에 입력된 검색어의 공백부분을 지워야하기 때문에 새로 setType을 생성하였다.
BoardMapper.xml에 쿼리를 추가한다.
<!-- 검색 조건문 -->
<sql id="criteria">
<trim prefix="AND (" suffix=")" prefixOverrides="OR">
<foreach collection="typeArr" item="type">
<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>
- trim을 만나고 하부에 쿼리가 있다면, 하부 쿼리부터 수행.
- foreach로 배열의 요소가 끝날때까지 하부 쿼리 수행.
- 또, trim을 만나서 // choose ~ when 은 스위치 케이스와 비슷.
- 쿼리문을 추가하고, 작은 trim 옆의 prefix 속성을 적용하여 or 를 쿼리문에 추가. (배열 요소가 끝날때까지 반복)
- 내부적 처리가 완료되면 마지막으로 바깥쪽 trim의 prefixOverrides 속성으로 시작 단어가 or 라면,
- 소괄호 여는 기호로 변경'(' 하고 생성된 쿼리문의 마지막에 ') and'를 추가하여 쿼리문 생 성.
getListWithPaging 쿼리와 getTotal 쿼리에 다음 코드를 추가한다.
<select id="getListWithPaging"
resultType="kr.icia.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 rownum <= #{pageNum} * #{amount}
]]>
<if test="keyword != null">
<!-- and title like '%' || #{keyword} || '%' -->
<include refid="criteria"></include>
</if>
<![CDATA[
)
where rn > (#{pageNum} -1) * #{amount}
]]>
</select>
<select id="getTotal" resultType="int">
select count(*) from tbl_board
<if test="keyword != null">
<!-- where title like '%'||#{keyword}||'%' -->
where bno > 0 <include refid="criteria"></include>
</if>
</select>
위에서 적은 검색 조건문의 id가 criteria인데, 이것을 include refied를 통해서 일종의 모듈화 방식으로 사용하였다.
이제 View단계에서 코드를 수정한다.
list.jsp의 table 태그가 끝나는 지점 바로 아래에 타입 및 검색창을 추가한다.
<div class="search_wrap">
<div class="search_area">
<select name="type">
<option value="" <c:out value="${pageMaker.cri.type == null?'selected':'' }"/>>--</option>
<option value="T" <c:out value="${pageMaker.cri.type eq 'T'?'selected':'' }"/>>제목</option>
<option value="C" <c:out value="${pageMaker.cri.type eq 'C'?'selected':'' }"/>>내용</option>
<option value="W" <c:out value="${pageMaker.cri.type eq 'W'?'selected':'' }"/>>작성자</option>
<option value="TC" <c:out value="${pageMaker.cri.type eq 'TC'?'selected':'' }"/>>제목 + 내용</option>
<option value="TW" <c:out value="${pageMaker.cri.type eq 'TW'?'selected':'' }"/>>제목 + 작성자</option>
<option value="TCW" <c:out value="${pageMaker.cri.type eq 'TCW'?'selected':'' }"/>>제목 + 내용 + 작성자</option>
</select>
<input type="text" name="keyword" value="${pageMaker.cri.keyword }">
<button>Search</button>
</div>
</div>
select태그와 option은 검색타입을 지정하는 창이고, input태그는 검색어를 입력할 창이다.
Search버튼엔 이제 작동기능을 연결해줘야한다. 스크립트부분에 아래 코드를 추가한다.
$(".search_area button").on("click", function(e){
var type = $(".search_area select").val();
var keyword = $(".search_area input[name='keyword']").val();
var sKey = '<c:out value="${pageMaker.cri.keyword}"/>';
// Criteria 필드 멤버의 검색어.
console.log("이전 검색어: " + sKey);
console.log("현재 검색어: " + keyword);
if(!type){
alert("키워드를 입력하세요");
return false;
}
if(!keyword){
alert("키워드를 입력하세요.");
return false;
}
if(sKey != keyword){
actionForm.find("input[name='pageNum']").val(1);
// 새로운 검색어라면 1페이지로 이동.
}
actionForm.find("input[name='type']").val(type);
actionForm.find("input[name='keyword']").val(keyword);
// 1페이지로 이동하는 구문
/* actionForm.find("input[name='pageNum']").val(1); */
actionForm.submit();
});
type, keyword 변수 2개에 우리가 선택한 타입과 검색어가 담겨지게 된다.
sKey 변수는 지난번 검색어와 현재 입력한 검색어가 같을 경우 페이지가 1페이지로
이동하지않게 해주기 위해 생성하였다. if문에서 skey와 keyword가 같지 않을 경우에 1페이지로 이동하게 구현하였다.
actionForm은 form태그에 get방식으로 데이터를 보내준다. 데이터는 type과 keyword이다.
검색 후 페이지 이동시에도 검색이 풀려버리지 않기 위해서 form태그 부분에 코드를 추가한다.
<form id="actionForm" action="/board/list" method="get">
<input type="hidden" name="pageNum" value="${pageMaker.cri.pageNum }">
<input type="hidden" name="amount" value="${pageMaker.cri.amount }">
<input type="hidden" name="keyword" value="${pageMaker.cri.keyword }">
<input type="hidden" name="type" value="${pageMaker.cri.type }">
</form>
이제 타입 및 검색창과 기능이 제대로 동작하는지 살펴보자.
아직 읽기 페이지, 수정페이지, 수정 후, 삭제 후, 목록 버튼등
페이지간의 keyword와 type을 전달하지 못하기때문에 해당 기능들을 이용시 검색한 기록이 풀려버리는 일이 발생한다.
form태그에 pageNum과 amount를 넘겼던것처럼 모든 페이지에 keyword와 tpye을 추가한다.
get.jsp
기존에 <a>태그안에 정보를 줘서 했지만, 따로 스크립트로 분류해서 처리한다.
<button data-oper="modify" class="btn btn-warning" id="modify_btn">수정</button>
<button data-oper="list" id="boardList_Btn" class="btn btn-info"> 목록</button>
<script>
$(document).ready(function() {
var formObj = $("form");
$("#boardList_Btn").on("click", function(e) {
e.preventDefault();
var operation = $(this).data("oper");
console.log(operation);
if(operation === 'list'){
formObj.attr("action", "/board/list");
formObj.find("#bno").remove();
}
formObj.submit();
});
$("#modify_btn").on("click", function(e) {
e.preventDefault();
var operation = $(this).data("oper");
console.log(operation);
if(operation === 'modify'){
formObj.attr("action","/board/modify");
}
formObj.submit();
});
});
</script>
<form id="infoForm" action="/board/modify" method="get">
<input type="hidden" id="bno" name="bno"
value='<c:out value="${board.bno }"/>'> <input
type="hidden" name="pageNum"
value='<c:out value="${cri.pageNum }"/>'> <input
type="hidden" name="amount"
value='<c:out value="${cri.amount }"/>'> <input
type="hidden" name="keyword"
value='<c:out value="${cri.keyword }"/>'> <input
type="hidden" name="type" value='<c:out value="${cri.type }"/>'>
</form>
modify.jsp 에서는 append 방식으로 했기 때문에 controller에 추가해준다.
@PostMapping("/modify")
public String modify(BoardVO board, RedirectAttributes rttr, Criteria cri) {
log.info("modify: " + board);
if(service.modify(board)) {
rttr.addFlashAttribute("result","success");
}
// 수정이 성공하면 success 메시지가 포함되어 이동.
// 실패해도 메시지 빼고 이동.
rttr.addAttribute("pageNum",cri.getPageNum());
rttr.addAttribute("amount",cri.getAmount());
rttr.addAttribute("type",cri.getType());
rttr.addAttribute("keyword",cri.getKeyword());
return "redirect:/board/list";
}
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, RedirectAttributes rttr,
Criteria cri) {
log.info("remove..." + bno);
if(service.remove(bno)) {
rttr.addFlashAttribute("result", "success");
}
rttr.addAttribute("pageNum",cri.getPageNum());
rttr.addAttribute("amount",cri.getAmount());
rttr.addAttribute("type",cri.getType());
rttr.addAttribute("keyword",cri.getKeyword());
return "redirect:/board/list";
}
modify.jsp의 스크립트에 마찬가지로 apppned를 추가해준다.
$(document).ready(function(){
var formObj = $("#modifyForm"); // 문서중 form 요소를 찾아서 변수에 할당
/* var iform = $("#infoForm"); */
$('button').on("click", function(e) {
// 버튼이 클릭된다면 아래 함수 수행, e라는 이벤트 객체를 전달하면서
e.preventDefault(); // 기본 이벤트 동작 막기.
var operation = $(this).data("oper");
// 버튼에서 oper 속성 읽어서 변수에 할당.
console.log(operation);
// 브라우저 로그로 oper값 출력
if(operation === 'remove'){
formObj.attr("action", "/board/remove");
// form에 액션 속성을 변경
} else if (operation === 'list'){
/* iform.find("#bno").remove();
iform.attr("action", "/board/list"); */
var PageNumTag = $("input[name='pageNum']");
var amountTag = $("input[name='amount']");
var keyword = $("input[name='keyword']");
var typetag = $("input[name='type']");
formObj.attr("action", "/board/list").attr("method","get");
formObj.empty(); // 폼의 내용들 비우기.
/* 폼의 내용을 비우고 재설정 하는 이유는, 목록 이동시 게시물의 제목, 내용, 작성자 등은 전달할 필요X */
formObj.append(PageNumTag);
formObj.append(amountTag);
formObj.append(keyword);
formObj.append(typetag);
}
formObj.submit();
// 위의 조건이 아니라면 수정 처리.
});
/* $("#list_btn").on("click", function(e){
iform.find("#bno").remove();
iform.attr("action", "/board/list");
iform.submit();
}); */
});