글 작성자: beaniejoy

웹페이지를 만들거나 게시판을 만들 때 중요한 요소 중에 하나가 페이징 관리(pagination)입니다. 배운 내용을 바로 복습해야 기억에 남을 것 같아서 이번시간에는 게시판 페이징 관리에 대해서 정리해보도록 하겠습니다.

여러 포털사이트나 웹서핑하다보면 자주 보는 것이 있습니다. 실제 포털 사이트로 보여드리면

네이버 증권 코스피200 상위종목 게시판

 위 그림과 같이 아래 페이지 넘버를 넘길 수 있게 페이징 처리를 했습니다. 이렇게 해야 수많은 데이터 중에서 원하는 페이지의 데이터를 바로바로 볼 수가 있습니다. 이것을 jsp를 이용해서 한번 구현해보고자 합니다.

 

 

📌 1. 필요한 변수부터 선언하고 할당하자!

페이징 처리를 통한 게시판 테이블을 보여주기 위해 어떠한 요소들을 고려해야 할지 알아봅시다.

🔖 1-1. 현재 페이지를 알리는 페이지 넘버

 현재 어디 페이지에 있는지 알려주는 변수를 parameter를 통해 전달받아서 변수에 저장해야 합니다. 페이징 처리에도 중요하게 쓰이고 수정, 추가, 삭제 등을 처리할 때마다 웹페이지가 이동될텐데 이 때 메인에서 몇 번째 페이지를 가리키고 있었는지 계속 전달하면서 기록해야 합니다. 그래야 나중에 작업을 마치고 다시 메인에 돌아왔을 때 본인이 보고있었던 페이지를 그대로 보여줄 수 있을 것입니다.

String tempPage = request.getParameter("page");

// cPage(현재 페이지 정하기)
if (tempPage == null || tempPage.length() == 0) {
    cPage = 1;
}
try {
    cPage = Integer.parseInt(tempPage);
} catch (NumberFormatException e) {
    cPage = 1;
}

현재 페이지 넘버는 url에서 파라미터(쿼리라고도 부릅니다.)를 받아서 cPage에 할당했습니다. 혹시나 page 파라미터가 없는 경우나 숫자가 아닌 문자가 들어있는 경우를 대비해 예외처리를 해야합니다.

 

🔖 1-2. 한 페이지에 들어갈 데이터(테이블의 행)의 개수

 우선 한 페이지에 몇 개의 데이터를 뿌릴 것인지 알아야 합니다. 우선 한 페이지당 5개씩으로 정의하겠습니다. 5개씩 정의하면 다음과 같은 그림이 나오겠죠?

 

 

🔖 1-3. 페이지  한 묶음(Block)을 몇 개의 페이지로 지정할 것인가?

 페이징 처리할 때 한 묶음을 몇 개의 페이지로 지정할 것인지 정해야 합니다. 

 위 그림에서는 데이터 수가 작아서 페이징이 3개 밖에 없지만 데이터 수가 많으면 한 묶음으로 5개씩, 10개씩도 가능합니다. 맨 위에 네이버 증권 페이지에서 가져온 사진에서는 10개의 페이지를 한 묶음으로 한 것을 볼 수 있었습니다. 보통 10개의 페이지를 한 묶음으로 하는 경우가 많은 것 같더라구요! 이번에는 임의로 5개씩으로 지정해보도록 하겠습니다.

 

🔖 1-4. 각 페이지의 시작지점 정하기

각 페이지에 데이터를 5개씩 뿌릴 텐데 가장 먼저 나올 시작포인트를 정해야 합니다. 이것을 이해하려면 DB에 보내는 SELECT쿼리를 이해해야 합니다. 제가 보내려는 쿼리는 이렇습니다.

SELECT deptno, dname, loc
FROM dept
ORDER BY deptno
LIMIT 0, 5

 LIMIT절을 붙여서 5개씩 뿌릴 수 있게끔 할 계획입니다. "LIMIT (start지점), (몇개를 뽑을 건지)" 이런 형식으로 LIMIT절을 작성합니다. start지점은 0부터 시작해서 원하는 지점으로 선택하면 되고 다음 인자는 끝지점을 표시하는 것이 아니라 몇 개를 뽑을 것인지 Length를 정하는 것입니다. 결국 페이지별로 5개씩 뿌릴 것이기에 length = 5는 고정시키고 start지점을 결정하는 것이 중요합니다.
(그리고 중요한 특징 중 하나가 데이터가 뽑으려는 length보다 작더라도 오류가 발생하지 않고 있는 데이터들을 몽땅 출력합니다.)

page 1 2 3 ... n
start 0 + 0 X 5 0 + 1 X 5 0 + 2 X 5 ... 0 + (n-1) X 5

  정말 간단한 패턴이죠? 결국 start지점은 "0 + (현재page - 1) X 5" 형태를 가지는 등차수열입니다. 이것을 변수 start에 지정하겠습니다. 

 

 

🔖 1-5. 각 페이지 묶음(block)의 시작지점과 끝지점 구하기

 

1-5-1. 각 블럭의 시작페이지 넘버 지정하기

한 페이지 블럭에는 시작 페이지와 끝 페이지를 정해야 합니다.

한 블럭에서의 시작지점도 위에 각 페이지 시작지점 구하는 것과 비슷합니다. 다만 여기서 알아야할 것은 현재 어디 블럭에 위치해있는지 입니다. 

No. 1 2 3 4 5
Block 1~5 6~10 11~15 16~20 21~23(끝)
startPage 1+(1-1)X5 1+(2-1)X5 1+(3-1)X5 1+(4-1)X5 1+(5-1)X5

 이것도 전에와 같이 startPage가 등차수열을 이룹니다. 현재페이지가 어디 블럭에 위치해 있는지만 안다면 startPage는 바로 구할 수 있을 것입니다.

