페이징,검색 개요
페이징,검색은 List만 신경쓰면됩니다.
우리가 만든 List페이지에서는 DB에 있는 데이터가 한 페이지에 전부 나온다.
하지만 몇 천번째 글을 읽기 위해 스크롤을 내리는것은 비효율적이고,
UI도 깔끔해 보이지않는다.
또 원하는 글을 찾기도 힘들다.
네트워크 사용량도 많아진다.
그에 비해 다음 정부24 페이지를 보자.
화면에 딱 10개씩(페이징)만 나오고 검색기능 까지 있어서 내가 원하는 글을 찾기도 용이하다.
우리도 freeList.jsp와 memberList.jsp에 페이징,검색기능을 적용해 볼 것이다.
페이징
페이징VO
public class PagingVO {
//입력받는 데이터
private int curPage=1; // 현재 페이지 번호
private int rowSizePerPage=10; // 한 페이지당 레코드 수 기본10
private int pageSize=10; // 페이지 리스트에서 보여줄 페이지 갯수 이거는 보통 10 or 5 안 변함
private int totalRowCount ; // 총 레코드 건수
//입력받는 데이터를 통해 계산되는 값
private int firstRow ; // 시작 레크드 번호
private int lastRow; // 마지막 레크드 번호
private int totalPageCount; // 총 페이지 건수
private int firstPage; // 페이지 리스트에서 시작 페이지 번호
private int lastPage; // 페이지 리스트에서 마지막 페이지 번호
//page계산
public void pageSetting() {
}
//getter/setter
}
각각의 필드의 의미를 말로만 이해하긴 쉽지않으니 그림을 첨부한다.
클라이언트 측에서는 curPage, rowSizePerPage 값이 넘어온다.
totalRowCount는 count쿼리를 통해 구하고, pageSize는 보통 정해져있다.
curPage,rowSizePerPage,totalRowCount,pageSize를 통해
나머지 필드 값을 계산하면 페이징에 필요한 모든 값들을 구할 수 있다.
PagingVO의 pageSetting 메소드
//page계산
public void pageSetting() {
totalPageCount=(totalRowCount-1)/rowSizePerPage+1;
firstRow=(curPage-1)*rowSizePerPage+1;
lastRow=firstRow+rowSizePerPage-1;
if(lastRow>totalRowCount) lastRow=totalRowCount;
firstPage=(curPage-1)/pageSize*pageSize+1;
lastPage=firstPage+pageSize-1;
if(lastPage>totalPageCount) lastPage=totalPageCount;
}
페이징 쿼리
페이징쿼리의 핵심은 curPage를 이용해 맞는 firstRow와 lastRow에 해당하는 데이터를
쿼리에서 가지고 오는 것이다. pageSetting()메소드를 통해 firstRow와 lastRow를 구했다면
오라클에서 다음과 같이 쿼리를 쓰면 될 거같다.
SELECT * FROM table WHERE rownum #{firstRow} BETWEEN #{lastRow} ORDER BY id
그런데 오라클에서의 쿼리 순서는
FROM-WHERE- GROUP BY -HAVING -SELECT - rownum할당 - ORDER BY순이다.
결국 ORDER BY가 먼저 적용되기 위해 1번 ,
rownum을 where절에서 사용하기 위해 또 한번 총 2번의 sub쿼리를 작성해야한다.
그래서 페이징쿼리는 다음과 같이 작성해야 된다.
SELECT *
FROM
(SELECT a.*,rownum AS rnum
FROM (
SELECT col1, col2
FROM table1
ORDER BY bo_no DESC
) a
) b
WHERE rnum between 11 and 20 --curPage가 2일 때
페이징 적용
List화면에서 페이징을 구현하기 위해선 DB의 전체 데이터 갯수를 구해야한다.
그래서 먼저 전체 개수를 구하는 getTotalRowCount를 실행한 다음,
전체 데이터 개수를 통해 PagingVO의 pageSetting() 을 실행해야 한다.
그 다음에 페이징 쿼리를 실행한다.
IFreeBoardDao
public int getTotalRowCount( PagingVO paging);
public List<FreeBoardVO> getBoardList(PagingVO paging);
freeBoard.xml
<select id="getTotalRowCount" resultType="int" parameterType="com.study.common.vo.PagingVO" >
SELECT count(*)
FROM free_board
WHERE 1=1
<include refid="searchInFree" />
</select>
<select id="getBoardList" resultType="com.study.free.vo.FreeBoardVO" parameterType="com.study.common.vo.PagingVO">
SELECT *
FROM
(SELECT a.*,rownum AS rnum
FROM
(
SELECT
to_char(bo_reg_date,'YYYY-MM-DD') AS bo_reg_date
,to_char(bo_mod_date,'YYYY-MM-DD') AS bo_mod_date
, bo_no , bo_title , bo_category
, bo_writer , bo_pass , bo_content
, bo_hit , bo_del_yn
,b. comm_nm AS bo_category_nm
FROM free_board a ,comm_code b
WHERE a.bo_category = b.comm_cd
ORDER BY bo_no DESC ) a ) b
WHERE rnum between #{firstRow} and #{lastRow}
</select>
IFreeBoardService
public List<FreeBoardVO> getBoardList(PagingVO paging);
FreeBoardServiceImpl
getTotalRowCount() 실행 후 pageSetting() 메소드 실행
@Override
public List<FreeBoardVO> getBoardList(PagingVO paging) {
try (SqlSession session = sqlSessionFactory.openSession()) {
IFreeBoardDao freeBoardDao= session.getMapper(IFreeBoardDao.class);
int totalRowCount= freeBoardDao.getTotalRowCount(paging);
paging.setTotalRowCount(totalRowCount);
paging.pageSetting();
List<FreeBoardVO> freeBoardList = freeBoardDao.getBoardList(paging);
return freeBoardList;
}
}
FreeList는 페이징 결과를 req에 담아 화면에 보여줘야 한다.
@Override
public String process(HttpServletRequest req, HttpServletResponse response) throws Exception {
ICommCodeService codeService=new CommCodeServiceImpl();
List<CodeVO> cateList = codeService.getCodeListByParent("BC00");
req.setAttribute("cateList",cateList);
IFreeBoardService freeBoardService=new FreeBoardServiceImpl();
PagingVO paging=new PagingVO();
BeanUtils.populate(paging,req.getParameterMap());
req.setAttribute("paging", paging);
List<FreeBoardVO> freeBoardList
= freeBoardService
.getBoardList(paging,search,searchCategory);
req.setAttribute("freeBoardList",freeBoardList);
return "free/freeList";
}
freeList.jsp
다음의 태그를 table 밑에다 작성하면 된다. (pagingVO의 값을 이용해 작성)
<!-- START : 페이지네이션 -->
<nav class="text-center">
<ul class="pagination">
<!-- 첫 페이지 -->
<li><a href="freeList.wow?curPage=1" data-page="1"><span aria-hidden="true">«</span></a></li>
<!-- 이전 페이지 -->
<c:if test="${paging.firstPage ne 1}">
<li><a href="freeList.wow?curPage=${paging.firstPage-1}" data-page="${paging.firstPage-1}"><span aria-hidden="true"><</span></a></li>
</c:if>
<!-- 페이지 넘버링 -->
<c:forEach begin="${paging.firstPage}" end="${paging.lastPage}" var="i" >
<c:if test="${paging.curPage ne i}">
<li><a href="freeList.wow?curPage=${i}" data-page="${i}">${i}</a></li>
</c:if>
<c:if test="${paging.curPage eq i}">
<li class="active"><a href="#">${i}</a></li>
</c:if>
</c:forEach>
<!-- 다음 페이지 -->
<c:if test="${paging.lastPage ne paging.totalPageCount}">
<li><a href="freeList.wow?curPage=${paging.lastPage+1}" data-page="${paging.lastPage+1}"><span aria-hidden="true">></span></a></li>
</c:if>
<!-- 마지막 페이지 -->
<li><a href="freeList.wow?curPage=${paging.totalPageCount}" data-page="${paging.totalPageCount}"><span aria-hidden="true">»</span></a></li>
</ul>
</nav>
<!-- END : 페이지네이션 -->
결과화면
curPage를 변경할 때마다 가지고오는 데이터가 달라질 것이다.
검색
먼저 freeList.jsp에 다음과 같은 검색태그를 추가하자.
<!-- START : 검색 폼 -->
<div class="panel panel-default">
<div class="panel-body">
<form name="search" action="freeList.wow" method="post" class="form-horizontal">
<input type="hidden" name="curPage" value="${paging.curPage}">
<input type="hidden" name="rowSizePerPage" value="${paging.rowSizePerPage}">
<div class="form-group">
<label for="id_searchType" class="col-sm-2 control-label">검색</label>
<div class="col-sm-2">
<select id="id_searchType" name="searchType" class="form-control input-sm">
<option value="T" ${search.searchType=='T' ? "selected='selected'" :""} >제목</option>
<option value="W" ${search.searchType=='W' ? "selected='selected'" :""} >작성자</option>
<option value="C" ${search.searchType=='C' ? "selected='selected'" : ""} >내용</option>
</select>
</div>
<div class="col-sm-2">
<input type="text" name="searchWord" class="form-control input-sm" value="${search.searchWord}" placeholder="검색어">
</div>
<label for="id_searchCategory" class="col-sm-2 col-sm-offset-2 control-label">분류</label>
<div class="col-sm-2">
<select id="id_searchCategory" name="searchCategory" class="form-control input-sm">
<option value="">-- 전체 --</option>
<c:forEach items="${cateList}" var="code">
<option value="${code.commCd}"
${searchCategory eq code.commCd ? "selected='selected'" : ""} >${code.commNm}</option>
</c:forEach>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-2 col-sm-offset-9 text-right">
<button type="button" id="id_btn_reset" class="btn btn-sm btn-default">
<i class="fa fa-sync"></i> 초기화
</button>
</div>
<div class="col-sm-1 text-right">
<button type="submit" class="btn btn-sm btn-primary ">
<i class="fa fa-search"></i> 검 색
</button>
</div>
</div>
</form>
</div>
</div>
<!-- END : 검색 폼 -->
<!-- START : 목록건수 및 새글쓰기 버튼 -->
<div class="row" style="margin-bottom: 10px;">
<div class="col-sm-3 form-inline">
전체 ${paging.totalRowCount}건 조회
<select id="id_rowSizePerPage" name="rowSizePerPage" class="form-control input-sm">
<c:forEach var="i" begin="10" end="50" step="10">
<option value="${i}" ${paging.rowSizePerPage eq i ? "selected='selected'" : ""} >${i}</option>
</c:forEach>
</select>
</div>
</div>
<!-- END : 목록건수 및 새글쓰기 버튼 -->
결과화면
사용자가 여러가지 검색조건(분류, 검색어 등)을 선택할 수도 있고 안 할수도 있다.
즉, DB에서 쿼리를 쓸 때 검색조건이 있을지 없을지에 대해 동적 쿼리를 생성해야 한다.
mybatis는 다행히 그런 동적 쿼리를 제공한다.
mybatis에 동적쿼리에 대한 사항은 https://mybatis.org/mybatis-3/ko/dynamic-sql.html 를 참고.
이제 이 검색조건을 가지고 mybatis+dao,service,controller에서 적용해보자.
IFreeBoardDao
public int getTotalRowCount(@Param("paging") PagingVO paging
, @Param("search") SearchVO search, @Param("searchCategory") String searchCategory);
public List<FreeBoardVO> getBoardList(@Param("paging") PagingVO paging
, @Param("search") SearchVO search, @Param("searchCategory") String searchCategory);
파라미터가 여러개일 때는 @Param을 사용한다.
mybatis mapper
<sql id="searchInFree" >
<if test='!@org.apache.commons.lang3.StringUtils@isBlank(search.searchWord)'>
<if test='search.searchType=="T"'>
AND bo_title LIKE '%' || #{search.searchWord} || '%'
</if>
<if test='search.searchType=="W"'>
AND bo_writer LIKE '%' || #{search.searchWord} || '%'
</if>
<if test='search.searchType=="C"'>
AND bo_content LIKE '%' || #{search.searchWord} || '%'
</if>
</if>
<if test='!@org.apache.commons.lang3.StringUtils@isBlank(searchCategory)'>
AND bo_category = #{searchCategory}
</if>
</sql>
<select id="getTotalRowCount" resultType="int" >
SELECT count(*)
FROM free_board
WHERE 1=1
<include refid="searchInFree" />
</select>
<select id="getBoardList" resultType="com.study.free.vo.FreeBoardVO" >
SELECT *
FROM
(SELECT a.*,rownum AS rnum
FROM
(
SELECT
to_char(bo_reg_date,'YYYY-MM-DD') AS bo_reg_date
,to_char(bo_mod_date,'YYYY-MM-DD') AS bo_mod_date
, bo_no , bo_title , bo_category
, bo_writer , bo_pass , bo_content
, bo_hit , bo_del_yn
,b. comm_nm AS bo_category_nm
FROM free_board a ,comm_code b
WHERE a.bo_category = b.comm_cd
<include refid="searchInFree" />
ORDER BY bo_no DESC ) a ) b
WHERE rnum between #{paging.firstRow} and #{paging.lastRow}
</select>
IFreeBoardService
public List<FreeBoardVO>getBoardList(PagingVO paging , SearchVO search, String searchCategory);
FreeBoardServiceImpl
@Override
public List<FreeBoardVO> getBoardList(PagingVO paging, SearchVO search, String searchCategory) {
try (SqlSession session = sqlSessionFactory.openSession()) {
IFreeBoardDao freeBoardDao= session.getMapper(IFreeBoardDao.class);
int totalRowCount= freeBoardDao.getTotalRowCount(paging,search,searchCategory);
paging.setTotalRowCount(totalRowCount);
paging.pageSetting();
List<FreeBoardVO> freeBoardList = freeBoardDao.getBoardList(paging,search,searchCategory);
return freeBoardList;
}
}
FreeList
public class FreeList implements Handler {
@Override
public String process(HttpServletRequest req, HttpServletResponse response) throws Exception {
ICommCodeService codeService=new CommCodeServiceImpl();
List<CodeVO> cateList = codeService.getCodeListByParent("BC00");
req.setAttribute("cateList",cateList);
IFreeBoardService freeBoardService=new FreeBoardServiceImpl();
PagingVO paging=new PagingVO();
BeanUtils.populate(paging,req.getParameterMap());
SearchVO search=new SearchVO();
BeanUtils.populate(search,req.getParameterMap());
String searchCategory=req.getParameter("searchCategory");
req.setAttribute("paging", paging);
req.setAttribute("search", search);
req.setAttribute("searchCategory", searchCategory);
List<FreeBoardVO> freeBoardList
= freeBoardService
.getBoardList(paging,search,searchCategory);
req.setAttribute("freeBoardList",freeBoardList);
return "free/freeList";
}
}
여기까지 하고 나면 검색 폼 버튼을 입력하고 나서 검색 된 내용만 잘 가져올 것이다.
그런데 아직 페이징 nav( 1~10버튼)은 curPage만 파라미터로 가진 <a>태그가 실행된다.
그래서 <a>태그 기본이벤트를 막고, 검색데이터와
paging데이터가 함께 넘어가도록 jquery를 작성한다.
freeList.jsp의 script태그 추가.
<script type="text/javascript">
// 변수 정의
$form=$("form[name='search']");
$curPage=$form.find("input[name='curPage']");
// 각 이벤트 등록
// 페이지 링크 클릭, event전파방지, data속성값읽고 form태그 내의 input name=curPage값 바꾸기
//submit
$('ul.pagination li a[data-page]').click(function(e) {
e.preventDefault();
let curPage= $(this).data('page');
$curPage.val( curPage );
$form.submit();
}); // ul.pagination li a[data-page]
//member까지 다 해보세요....3시간 동안..
//form태그내의 버튼 클릭
//이벤트전파방지, curPage 값 1로
//submit
$form.find("button[type=submit]").click(function(e) {
e.preventDefault();
$curPage.val(1);
$form.submit();
});
// 목록 갯수 변경
// id_rowSizePerPage 변경되었을 때
// 페이지 1, 목록수 = 현재값 으로 변경 후 서브밋
$('#id_rowSizePerPage').change(function(e) {
$curPage.val(1);
$form.find("input[name='rowSizePerPage']").val($(this).val());
$form.submit();
}); // '#id_rowSizePerPage'.change
// 초기화 버튼 클릭
$('#id_btn_reset').click(function() {
$("#id_searchType option:eq(0)").prop("selected", "selected");
$form.find("input[name='searchWord']").val("");
$("#id_searchCategory option:eq(0)").prop("selected", "selected");
}); // #id_btn_reset.click
</script>
이제 검색도 잘 되고, 버튼 눌렀을 때 검색한 내용이 유지되면서 다음페이지로 넘어갈 것이다.