9/11/2019

스프링 게시판 만들기 #10. 페이징 구현 2

한 페이지당 게시물을 10개씩 보여줄 수 있도록 설정 했는데, 게시물 전체 갯수가 너무 많아서 하단에 위치한 페이지 번호가 어마어마하게 많아졌습니다.

게시물을 한 페이지에 10개씩 보여주듯, 페이지 번호도 한번에 몇개만 출력되도록 설정할 필요가 있습니다.

페이징 번호를 출력하는 방법중 보편적인건 2가지가 있는데

첫번째로는 1~10, 11~20, 21~30처럼 특정 갯수만큼 출력하는 방법

두번째로 현재 페이지 번호의 앞뒤로 특정 갯수씩 출력하는 방법이 있습니다.

저는 이 두가지 방법중 첫번째 방법을 먼저 해보겠고, 나중에 두번째 방법도 해보겠습니다.

가장 먼저 해야할건, 한번에 표시할 페이지 번호의 갯수입니다.
이 번호의 갯수를 알면 현재 페이지의 마지막 번호를 알 수 있으며, 현재 페이지의 마지막 번호를 알 수 있다면 현재 페이지의 시작 번호도 알 수 있습니다.

// 한번에 표시할 페이징 번호의 갯수
int pageNum_cnt = 10;

// 표시되는 페이지 번호 중 마지막 번호
int endPageNum = (int)(Math.ceil((double)num / (double)pageNum_cnt) * pageNum_cnt);

// 표시되는 페이지 번호 중 첫번째 번호
int startPageNum = endPageNum - (pageNum_cnt - 1);

한번에 표시할 페이지 번호의 갯수(pageNum_cnt)는 10개로 했습니다.

현재 페이지 번호가 8번이라면, 한번에 표시할 페이지 번호의 갯수인 10으로 나눕니다. 8 / 10 = 0.8
소수점을 올림처리(ceil)하면 0.8은 1이 됩니다.
1을 한번에 표시할 페이지 번호의 갯수인 10을 곱하면 10이 됩니다.

현재 페이지 번호가 41이라면, 한번에 표시할 페이지 번호의 갯수인 10으로 나눕니다. 41 / 10 = 4.1
소수점을 올림처리하면 4.1은 5가 됩니다
5를 한번에 표시할 페이지 번호의 갯수인 10을 곱하면 50이 됩니다.

즉, 마지막 페이지 번호를 구하는 공식은 다음과 같습니다.

마지막 페이지 번호 = ((올림)(현재 페이지 번호 / 한번에 표시할 페이지 번호의 갯수)) * 한번에 표시할 페이지 번호의 갯수

마지막 페이지 번호에서 한번에 표시할 번호의 갯수를 빼면,
마지막 페이지 번호가 10일 경우 0
마지막 페이지 번호가 50일 경우 40
여기에 1을 더하면 각 페이지의 시작 번호가 됩니다.

그러므로 시작 페이지를 구하는 공식은 다음과 같습니다.

시작 페이지 = 마지막 페이지 번호 - 한번에 표시할 페이지 번호의 갯수 + 1

// 마지막 번호 재계산
int endPageNum_tmp = (int)(Math.ceil((double)count / (double)pageNum_cnt));
 
if(endPageNum > endPageNum_tmp) {
 endPageNum = endPageNum_tmp;
}

마지막 페이지 번호는 다시 한번 더 계산할 필요가 있습니다.

마지막 페이지 번호를 구하는 공식은 위에서 구한대로
[ 마지막 페이지 번호 = ((올림)(현재 페이지 번호 / 한번에 표시할 페이지 번호의 갯수)) * 한번에 표시할 페이지 번호의 갯수 ]입니다.

만약 게시물 총 갯수가 112개라면, 하단에 표시될 페이지 번호는 1 ~ 10, 11 ~ 12가 되어야합니다.
여기서 한번에 표시할 페이지 번호의 갯수가 10이고, 현재 페이지가 11일 경우

