Pagination 기능

Intro

  • Pagination이란 게시판과 같은 웹페이지에서 클라이언트가 게시글정보를 요청할때 데이터베이스상에 존재하는 대량의 게시글정보를 여러 Page로 구분하여 클라이언트가 요청한 특정 Page에 해당하는 정보만 제공하는 방식이다.

게시글과 페이징바
  • 위 이미지에서 각 썸네일은 1페이지의 "게시글정보"들이고, 하단 1~10페이지까지 이동할수 있는 버튼들을 "페이징바"라고 부른다.

  • 즉 페이징 작업에는 요청한 페이지에대한 게시글 정보들과, 요청한 페이지에서 보여줄 페이징바에 대한 정보를 함께 클라이언트에게 응답해줘야한다.

페이징 처리 과정

  • 요청한 페이지에 맞는 게시글과 페이징 바를 만들기 위해선 페이지 정보를 가지고 있는 페이징 객체를 우선적으로 만들어 줘야 한다.

1) 페이징객체 만들기

//페이징 객체 멤버변수들
@Data
public class PageInfo {
	private int listCount; // 게시글의 총 갯수
	private int currentPage; // 클라이언트가 요청한 페이지
	private int pageLimit; // 한 페이징바당 보여줄 페이지넘버의 갯수
	private int boardLimit; // 한 페이지당 보여줄게시글의 갯수
	
	private int maxPage; // 최대 페이지
	private int startPage; // 페이징바의 시작 페이지넘버
	private int endPage; // 페이징바의 끝 페이지 넘버
}
  • Pagination객체를 만들기 위해서는 계산을 통해 구해야 할 값과, 미리 준비해둬야 할 변수들이 정해져 있다.

미리 값을 준비해야 하는 변수들

  1. listCount : 게시글의 총 갯수로 DB에서 SELECT문을 통해 조회해온다.

  2. currentPage : 클라이언트가 요청한 페이지로 요청 parameter로부터 얻어온다

  3. pageLimit : 하나의 페이징바에 보여줄 페이지넘버의 갯수로 내부 규칙에 의해 고정된 값이 정해진다

  4. boardLimit : 한 페이지에 보여줄 게시글의 갯수로 값을 고정해두는 경우도 있고, 10개씩보기 20개씩 보기등 보여줄 갯수를 클라이언트에게 추가로 위임하는 경우도 있다.

계산을 통해 값을 구해야하는 변수

  1. maxPage : 최대 페이지넘버로 총 개시글 갯수와 게시글당 보여줄 게시글의 갯수에 영향을 받는다 int maxPage =(int)(Math.ceil(((double)listCount/boardLimit)));

  2. startPage : 페이징바의 시작숫자로 현재 요청페이지와 페이징바당 보여줄 페이지넘버의 갯수에 영향을 받는다. int startPage = (currentPage-1) / pageLimit * pageLimit+1;

  3. endPage : 페이징바의 끝수로 startPage와 pageLimit에 영향을 받으며, maxPage에도 영향을 받는다 int endPage = startPage+pageLimit-1;

    if(endPage>maxPage) {
        endPage=maxPage;
    }

2) 페이지객체를 통한 게시글 조회

  • Controller에서는 PageInfo 객체를 생성한후 Service에게 전달하고, Service는 다시한번 Dao에게 전달한다.

  • Dao는 전달받은 PageInfo객체를 통해 요청페이지에 맞는 게시글 정보를 조회한다.

방법1) ROWNUM을 활용한 데이터 조회

  • ORACLE의 ROWNUM은 조회된 순서대로 겹치지 않는 고유한 번호를 매겨주는 가상칼럼으로 INLINE VIEW와 결합하여 올바른 데이터를 조회한다

  • EX) 클라이언트가 요청한 페이지가 1페이지이고, 한페이지당 보여줘야할 게시글의 갯수가 10개인경우? 조회할 데이터는 1번행부터 10개 즉, 1~10번행까지의 데이터를 조회하면 될것이다. 마찬가지로2페이지라면 RNUM은 11 ~ 20까지의 데이터를 조회할수 있도록 꾸미면 된다

