프로젝트/기능 정리

[Java] SpringBoot 프로젝트 : 리스트 페이징 처리(5) - PagingUtil 클래스

민뇌 2023. 6. 21. 15:37

목적

  • 게시판 형식 웹사이트 개발 시 DB에서 글 리스트 불러오기 기능 구현
  • 리스트 게시판 페이징 처리 받은 코드 리뷰

 

이제 마지막으로 PagingUtil 클래스 내부 코드를 보도록 하자.

 

이전 글에서 페이징 처리에 필요한 값들을 구하는 식을 봤으니, 이제 코드를 보면 쉽게 이해가 갈 것이다.

 

[데이터 흐름도]

데이터 흐름도는 다음과 같다.

 

게시판 리스트 데이터 흐름도

 

 

[PagingUtil 클래스]

우선 클래스 내부 코드를 보기 전 어떤 인자를 넘겨줬었는지 다시 보자.

 

아래는 컨트롤러에서 PagingUtil 객체를 생성하는 부분이다.

PagingUtil page = new PagingUtil(keyfield, null, currentPage, count, 5, 5, "pointList.do");

각 인자들이 무엇인지 앞에서 설명했지만, 다시 간단히 말하면

 

  1. keyfield: 리스트 정렬 옵션 값
  2. keyword: 검색 기능 구현 시 사용자가 입력한 검색 키워드
  3. currentPage: 현재 사용자가 요청한 페이지
  4. count: DB에서 가져온 총 게시물 수
  5. rowCount: 한 페이지에서 보여줄 게시물 수
  6. pageCount: 한 페이지에서 보여줄 페이지 번호 수
  7. url: 페이지 번호에 넣어줄 호출 페이지URL

 

이 되겠다.

 

이제 클래스 내부를 보자.

public class PagingUtil {
    private int startRow; //한 페이지에서 보여줄 글 시작 번호(start)
    private int endRow; //한 페이지에서 보여줄 글 끝 번호(end)
    private StringBuffer page; //페이지 표시 문자열

    public PagingUtil(String keyfield, String keyword, int currentPage, int count, int rowCount, int pageCount,String pageUrl) {

    //페이징 처리 코드
    
    }
    public int getStartRow() {
        return startRow;
    }
    public int getEndRow() {
        return endRow;
    }
    public StringBuffer getPage() {
        return page;
    }
}

PagingUtil 객체가 리턴할 수 있는 값은 총 세 개이다.

 

차례로 보면, 글 시작 번호, 글 끝 번호, 페이지 문자열이다.

 

시작 번호와 끝 번호가 무엇인지는 이전 글에서 설명했다.

 

페이지 문자열은 HTML태그를 직접 작성한 뒤 버퍼에 담아 리턴하는 것으로, 하단에 페이지 버튼을 생성하는 HTML태그 모음이라고 생각하면 된다.

 

이제 어제 설명한 순서로 필요한 값들을 구해보자.

 

[전체 페이지 수]

전체 페이지 수를 구하는 수식은 아래와 같았다.

 

( (총 게시물 수 - 1) / 한 페이지에서 보여주고 싶은 글 개수 ) + 1

 

코드로 표현하면, 아래와 같다.

int totalPage = ((count - 1) / rowCount) + 1;

여기서 한 가지 조건을 추가해 줘야한다.

 

현재 페이지의 경우 GET방식으로 URL을 통해 전달하게 되는데 예를 들면 다음과 같다.

 

currentPage, keyfield 전달 방식

 

따라서 못된(?) 이용자가 임의로 pageNum(= currentPage) 값을 전체 페이지 수보다 크게 입력할 수 있는데, 이를 방지하기 위해 다음 조건문을 넣어주면 좋다.

if (currentPage > totalPage) {
    currentPage = totalPage;
}

물론 반대로 가장 작은 페이지 번호는 1일테니 1보다 작은 음수값이 들어왔을 때에도 currentPage 값을 1로 고정해주는 방식으로 오류를 막을 수 있을 것이다.

 

[한 페이지의 글 번호 - start, end]