# 1차 계산
[ ((올림)(11 / 10)) * 10 => (올림)1.1 * 10 => 2 * 10 = 20 ] 이 됩니다.
즉, 13 ~ 20까지 없어야할 페이지 번호가 출력됩니다.

# 2차 계산
여기에서 게시물 총 갯수와 한번에 표시될 페이지 번호의 갯수를 이용해 재계산합니다.
[ (올림)112 / 10 => (올림)11.2 => 12 ] 가 됩니다.

1차 계산한 마지막 페이지 번호는 20이며, 2차로 계산한 마지막 페이지 번호는 12입니다.
이 둘을 비교해서 만약 1차 계산이 더 크다면, 2차로 계산한 값을 넣어줍니다.

다른 예시로, 게시물 총 갯수가 500개이며, 현재 페이지가 5인 경우

# 1차 계산
[ ((올림)(5 / 10)) * 10 => (올림)0.5 * 10 => 1 * 10 = 10 ]

# 2차 계산
[ (올림)500 / 10 => (올림)50 => 50 ]

1차 계산의 결과가 2차 계산의 결과보다 작으므로 조건문은 false가 되어 실행되지 않으므로, 1차 계산에서 나온 결과인 10이 그대로 나오게 됩니다.

다음은 페이지 번호의 간격을 넘어가는 이전과 다음 링크의 표시입니다.
추가적으로 위에서 구한 시작과 끝번호, 이전과 다음 링크 표시를 뷰(view)에 출력하기 위해 모델(model)에 넣어줍니다.

boolean prev = startPageNum == 1 ? false : true;
boolean next = endPageNum * pageNum_cnt >= count ? false : true;
// 시작 및 끝 번호
model.addAttribute("startPageNum", startPageNum);
model.addAttribute("endPageNum", endPageNum);

// 이전 및 다음 
model.addAttribute("prev", prev);
model.addAttribute("next", next);

이전 링크는, 시작 페이지 번호가 1일 때를 제외하곤 무조건 출력되어야합니다.
다음 링크는, 마지막 페이지 번호가 총 게시물 갯수보다 작다면, 다음 구간이 있다는 의미이므로 출력되어야합니다.

<c:if test="${prev}">
 <span>[ <a href="/board/listPage?num=${startPageNum - 1}">이전</a> ]</span>
</c:if>

<c:forEach begin="${startPageNum}" end="${endPageNum}" var="num">
  <span>
   <a href="/board/listPage?num=${num}">${num}</a> 
 </span>
</c:forEach>

<c:if test="${next}">
 <span>[ <a href="/board/listPage?num=${endPageNum + 1}">다음</a> ]</span>
</c:if>

기존에 사용했던 무식한 페이지 번호 출력부는 주석처리나 삭제하고, 새로운 코드를 추가합니다.

이전과 다음 링크가 조건문으로 들어가 있으며
페이지 번호를 출력하는 반복문은 페이지 시작번호부터 페이지 마지막 번호까지만 출력되도록 했습니다.

프로젝트를 실행하고 접속해보면 의도한대로 페이지 번호는 10개씩 출력되며, 현재 시작 페이지 번호가 1이므로 이전 링크는 표시가 되지 않고 있습니다.

다음 링크를 눌러보면, 시작 페이지 번호가 1이 아니므로 이전 링크가 출력된걸 확인할 수 있습니다.

그건 그렇고 지금 현재 페이지가 정확히 어디인지 확실하게 눈에 들어오지 않고 있습니다.

// 현재 페이지
model.addAttribute("select", num);

현재 페이지를 그대로 모델(model)에 select란 이름으로 보냈습니다.

<c:forEach begin="${startPageNum}" end="${endPageNum}" var="num">
 <span>
 
  <c:if test="${select != num}">
   <a href="/board/listPage?num=${num}">${num}</a>
  </c:if>    
  
  <c:if test="${select == num}">
   <b>${num}</b>
  </c:if>
    
 </span>
