본문으로 바로가기

게시글의 페이징 처리

category 스프링 2020. 6. 26. 23:46

게시글이 1000개가 있을 때 한페이지 내에 전부 출력 된다면 굉장히 비효율적일 것입니다. 그래서 서버단에서 항상 페이징 처리를 해주어야 합니다.
필자는 프로젝트 때 만들어 놓은 CRUD 게시판을 이용하여 작업을 했기 때문에 본인의 게시판에 맞게 적절히 맟춰주면 되겠습니다.

1. 페이징의 이해

페이징이 어떻게 처리 되는지 이해해야 합니다. 이 글에선 한 페이지당 10개의 글을 보여주고 버튼은 최대 10개로 예시를 들겠습니다.

  • 총 게시글 수 91개인 경우
    [1][2][3][4][5][6][7][8][9]
    총 게시글이 91개 일 때 마지막 페이지는 9페이지가 되어야 합니다.
    이때 페이지의 끝 번호를 알기 위해서 총 게시글의 개수가 필요 합니다.
  • 총 게시글 수 223개
    [1][2][3][4][5][6][7][8][9][10][다음]
    [이전][11][12][13][14][15][16][17][18][19][20][다음]
    [이전][21][22]
    페이지 수가 100개를 넘어가면 다음 버튼이 있어야 하고 첫 페이지가 1이 아니라면
    이전 버튼이 있어야 합니다. 또한 현재 페이지가 13번 이면 페이지 버튼은 11~20 이여 하겠죠?

2. 페이징 규칙

  • 페이징 처리는 반드시 GET 방식만을 이용해야 한다.
  • 게시글 목록 페이지의 하단에 페이지들의 번호를 보여주고 원하는 번호를 선택하면 해당 페이지로 이동해서 목록을 보여줘야 한다.
  • 페이징은 반드시 필요한 페이지 번호만 출력해야 한다. 만약 페이지당 10개의 게시글을 출력하는데 전체 게시글의 수가 42개라면 페이지의 번호는 5페이지까지여야 한다.
  • 이전과 다음 버튼이 존재해야 한다. 게시글이 1000개인데 1페이지당 10개의 게시글을 볼 수 있게 했다면 페이징 버튼은 100개가 필요하다. 그런데 이전과 다음 버튼이 존재하지 않는다면 100개의 버튼을 한번에 보여줘야 한다. 그렇기때문에 하단 페이지에 보여줄 버튼의 갯수를 정하고 더 많은 데이터가 있다면 이전과 다음 버튼으로 표시해줘야 한다.
  • 게시글을 조회하거나 수정, 삭제를 하고 난 뒤, 다시 원래의 목록 페이지로 이동해야 한다. 예를 들면 게시판에 5페이지에 있는 어떠한 게시물을 조회하거나 수정, 삭제를 했다고 한다면, '목록으로'버튼을 이용해 목록으로 돌아갈 땐 5페이지로 이동해야 한다.

3. SQL (Oracle)

페이징 처리를 위한 데이터를 DB에 넣어 두었습니다. 오라클에선 페이징 처리를 위한 쿼리를 짤 땐 ROWNUM을 이용합니다.

게시판에 뿌려질 데이터를 충분히 많이 넣어두고 SELECT 문으로 가져 옵니다.

SELECT  BNO, 
                TITLE, 
                CONTENT,
                WRITER, 
                REGDATE,
                HIT    
         FROM ( 
                SELECT BNO, 
                       TITLE, 
                       CONTENT, 
                       WRITER, 
                       REGDATE,
                       HIT, 
                       ROW_NUMBER() OVER(ORDER BY BNO DESC) AS RNUM
                 FROM MP_BOARD 
                               ) 
        WHERE RNUM BETWEEN 시작열 AND 마지막열
        ORDER BY BNO DESC

위와 같이 인라인뷰 쿼리와 ROWNUM을 이용하여 시작열과 마지막열을 정해서 사용자게 뿌려줄 특정 게시글만 가져올 수 있습니다.

  • 1페이지 -> WHERE RNUM BETWEEN 1 AND 10
  • 2페이지 -> WHERE RNUM BETWEEN 11 AND 20
  • 3페이지 -> WHERE RNUM BETWEEN 21 AND 30