currentBlock = cPage % pageLength == 0 ? cPage / pageLength : (cPage / pageLength) + 1;
startPage = (currentBlock - 1) * pageLength + 1;
endPage = startPage + pageLength - 1;

 여기에 끝지점 endPage는 startPage + pageLength(여기서는 5) - 1 해주면 나옵니다. 그런데 주의할 점이 있습니다. 맨 마지막 블럭에서는 끝지점에 도달하기도 전에 페이지가 끝나버리는데 이것도 같이 처리해주어야 합니다.

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

 endPage가 총 페이지 개수(totalPages) 보다 크면 끝지점을 맨 마지막 페이지로 지정해주면 됩니다. 그러면 여기서 totalPages는 어떻게 구할까요?

 

1-5-2. totalPages: 총 페이지 수 구하기

 총페이지 수를 구하기 위해서는 총 데이터의 개수를 알아야 합니다. 예를 들어 데이터가 83개가 있다고 생각하고 한페이지에 5개씩 담을려고 한다면 총 페이지는 [16(83 / 5) + 1]개가 될 것입니다. +1을 한 것은 맨 마지막 5개를 다 채우지 못한 나머지 3개의 데이터가 페이지 하나를 구성하고 있기 때문에 1을 더해준 것입니다. 데이터의 개수가 5의 배수라면 5로 나눈 몫이 곧 총 페이지수가 되겠지만 나머지가 발생하면 그 나머지가 페이지 하나를 구성할 것이기에 1을 더해주어야 하는 것이라고 생각하면 됩니다.

  1. 데이터 개수가 5의 배수(5로 나눈 나머지가 0) → (데이터 개수 / 5) = 총 페이지 수
  2. 데이터 개수가 5의 배수가 아니라면(나머지 발생) → (데이터 개수 / 5) + 1 = 총 페이지 수 
totalPages = totalRows % len == 0 ? totalRows / len : (totalRows / len) + 1;
if (totalPages == 0) {
    totalPages = 1;
}

 

1-5.3. totalRows: 총 데이터 개수 구하기

 totalPages를 구하기 위해 totalRows가 필요로 합니다. 어떻게 구하면 될까요. 원하는 데이터는 DB에 다 저장이 되어있으니 쿼리로 데이터 개수를 뽑아내면 됩니다. SELECT문에 COUNT함수를 통해서 뽑으면 좋겠네요. 이를 위해 DAO 클래스 내에 메서드를 정의해서 총 데이터의 개수를 반환해주도록 만들면 됩니다. 

// DeptDao.java
public int getTotalRows() {
    int count = 0;

    Connection con = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;

    try {
        con = ConnLocator.getConnection();
        StringBuffer sql = new StringBuffer();
        sql.append("SELECT COUNT(deptno) FROM dept ");

        pstmt = con.prepareStatement(sql.toString());

        rs = pstmt.executeQuery();

        int index = 0;
        if (rs.next()) {
            count = rs.getInt(++index);
        }
    } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } finally {
        try {
            if (rs != null)
                rs.close();
            if (pstmt != null)
                pstmt.close();
            if (con != null)
                con.close();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    return count;
}
// list.jsp
totalRows = dao.getTotalRows();

getTotalRows 메서드를 통해 totalRows에 총 데이터 수를 저장했습니다.

 

📌 2. 정리

  1. 페이징 처리를 위해서 필요한 데이터는 크게 두 부분을 고려해야 합니다.
      - 첫 번째는 테이블의 데이터 정렬부분(몸통부분)
      - 두 번째는 페이징처리 넘버부분(페이지 숫자와 prev, next를 표시하는 부분)
  2. 첫 번째 테이블의 데이터정렬부분에서 알아야할 변수는 이렇습니다.
      - 각 페이지에 들어갈 데이터의 시작점(start) → 여기에 필요한 변수는 cPage(현재 페이지 넘버)
      - 각 페이지에 들어갈 데이터 개수(len) → 지정값
      - 두 변수를 알게되면 SELECT쿼리문을 보낼 수 있다. (LIMIT [start], [len] 절을 넣어서 데이터를 뽑을 수 있습니다.)
  3. 두 번째로 페이징 처리부분에서 알아야할 변수는 이렇습니다.
      - 한 블럭에 들어갈 시작페이지(startPage)와 끝페이지(endPage) 넘버
      - 한 블럭에 들어갈 페이지 개수(pageLength: 지정값)
      - startPage = 1+(currentBlock-1) X pageLength → 여기에 필요한 변수는 currentBlock(현재 페이지를 포함하는 블럭)
      - endPage = startPage + pageLength - 1
      - currentBlock = (cPage % pageLength == 0) ? cPage / pageLength : (cPage / pageLength) + 1 → cPage값이 필요
      - 맨 마지막 끝지점을 알기 위한 총 페이지 수(totalPages)
      - totalPages = (totalRows % len == 0) ? totalRows / len : (totalRows / len) + 1;
      - totalRows(총 데이터수) = DeptDao 클래스내에 getTotalRows() 메서드를 통해 받아온다.
  4. cPage(현재 페이지)는 parameter를 통해 전달받은 인자를 받아서 parseInt()를 통해 할당해준다. (예외처리 필수)


(다음 시간에는 페이징 처리에 쓰이는 이 변수들을 가지고 실제로 어떻게 구현을 하는지 정리해보겠습니다. 페이징 처리는 고려해야할 요소들이 많기 때문에 계산은 단순한 등차수열만 나오지만 어렵게 느껴집니다. 대부분의 페이징 처리가 이런 방식으로 이루어진다고 하셨으니 한 번 빠삭하게 이해하고 알아두고 있으면 요긴하게 잘 쓰일 것 같습니다!)