SELECT * 
FROM (
       SELECT B.* , ROWNUM RNUM
       FROM 
       (
               SELECT * FROM BOARD
       ) B
      ) C
WHERE RNUM >= 1 AND RNUM <=10;

// 위 코드를 마이바티스 구문으로 바꾸면 아래와 같다.
WHERE RNUM >= 1 AND RNUM <=10 ==> WHERE RNUM >= #{startRow} AND RNUM <=  #{endRow}

// 각 변수에 들어갈 값값
startRow = (currentPage -1) * boardLimit +1
endRow = startRow + boardLimit - 1; 
  • 특징 : 조회할 칼럼이 많아지거나, join문이 많이 들어가면 가독성이 굉장히 나빠질수 있으며 유지보수성또한 떨어질 수 있다. 만약 방법2번과 쿼리수행비용(cost)이 비슷하게 산출이 된다면 가독성이 확보되는 방법2번을 사용하는게 좋을 것 이다.

방법2) RowBounds클래스를 활용한 데이터 조회

  • RowBounds는 위 ROWNUM과는 다른방식으로 응답 페이지에 들어갈 데이터를 조회한다.

  • ROWNUM방식은 데이터를 조회할때 내가 요청한 페이지의 게시글정보만큼만 조회해 왔다면 , RowBounds는 우선 모든 게시글정보를 조회한 후 조회된 행의 offset 위치에서부터 limit만큼 반복을 진행하여 데이터를 조회해온다.

int offset = (pi.getCurrentPage() -1 ) * pi.getBoardLimit(); // 데이터를 조회할 시작행
int limit = pi.getBoardLimit(); // 조회할 갯수
		
RowBounds rowBounds = new RowBounds(offset, limit); // 매개변수로 초기화

return sqlSession.selectList("boardMapper.selectList" , paramMap ,rowBounds);

// sql문쪽 코드
SELECT * FROM BOARD

  • 특징 : 복잡한 조건식이나 중첩 인라인 뷰를 사용할 필요가 없어 읽기에 좋고 유지보수성도 올라간다. 단, DB의 모든 데이터를 조회한후 JAVA-SERVER로 전달하여 데이터 필터링 작업을 수행하게 되는데 이떄 너무 많은 데이터가 JAVA서버로 전달된다면 많은 리소스가 이를 위해 소비될 것이다.

  • 결론 : 데이터를 조회할때 자원의 소비와 가독성 및 유지보수성의 비교를 수행하여 편한 방법으로 코드를 짜면 된다.

3) 페이지객체를 통한 페이징바 구현

  • 서버에서 계산한 PageInfo객체를 클라이언트의 requestSopce에 담아서 전달해주면 클라이언트는 전달받은 페이징 객체를 통해 페이징바를 만들면 된다.

// 페이징바 구현 소스코드
<div id="pagingArea">
<ul class="pagination">
	<c:if test="${pi.currentPage eq 1 }">
		<li class="page-item">
        		<a class="page-link">Previous</a>
        	</li>
	</c:if>
	<c:if test="${pi.currentPage ne 1 }">
       		<li class="page-item">
        		<a class="page-link" href="${url}${pi.currentPage -1}${sUrl}">Previous</a>
        	</li>        		
	</c:if>
	<c:forEach var="i" begin="${pi.startPage }" end="${pi.endPage }">
        	<li class="page-item">
        		<a class="page-link" href="${url}${i}${sUrl}">${i}</a>
        	</li>	
	</c:forEach>      
			  		
	<c:if test="${pi.currentPage eq pi.maxPage }">
		<li class="page-item">
        		<a class="page-link">Next</a>
        	</li>
	</c:if>
	<c:if test="${pi.currentPage ne pi.maxPage }">
       		<li class="page-item">
        		<a class="page-link" href="${url}${pi.currentPage +1}${sUrl}">Next</a>
        	</li>        		
	</c:if>
</ul>
</div>

Last updated