4. 페이징 처리 클래스

SQL query의 시작열과 마지막열의 데이터를 Criteria라는 클래스가 전달해줍니다.
public class Criteria {
    private int page; // 페이지 번호
    private int perPageNum; // 페이지당 게시글 갯수
    private int rowStart; // 페이지 한 행의 첫번째 게시물 rowNum
    private int rowEnd; // 페이지 한 행의 마지막 게시물 rowNum

    public Criteria() { //디폴트 생성자 
        this.page = 1; // 페이지 1 로 초기화
        this.perPageNum = 10; // 페이지당 게시글 10개
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) { // 페이지 번호 set
        if (page <= 0) {
            this.page = 1;
        }
        this.page = page;
    }

    public int getPerPageNum() {
        return perPageNum;
    }

    public void setPerPageNum(int perPageNum) { 
    // 페이지당 게시물 갯수 set
        if (perPageNum <= 0 || perPageNum > 100)
            this.perPageNum = 10;
        this.perPageNum = perPageNum;
    }

    public int getPageStart() {
        // 현재 페이지의 페이지당 게시글 수를 곱하여
        // 현재 페이지의 시작 게시글 ROWNUM수를 구하는것
        return (this.page - 1) * perPageNum;
    }

    public int getRowStart() {
        rowStart = ((page - 1) * perPageNum) + 1;
        return rowStart;
    }

    public int getRowEnd() {
        rowEnd = rowStart + perPageNum - 1;
        return rowEnd;
    }

    @Override
    public String toString() {
        return "Criteria [page=" + page + ", perPageNum=" + perPageNum + ", rowStart=" + rowStart + ", rowEnd=" + rowEnd
                + "]";
    }

}

게시글 조회 쿼리에 전달해줄 데이터를 담게 될 클래스라고 생각하시면 됩니다.

  • int page : 현재 페이지 번호
  • int perPageNum: 한 페이지 당 보여줄 게시글 수
  • int getPageStart(): 특정 페이지의 시작 게시글 번호
이번엔 화면에서 버튼 생성을 도와줄 계산 클래스입니다. 수학적 계산이 필요한 부분입니다.
public class PageMaker {

    private int totalCount; // 게시글 총 갯수
    private int startPage; // 10개의 페이지중 첫번째
    private int endPage; // 10개의 페이지중 마지막
    private boolean prev; // 페이지 이전 버튼
    private boolean next; // 페이지 다음 버튼
    private int displayPageNum = 10;
    private Criteria cri; // 페이지 정보 객체

    public void setCri(Criteria cri) {
        this.cri = cri;
    }

    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
        calcData();
    }

    public int getTotalCount() {
        return totalCount;
    }

    public int getStartPage() {
        return startPage;
    }

    public int getEndPage() {
        return endPage;
    }

    public boolean isPrev() {
        return prev;
    }

    public boolean isNext() {
        return next;
    }

    public int getDisplayPageNum() {
        return displayPageNum;
    }

    public Criteria getCri() {
        return cri;
    }

    private void calcData() { // 페이지 데이터 처리
        // 1~10 페이지는 endPage가 10으로 고정되고 11~20 페이지는 endPage가 20으로 고정되는 방식
        endPage = (int) (Math.ceil(cri.getPage() / (double)displayPageNum) * displayPageNum);
        // startPage는 매 첫번째 페이지
        startPage = (endPage - displayPageNum) + 1;

        int tempEndPage = (int) (Math.ceil(totalCount / (double)cri.getPerPageNum()));
        if (endPage > tempEndPage) {
            endPage = tempEndPage; 
            // 마지막 게시물이 있는 페이지가 endPage로 다시 할당해준다.
        }
        prev = startPage == 1 ? false : true; 
        // 첫번째 페이지가 1이면 false를 반환하여 이전버튼이 사라지게 한다.
        next = endPage * cri.getPerPageNum() >= totalCount ? false : true;            // 마지막페이지의 게시글이 10개 이하면 false를 반환.
    }



    public String makeQuery(int page) {  
    // 원하는 페이지로 페이지 쿼리문을 날려준다.
        UriComponents uriComponents =
                UriComponentsBuilder.newInstance()
                .queryParam("page", page) // page번호를 파라미터값으로 날려준다.
//                .queryParam("perPageNum", cri.getPerPageNum()) // page당 게시글 갯수를 파라미터값으로 날려준다.
                .build(); 
        return uriComponents.toUriString();  
    }
}
  • Criteria cri: 위에서 만든 page와 perPageNum을 가지고 있는 객체
  • int totalCount: 전체 게시글 수
  • int startPage: 시작 페이지, 현재 페이지가 12쪽이면 11쪽이 startPage 현재 페이지가 28쪽이면 21쪽이 startPage가 됩니다.
  • int endPage: 마지막 페이지
  • boolean prev: [이전] 버튼의 생성 조건이 되는 필드
  • boolean next: [다음] 버튼의 생성 조건이 되는 필드
  • makeQuery() : 이 메소드는 스프링에서 지원 해주는 객체를 가지고 있습니다 UriComponent라는 객체입니다. 이 놈은<a href="/list?page=2&perPageNum=10"> 을 작성할 때 파라미터를 동적으로 넘겨줄 수 있는 객체입니다.