이제 쿼리문에 사용하기 위해 필요했던 한 페이지에 보여줄 게시물의 시작 번호와 끝 번호를 구해보자.

 

마지막 글 번호를 구하는 식은 다음과 같았다.

 

현재 페이지 번호 × 한 페이지에서 보여주고 싶은 글 개수

 

코드로 표현하면, 아래와 같다.

endRow = currentPage * rowCount;

다음으로 시작 글 번호를 구해보자.

 

식은 아래와 같다.

 

(현재 페이지 번호 - 1) × 한 페이지에서 보여주고 싶은 글 개수 + 1

 

이 식을 전개해보면, (끝 번호 - 한 페이지에서 보여주고 싶은 글 개수 + 1)이 된다.

 

코드로 표현하면, 아래와 같다.

startRow = (currentPage - 1) * rowCount + 1;

 

[페이지 번호]

마지막으로 페이지 번호의 시작값과 끝값을 구해보자.

 

시작 번호를 구하는 식은 아래와 같았다.

 

( ( 현재 페이지 번호 - 1 ) / 페이지 번호 개수 ) × 페이지 번호 개수 + 1

 

이를 코드로 표현하면 다음과 같다.

int startPage = (int) ((currentPage - 1) / pageCount) * pageCount + 1;

끝 번호를 구하는 식은 다음과 같았다.

 

( ( ( 현재 페이지 번호 - 1 ) / 페이지 번호 개수 ) × 페이지 번호 개수 + 1 ) + 페이지 번호 개수 - 1

≒ 시작 번호 + 페이지 번호 개수 - 1

 

코드로 보면 아래와 같다.

int endPage = startPage + pageCount - 1;

이전 글에서 마지막에 제시했듯, 페이지 끝 번호를 구하는 식을 통해 얻은 값이 전체 페이지 수보다 큰 경우가 있을 수 있다.

 

따라서 아래 조건을 추가해준다.

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

 

[HTML태그 생성]

페이지 번호와 [다음] 또는 [이전]버튼을 생성할 때, 해당 버튼에 주소가 매핑되어야 한다.

 

검색 조건 또는 정렬 조건이 keyword와 keyfield에 들어 있으므로 우선 주소 뒤에 달아줄 문자열을 만들어 두자.

String sub_url = "";
if(keyword != null) {
    sub_url = "&keyfield="+keyfield+"&keyword="+keyword;
} else if(keyword == null) {
    sub_url = "&keyfield="+keyfield;
}

 

이제 위에서 구한 값들을 이용해 조건에 따라 태그를 생성하고, 페이지 번호에 링크를 달아줘야 한다.

 

이 부분은 태그에 원하는 클래스나 인라인 스타일 등을 부여해서 커스텀할 수 있으니, 태그 자체의 내용보다는 조건을 유의해서 보면 될 것이다.

 

이해를 위해 개념을 조금 잡고 가겠다.

 

예를 들어, 33개의 게시물이 있고 5개씩 나눠서 글을 보여줄 것인데, 5개의 페이지씩 분할하고 싶다면,

 

위 식의 계산들에 따라 총 7개의 페이지가 나오고 1, 2, 3, 4, 5 [다음]이 한 묶음, [이전] 6, 7 이 한 묶음이 될 것이다.

 

먼저 [이전]버튼을 보자.

 

[이전]버튼은 간단하다. 현재 페이지가 한 페이지에서 보여주기로한 버튼의 개수보다 크면, 이미 한 묶음이 앞에 있다는 뜻이다.

 

다시 말하면 현재 페이지가 6번일 때, 5개씩 보여주기로 했으니까, 6 > 5로 앞에 5개의 한 묶음이 있다는 뜻이다. 이럴 때 [이전]버튼을 표시하면 된다.

page = new StringBuffer();
if (currentPage > pageCount) {
    page.append("<a href="+pageUrl+"?pageNum="+ (startPage - 1) + sub_url +">");
    page.append("[이전]");
    page.append("</a>");
}

