그림과 같이 사용자가 게시물 중 원하는 게시물을 빨리 찾기 위한 검색 기능을 구현해 본다.


검색의 기본 개념은 데이터 베이스에 저장된 많은 데이터 중에서

특정 필드를 대상으로 어떤 값을 가진 데이터를 조회하도록 DBMS에게 시키는 것이다.

예로, 게시물 중에서 제목이나 내용에 “게시판”이라는 글자가 있는 데이터를 모두 찾는 것이다.

이 처리를 위해서는 


1.    리스트에서 찾고자 하는 필드(제목, 내용)와 찾고자 하는 검색어(게시판)를 입력할 수 있도록 해줘야 한다.

2.    그리고 이 값을 SQL로 전달해서 실행하면 된다.

3.    실제로 리스트를 가져오는 SELECT문에 WHERE만 추가해주면 끝이다.


위 3가지만 수정하면 되지만 선행 작업이 필요하다. 

먼저, 다음 코드와 같이 PageVO를 상속 받아서 SearchVO를 생성한다.

위 그림에서 검색하고자 하는 필드(제목, 내용)을 저장하는 searchType,

사용자가 찾으려는 검색어를 저장하는 searchKeyword를 추가해서 생성한다.

PageVO에 이 두 필드를 추가해서 사용해도 되지만

상속받아서 별도의 클래스를 만드는 이유는

실제 사이트 개발시 많이 사용되기 때문에

페이징과 관계된 기능을 PageVO에 두고 상속받아 사용하는 것이 편리하다.

따라서 PageVO와 pageVO를 사용한 것들을 모두 수정해 줘야 한다.

public class SearchVO extends  PageVO  {

            private String searchKeyword = "";           // 검색 키워드

           private String searchType = "";                // 검색 필드: 제목, 내용 

           private String[] searchTypeArr;                // 검색 필드를 배열로 변환

          

           public String getSearchKeyword() {

                     return searchKeyword;

           }

           public void setSearchKeyword(String searchKeyword) {

                     this.searchKeyword = searchKeyword;

           }

           public String getSearchType() {

                     return searchType;

           }

           public void setSearchType(String searchType) {

                     this.searchType = searchType;

           }

           public String[] getSearchTypeArr() {

                     return searchType.split(",");

           }

 }

/common/SearchVO.java


다음 코드 중 Form 테그의 내용은 검색 기능을 위해 추가된 내용이다.

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%>
<%@ taglib prefix="c"  uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>

~~ 생략 ~~
<c:forEach var="listview" items="${listview}" varStatus="status">   
<c:url var="link" value="board3Read">
    <c:param name="brdno" value="${listview.brdno}" />
</c:url>       
<tr>
    <td>
    <c:out value="${searchVO.totRow-((searchVO.page-1)*searchVO.displayRowCount + status.index)}"/>    </td>
    <td><a href="${link}"><c:out value="${listview.brdtitle}"/></a></td>
    <td><c:out value="${listview.brdwriter}"/></td>
    <td><c:out value="${listview.brddate}"/></td>
    <td><c:out value="${listview.brdhit}"/></td>
</tr>
</c:forEach>
</tbody>
</table>
<c:if test="${searchVO.totPage>1}">
<div class="paging">
    <c:forEach var="i" begin="${searchVO.pageStart}" end="${searchVO.pageEnd}" step="1">
<c:url var="pageLink" value="board3List">
        <c:param name="page" value="${i}" />
        </c:url>
        <c:choose>
            <c:when test="${i eq searchVO.page}">
            <c:out value="${i}"/>
            </c:when>
            <c:otherwise>
                 <a href="${pageLink}"><c:out value="${i}"/></a>
            </c:otherwise>
        </c:choose>
    </c:forEach>
</div>
<br/>
</c:if>
<form id="form1" name="form1"  method="post">
<div>
    <input type="checkbox" name="searchType" value="brdtitle" <c:if test="${fn:indexOf(searchVO.searchType, 'brdtitle')!=-1}">checked="checked"</c:if>/>
    <label class="chkselect" for="searchType1">제목</label>
    <input type="checkbox" name="searchType" value="brdmemo" <c:if test="${fn:indexOf(searchVO.searchType, 'brdmemo')!=-1}">checked="checked"</c:if>/>
    <label class="chkselect" for="searchType2">내용</label>
    <input type="text" name="searchKeyword" style="width:150px;" maxlength="50" value='<c:out value="${searchVO.searchKeyword}"/>' onkeydown="if(event.keyCode == 13) { fn_formSubmit();}">
    <input name="btn_search" value="검색" class="btn_sch" type="button" onclick="fn_formSubmit()" />
</div>
/form>   
</body>
</html>


board3List.jsp

체크 박스(searchType)를 이용해 사용자가 검색하고자 하는 필드를 선택할 수 있도록 했다.