PageMaker 클래스를 사용할 땐 totalCount(전체 게시글 수)와 Criteria가 먼저 셋팅 되어 있어야 합니다.

5. 스프링 컨트롤러

    // 게시물 목록 컨트롤러
    @RequestMapping(value ="/list", method = RequestMethod.GET)
    public String listView(Model model, Criteria cri) throws Exception{
        logger.info("list");
        List<BoardVO> vo = service.list(scri);
        model.addAttribute("list", vo);
        model.addAttribute("cri", cri);
        pageMaker.setCri(cri);
        pageMaker.setTotalCount(service.listCount(scri));
        model.addAttribute("pageMaker", pageMaker);

        return "board/list";
    }
  • 여기서 pageMaker 객체에 먼저 cri 객체를 셋팅 해주는것을 볼 수 있습니다. 총 게시글 수와 현재 페이지, 페이지 당 게시글 수의 값을 가져오는 겁니다.

6. 게시글 목록을 보여줄 화면 작업

<!-- 페이징 -->
<div>
    <ul>
        <c:if test="${pageMaker.prev}">
            <li>
                  <a href="list${pageMaker.makeQuery(pageMaker.startPage - 1)}">이전</a>
            </li>
        </c:if>
        <c:forEach begin="${pageMaker.startPage}" end="${pageMaker.endPage}" var="idx">
            <li>
                <a href="list${pageMaker.makeQuery(idx)}">${idx}</a>
            </li>
        </c:forEach>
        <c:if test="${pageMaker.next && pageMaker.endPage > 0}">
            <li><a href="list${pageMaker.makeQuery(pageMaker.endPage + 1)}">다음</a></li>
        </c:if>
    </ul>
</div>

== [이전] 버튼 ==

  • 이전 버튼이 클릭가능한 조건이면, a태그를 이용해 이전 버튼이 뜨도록 하고, href로 링크를 걸되
    아까 만든 makeQuery 메서드를 이용해서 쿼리문자열을 만들어 줍니다.
    ?page=3&perPageNum=10 이 부분을 생성해서 넣게 되는데 단 이전 버튼을 클릭하면 현재 페이지가 시작페이지의 -1 이 되도록 되어야 함으로 그 부분만 신경써 주면 됩니다.

== [1],[2],[3]...[10] 버튼==

  • jstl 이용해for문을 돌면서 startPage ~ endPage까지 표시해주고, idx에 현재 페이지를 지정하여 a링크를 통해 원하는 페이지로 이동한다.

==[다음] 버튼==

  • 이전 버튼과 마찬가지로 +1를 해주어 현재 페이지가 끝 페이지보다 + 1해주어 이동한다.

사용자 화면

'스프링' 카테고리의 다른 글

검색 처리와 동적 SQL  (2) 2020.07.19
스프링의 AOP 란?  (0) 2020.06.23
스프링의 @ModelAttribute 어노테이션  (0) 2020.06.15
AJAX란?  (0) 2020.06.15
인터셉터(Interceptor)란?  (0) 2020.06.13