</c:forEach>

반복문 중간에 조건을 넣어서, select의 값이 num과 다를 경우 링크를 그대로 출력하고
select의 값이 num과 같을 경우 링크가 아닌 굵은 글자로 출력하게 했습니다.

프로젝트를 재실행하여 접속해보면, 현재 페이지를 확실하게 구분할 수 있게 되었습니다.

게시물 수정
  1. 익명3/24/2020

    완료!! 감사합니다~~~

    답글삭제
  2. 우와! 이렇게 유익할수가! 대단한 자료 정말 감사히 보고 갑니다!

    답글삭제
  3. 안녕하세요 ? 포스팅 보면서 도움 많이 받고 있습니다 :)

    다름이 아니라 선택한 페이지 번호를 굵은 글씨로 표시하려고
    태그를 넣으면 갑자기 페이징 번호에 없던 0이 생깁니다..
    심지어 0을 클릭하면 오류가 나요 ㅠㅡㅠ 왜 그러는 걸까요??

    답글삭제
    답글
    1. 아이고 해결됐습니다. 감사합니다!

      삭제
  4. 페이징이 제일 어렵다...

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      복잡한듯 하면서도, 나중에 정리해서보면 '생각보다' 어렵진 않습니다(안 어려운건 아닙니다-_ㅠ)

      삭제
  5. 그러게요 페이징... 집중안해서 보면 이해가 어렵네요 ㅎㅎ

    답글삭제
  6. 어려워도 한번 이해 가니까 공부도 많이되고 좋네요

    답글삭제
  7. 안녕하세요 kuzuro님! 올려주신 글 덕분에 스프링 게시판 만들기를 정말 수월하게 이해하고 있습니다. 먼저 너무 감사합니다!
    다음 글의 dataCalc에 해당하는 부분에서 오류를 발견해서 댓글 남깁니다.
    kuzuro님의 코드에서는 pageNum_cnt와 postNum을 같은 값으로 지정하고 진행하고 있어서 치명적이지 않은 문제지만 저는 두 값을 다르게 지정해둬서 우연히 발견할 수 있었어요.
    이하 페이지에 조금 더 구체적으로 설명해 두었습니다. https://dev-0su.tistory.com/11
    좋은 하루 되세요! 지금까지 알려주신 걸 토대로 이런저런 기능을 구현해볼 생각이지만 혼자 하려니 막막해서 다음 글이 자꾸 기다려 지네요 ㅎㅎ

    답글삭제
  8. 한페이지에 출력할 게시물 수를 수정하면 안되네요..
    이렇게 수정해야 하지 않을까요? ^^;;

    1. pageNum_cnt ->postNum
    int endPageNum_tmp = (int)(Math.ceil((double)count / (double)postNum));

    2. pageNum_cnt ->postNum
    boolean next = endPageNum * postNum >= count ? false : true;

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      엇.. 제가 이후 게시물 작성할때 확인 후 수정하겠습니다. 감사합니다.

      삭제
    2. 아! 해결했습니다!

      삭제
  9. 익명2/19/2022

    페이징처리.... 토할거 같아요....

    답글삭제
  10. 익명8/22/2023

    선생님 감사합니다~~~~~~~~~~~~ 즐거운 날 빛나는 날 가득하세요~~~~~~~~~~

    답글삭제
  11. 컨트롤러 listPage에서 num 값이 선언되어잇지않아 오류가나는데 어떻게 구해야하나요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      해당 컨트롤러의 메서드(listPage)는
      public void getListPage(Model model, @RequestParam("num") int num) ... 으로 작성되어있습니다
      num이 선언 문제로 오류가 발생한 경우 매개변수로에 작성해주시고, 그에 맞게 요청하는 url도 num을 보내주고 있는지 보셔야합니다

      삭제