제목(brdtitle)과 내용(brdmemo)이 동일한 이름(searchType)을 가지기 때문에

서버로 전달될 때 searchType 변수에 콤마(,)로 구분되어 전달된다.

즉, 사용자가 제목을 체크하면 searchType에 “brdtitle”,

둘 다 체크하면 “brdtitle, brdmemo”이 저장되어 전달 된다.

폼 테그의 action에 url이 지정되지 않았기 때문에 자기 자신(boardList)을 호출한다.


JSP 파일 시작 부분에 테그 라이브러리 선언(<%@ taglib prefix="fn" uri="~~"%>)을 넣어 주고,

fn:indexOf를 사용하였다.

검색 값을 넣고 검색하면 선택된 필드가 계속 선택된 상태로 보여야 한다.

사용자가 선택한 필드는 searchType에 넣어져 서버로 전송되고,

서버에서 처리 후 다시 JSP 파일로 전달 된다.

JSP에서 HTML 생성시 searchType에 각 체크 박스의 값이 있는지 (IndexOF) 확인하여,

있으면 체크(checked="checked")하여 값이 유지되도록 처리한다.


사용자가 선택한 필드와 검색어가 컨트롤과 서비스를 통해 다음과 같이 전달된다.

@RequestMapping(value = "/board3List")
public String boardList(SearchVO searchVO, ModelMap modelMap) throws Exception {
        searchVO.pageCalculate( boardSvc.selectBoardCount(searchVO) );

        List<?> listview   = boardSvc.selectBoardList(searchVO);
       
        modelMap.addAttribute("listview", listview);
        modelMap.addAttribute("searchVO", searchVO);

        return "board3/boardList";
}

board3Ctr.java

public Integer selectBoardCount(SearchVO param) throws Exception {
    return sqlSession.selectOne("selectBoard3Count", param);
}
public List<?> selectBoardList(SearchVO param) throws Exception {
    return sqlSession.selectList("selectBoard3List", param);
}

board3Svc.java

넘겨 받은 SearchVO의 값을 가지고 WHERE 절을 생성한다.

다만, 데이터 개수를 세는 selectBoard3Count, selectBoard3List 에 동일한 조건식이 적용되어야 한다.

동일한 코드를 두 번 입력하면 귀찮고 유지보수 문제가 있어서

다음과 같이 공통으로 빼고 식별자(includeBoard)를 부여한 뒤

이 것을 각 SQL문에서 include하여 사용하는 것이 좋다.

<sql id="includeBoard">
    WHERE BRDDELETEFLAG='N'
    <if test="searchKeyword!=null and searchKeyword!=''">
        <foreach item="item" index="index" collection="searchTypeArr">
            AND ${item} LIKE CONCAT('%', #{searchKeyword},'%' )
         </foreach>
    </if>
</sql>

<select id="selectBoard3Count" resultType="Integer" parameterType="gu.common.SearchVO">
    SELECT COUNT(*)
      FROM TBL_BOARD
     <include refid="includeBoard"/>
</select>

<select id="selectBoard3List" resultType="gu.board3.boardVO" parameterType="gu.common.SearchVO">
    SELECT BRDNO, BRDTITLE, ~~ 생략 ~~
      FROM TBL_BOARD
     <include refid="includeBoard"/>
     ORDER BY BRDNO DESC
     LIMIT ${rowStart-1}, 10
</select>

baord3.xml

검색어가 없으면 조건을 사용하지 않고

전체 리스트를 보여주기 위해 IF문이 사용되었고,

검색어가 있으면 검색 대상 필드의 개수만큼 돌면서(foreach)서 조건을 생성하도록 했다. 

searchTypeArr는 searchType을 콤마(,)를 기준으로 잘라서 배열로 반환하는 것으로

foreach문은 배열 개수만큼 반복하여 다음과 같은 코드가 생성한다.

AND brdtitle LIKE CONCAT('%', '게시판','%')
AND brdmemo LIKE CONCAT('%', '게시판','%')

위의 조건절은 다소 잘 못 된 것으로 두 개의 필드가 선택된 경우

AND가 아닌 OR가 되어야 한다.

즉, 제목과(AND) 내용에 해당 검색어가 있는 것이 아니라

제목이나(OR) 내용에 해당 검색어가 있는 데이터를 찾는 것이다.

즉 다음과 같이 생성되어야 한다.

Mybatis의 foreach문은 보다 더 다양한 기능을 가지고 있으니

잘 활용해서 다음과 같이 작성해 보길 바란다.

AND (
        brdtitle LIKE CONCAT('%', '게시판','%')
   OR brdmemo LIKE CONCAT('%', '게시판','%')
)

검색 필드로 작성자도 추가해 보자.

+ Recent posts