전달하는 주소는 호출할 컨트롤러 주소 뒤에 pageNum(= currentPage)값을 (현재 보고 있는 페이지 번호의 첫 번째 값 - 1)로 넣어주어 앞 묶음의 가장 마지막 번호 페이지를 매핑하겠다는 의미로 보면 된다.

 

[다음] 버튼은 언제 생성되어야 할까..?

 

만약 총 페이지가 5개일 경우 [다음] 버튼은 없을 것이다.

 

하지만 6개 이상이라면 [다음] 버튼은 있을 것이다.

 

만약 현재 6페이지에 위치했는데 총 페이지가 11개 이상이라면, [다음] 버튼은 있을 것이다.

 

좀 더 정리해서 말하자면 5개씩 보여주기로 했는데,

 

총 페이지가 현재 화면에 보이는 (시작 페이지 번호 + 보여주기로 한 페이지 개수) 이상이면 그 뒤에 페이지가 더 있다는 뜻이다.

 

예를 들어 아래와 같을 때(현재 페이지 번호는 시작 페이지 번호 구할 때 사용함),

 

총 페이지 수 현재 페이지 번호 시작 페이지 번호 페이지 번호 개수 시작 + 번호 개수
6 3 1 5 1 + 5 = 6

 

이니까 [다음] 버튼이 보여야 한다.

 

총 페이지 수 현재 페이지 번호 시작 페이지 번호 페이지 번호 개수 시작 + 번호 개수
5 2 1 5 1 + 5 = 6

 

반대로 위와 같은 경우 (시작 번호 + 번호 개수)가 6으로 총 페이지 수보다 적다. 따라서 [다음]버튼은 없어야 한다.

 

코드를 보자.

if (totalPage >= startPage + pageCount) {
    page.append("<a href="+pageUrl+"?pageNum="+ (endPage + 1) + sub_url +">");
    page.append("[다음]");
    page.append("</a>");
}

여기서도 전달하는 주소는 pageNum에 현재 화면에 보이는 (페이지 번호 중 마지막 값 + 1)을 할당해, 다음 페이지의 시작 번호를 매핑하겠다는 의미다.

 

마지막으로 번호 자체를 생성하는 코드를 보자.

 

번호는 페이지 시작 번호 ~ 끝 번호까지 루프를 돌려서 생성하면 된다.

for (int i = startPage; i <= endPage; i++) {
    if (i > totalPage) {
        break;
    }
    if (i == currentPage) {
        page.append("&nbsp;<b><span style='color:red;'>");
        page.append(i);
        page.append("</span></b>");
    } else {
        page.append("&nbsp;<a href='"+pageUrl+"?pageNum=");
        page.append(i);
        page.append(sub_url+"'>");
        page.append(i);
        page.append("</a>");
    }
    page.append("&nbsp;");
}

[이전] 페이지 번호 [다음] 순으로 태그가 만들어져야 하므로 코드 순서도 아래와 같이 적어야 한다.

 

[이전] 코드

 

페이지 번호 코드

 

[다음] 코드

 

[결과화면]

 

페이징 결과화면

 

page 관련 태그 코드를 생성할 때, 부트스트랩 클래스 등을 잘 사용해서 작성하면, 아래와 같이 만들 수 있다.

 

페이징 디자인 커스텀

 

이상으로 리스트 페이징 처리 관련 내용을 모두 정리해봤다.

 

그 과정에서 @RequestParam, ModelAndView, xml, EL표현식 param, 제네릭 표현식, Oracle ROWNUM, 페이징 처리 수식 유도 방법 등의 개념도 짧게나마 정리했다.

 

PagingUtil 클래스 내부 코드는 일부 내가 이해하고 유도한 수식대로 변형한 부분이 있지만, 받아서 쓴 코드가 잘 작성되어 있어 대부분 그대로 가져왔다.

 

이 내용을 기반으로 페이징 관련 처리를 할 때, 상황에 따라 변형해서 사용할 수 있을 것으로 기대한다.

 

혹시 잘못된 정보나 오류가 있으면 알려주길 바라며.. 리스트 페이징 처리 정리를 마치도록 하겠다.