티스토리 뷰

반응형

페이징에 대한 이해를 하기 위해서는 페이징을 하기 위해 각 DBMS 가 어떤 정보를 제공해줘야 하는지 알아야 합니다. DBMS 마다 페이징을 위한 방법이 다른데, 제공하는 정보가 다르기 때문입니다.

모든 DBMS 는 내가 원하는 정보의 총 갯수를 가져오는 기능을 제공합니다. 바로 select count(*) 을 이용하는 것입니다. 이것을 이용해서 게시판 아래 출력되는 페이지 번호를 생성할 수 있습니다.

그렇다면, 해당 페이지의 게시물만 가져오는 방법은 무엇일까요?

오라클은 전통적으로 ROWNUM 을 제공합니다. 출력되는 정보에 순차적인 번호를 부여함으로써 원하는 게시물만 가져오는 방식입니다.

MS SQL Server 는 예전에는 TOP 을 이용했습니다. 출력될 정보 중 상위 몇 개를 가져온 뒤, 그 중 원하는 시작점까지 프로그램 상에서 이동을 해서 그 뒤의 것들을 가져오는 방식입니다. 하지만, 최근에는 오라클과 마찬가지로 ROW_NUMBER() 을 제공함으로써 좀 더 편한 페이징이 가능해졌습니다.

MySQL 이나 PostgreSQL 은 전통적으로 LIMIT 를 이용한 페이징을 제공했습니다. 페이징 할 때 크게 고민할 것 없이 사용이 가능하므로 매우 개발자 친화적이긴 하지만, 두 DBMS 의 LIMIT 는 의미가 약간 다르므로 주의해서 사용해야 합니다.

참고로, 제가 주로 사용하는 NoSQL 제품인 Couchbase 에서는 limit 와 skip 을 통해서 페이징을 가능합니다. 사실 내부적인 동작 원리는 비슷하지만, 어떻게 페이징된 정보를 가져오냐의 차이인 것 같습니다.



이번에는 게시판 하단에 표시되는 페이지 번호에 대해서 알아보겠습니다.

예를 들어 25 개의 게시물이 등록된 게시판(board)이 있다고 하겠습니다. 그럼 다음과 같은 Query 을 통해 총 게시물의 수(totalCount)를 가져올 수 있습니다.


select count(*) as totalCount from board


그럼 프로그램에선 몇 가지 정보를 이용해서 하단의 페이지 번호를 생성할 수 있습니다. 필요한 정보는 다음과 같습니다.


1. 한 페이지에 출력될 게시물 수 (countList)

2. 한 화면에 출력될 페이지 수 (countPage)

3. 현재 페이지 번호 (이하 page)


처음 해야할 것은 총 몇 페이지가 존재하는지 알아내는 것입니다(총 페이지, 이하 totalPage). 이 때 필요한 것은 countList 인데, 이해를 돕기 위해 countList = 10 혹은 countList = 5 라고 가정하고 계산해보겠습니다.


25 / countList

25 / 10 = 2 나머지 5

25 / 5 = 5 나머지 0


우리가 기대하는 수는 3 와 5 입니다. 왜냐하면 25 개의 게시물이 있을 경우 한 페이지에 10 개씩 글을 보여주면 3 번째 페이지에서 5 개의 게시물만 표시될 것이고, 한 페이지에 5 개씩 보여줄 경우 5 번째 페이지에서 마지막 글까지 다 보여주기 때문입니다. Java 와 같은 언어에서는 int 형으로 보통 계산을 하기 때문에 25 / 10 을 해버리면 소수점 혹은 나머지가 사라지고 정수값인 2 만 남게 됩니다. 그러므로 여기에 1 을 더하면 3 총 페이지는 3 이 될 것이라 생각하고 1 을 더하는 경우가 있습니다. 하지만, 무조건 1 을 더하게 되면 countList = 5 와 같은 경우 때문에 부정확한 결과가 나오게 됩니다. 의외로 이렇게 계산해서 마지막 페이지에 게시물 안나오는 곳이 많습니다. 결국 가장 정확하면서 단순한 방법은 나머지가 있을 경우에만 1 을 더해줘야 합니다. Java 로 된 최종 코드는 아래와 같이 될 것입니다. 기본 원리는 countList 로 나눈 뒤, 다시 전체 게시물 수를 countList 로 나누어서 나머지 값을 얻은 뒤(Java 에서 % 는 나머지만을 구하는 연산자입니다) 이 값이 0 보다 크면 1 을 더해주는 것입니다.


int totalCount = 25; // 물론 실제론 여긴 DBMS 에서 조회해서 들어가야 합니다.
int countList = 10;

int totalPage = totalCount / countList;

if (totalCount % countList > 0) {

    totalPage++;

}


혹은 아래와 같이 처리할 수도 있겠죠. 25 / 5 을 한 결과가 5 일 때, 역으로 5 * 5 을 하면 원래의 총 게시물 수와 일치하게 되지만, 25 / 10 을 해서 정수 부분만 취할 경우 2 가 총 페이지 수가 되어 2 * 10 으로 다시 역산을 하면 총 게시물 수가 20 이 되어서 일치하지 않게 되므로, 이러한 원리를 이용해서 총 페이지 수를 한 개 늘려주는 것입니다.


int totalCount = 25;
int countList = 10;

int totalPage = totalCount / countList;

if (totalCount > countList * totalPage) {

    totalPage++;

}


이렇게 하면 총 페이지 수를 알 수 있습니다. 이 값을 통해서 한 화면에 출력될 페이지 수가 10 개라고 해도, 3 페이지까지만 존재할 경우 1 에서 3 페이지까지만 하단에 출력해줄 수 있습니다. 다시 한 번 강조하지만, 실제로 이런 처리 안해서 마지막 페이지가 빈 페이지로 나오는 사이트가 생각보다 많습니다.


또한, 잘못된 현재 페이지에 대한 보정도 가능합니다.

현재 페이지 번호가 총 페이지 번호보다 크다면 어떻게 해야 할까요? 현재 페이지를 강제로 총 페이지 번호로 치환하는 것도 방법이 될 것입니다.


int page = 4;
int totalCount = 25;
int countList = 10;

int totalPage = totalCount / countList;

if (totalCount % countList > 0) {

    totalPage++;

}
// 여기까지 계산하면 totalPage 는 3 이 됩니다.

// 4 페이지가 현재 페이지라고 했으나, 실제로 3 페이지가 총 페이지이므로 강제로 현재 페이지는 3 페이지라고 보정할 수 있습니다.
if (totalPage < page) {

    page = totalPage;

}


이번에는, 하단에 표시될 페이지 번호들을 어떻게 알아낼 수 있는지 알아보겠습니다.

이번 예제에는 totalCount = 255, countList = 10, countPage = 10 으로 하겠습니다. 그리고 현재 페이지인 page 는 5 와 22 의 두 가지 경우를 생각해보겠습니다. 또한, 페이지 표시 방식은 대한민국에서 가장 많이 쓰는 방식인 현재 페이지가 속한 countPage 개의 페이지를 보이는 방식으로 하겠습니다. 이 방식은 현재 페이지 기준으로 앞 뒤 몇 개의 페이지를 보여주는 외국의 방식보다 대한민국에서 더 많이 쓰는 방식입니다.


보통 한국에선 5 페이지를 보고 있다면 다음과 같은 형태로 하단 페이징 부분을 보여주는 경우가 많습니다. 빨간색으로 칠해진 5 가 현재 페이지입니다. 일반적으로 현재 페이지가 포함된 한 블럭이 모두 표시가 되고 그 블럭을 벗어나지 않는다면 어떤 페이지라도 동일한 페이지 번호들이 출력되는게 특징입니다. 아래 예제에서는 1~10 페이지 중 어떤 페이지에 존재하더라도 현재 페이지 표시를 제외하고 다른 모든 것들이 동일하게 보이고 동작한다는 것입니다.


[처음 페이지]  [이전 페이지]  1  2  3  4  5  6  7  8  9  10  [다음 페이지] [마지막 페이지]


하지만, 서양 문화권에서는 아래와 같이 출력하는 경우가 더 많습니다. 보통 서양에선 현재 페이지를 기준으로 앞과 뒤의 몇 페이지를 보여주는 형태로 많이 구현됩니다. 그래서 현재 페이지는 페이징 문자열의 중앙에 위치하는 것이 보통입니다.

1  ...  4  5  6  ... 26


장단점이 있겠지만 이 글에서는 위의 방식을 가지고 설명할 것이고, 3 부에 걸친 문서를 모두 숙지하신다면 아래의 방식도 충분히 혼자서 구현하실 수 있으리라 믿습니다. 원리가 크게 다르지 않을 것이니까요.


이제 실제 구현에 대해서 설명해 보겠습니다.

먼저 page = 5 일 경우입니다. 이 경우 우리가 예상하는 결과는 하단에 1 ~ 10 까지의 페이지 번호가 표시되는 것입니다. 그 이유는 countPage 가 10 이기 때문입니다. countPage 가 10 일 경우 처음에는 1~10, 그 다음은 11~20, ... 이런 식으로 10 개씩 표시되는 것을 예상할 수 있습니다. countPage 가 만약 7 이라면 1~7, 8~14, ... 이런 식으로 표시되겠죠.


이런 페이지 번호 계산을 하는 방법은 몇 가지가 있습니다만, 가장 간단하고 쉬운 방법은 바로 시작 페이지 번호를 먼저 계산해내는 것입니다. page = 5 일 경우 시작 페이지(startPage)는 1, 마지막 페이지(endPage)는 시작 페이지에서 10 페이지까지라는 건 금방 이해하시리라 생각합니다. 그럼 page = 5 에서 어떻게 1 페이지를 찾아낼 수 있을까요? 아주 쉽습니다. 그냥 countPage 로 나눠버리고 1 을 더해주면 됩니다. 그럼 마지막 페이지는 어떨까요? startPage 에 countPage 을 더한 뒤 1 을 빼주면 됩니다. 


아래 예제 코드에는 startPage 와 endPage 을 구하는 방법이 기술되어 있습니다. 그런데, 위에서는 간단하게 시작 페이지를 1 이라고 했는데, 실제 산술식에서는 조금 복잡한 형태를 띄고 있습니다. 역시나 위와 같이 Java 언어의 int 는 나머지를 지워버리는 원리를 이용하는 것입니다.

첫번째 표시될 페이지 리스트는 단번에 1~10 이라는 숫자라는 걸 알 수 있습니다. 이 숫자들은 10 으로 나눌 경우 10 을 제외하고는 모두 0 이 몫이라는 것도 알 수 있습니다. 다음 페이지 리스트인 11~20 에서는 마찬가지로 20 을 제외한 모든 수의 몫이 1 이라는 것 역시 조금만 생각해보면 알 수 있을 것입니다. 여기서 공식이 나오는데, 결국 리스트에 표시될 숫자가 1~10 일 경우 혹은 11~20 일 경우 현재 페이지가 무엇이든 1 을 뺀 상태에서 나누기를 하여 몫을 구하면 몇 번째 페이지 리스트인지 알 수 있다는 것입니다. 여기에 다시 countPage 을 구하고 계산을 위해 빼주었던 1 을 더하면 시작 페이지를 구할 수 있게 됩니다.


int page = 5;
int countPage = 10;

int startPage = ((page - 1) / countPage) * countPage + 1;

int endPage = startPage + countPage - 1;


실제 보이는 페이지는 1 ~ 10 까지이지만, 실제 페이지 번호는 프로그램 내에서 0 ~9 까지로 처리하는 경우도 있습니다. 그게 Java 와 같은 언어에선 위에서 처럼 +1 와 -1 처리를 생략할 수 있어 내부 구현이 더 간단하기 때문인데, 저 역시 시작 페이지는 0 으로 해서 처리한 클래스를 만들어서 사용하는 경우가 많습니다만, 이해를 돕기 위해 여기서는 1 페이지는 1 의 page 번호를 가지도록 하겠습니다.


어쨌든, 이러한 공식에 의해 현재 페이지가 5 일 경우를 상정해서 계산을 하면 startPage = 1, endPage = 10 이라는 결과가 나옵니다.


그럼 화면에 출력할 때에는 어떻게 해야 할까요? 실제 화면에 출력될 때에는 시작 페이지와 마지막 페이지 사이의 번호를 모두 출력하면 됩니다. 그래서 loop 을 이용해서 숫자를 그대로 출력하면 됩니다.


int page = 5;
int countPage = 10;

int startPage = ((page - 1) / 10) * 10 + 1;

int endPage = startPage + countPage - 1;

for (int iCount = startPage; iCount <= endPage; iCount++) {

    System.out.print(" " + iCount + " ");

}


이런 식으로 출력하면 페이지 번호가 연달아서 출력이 되겠죠.


그렇다면 page 가 22 인 경우에는요?


먼저 단순히 산수 계산을 해보면 255 개의 게시물이 있을 경우 총 26 페이지가 존재할 것이고, 22 페이지가 있는 곳에는 21 에서 30 페이지 영역일 것입니다. 하지만, 26 페이지까지이기 때문에 단순히 21 페이지에서 countPage 을 더해서 마지막 페이지로 사용하면 안된다는 것을 대번에 눈치 채셨을 겁니다. 그래서 이 경우에도 마지막 페이지는 총 페이지 수로 대체를 해줘야 합니다.


int page = 22;
int countList = 10;
int countPage = 10;
int totalCount = 255;

int totalPage = totalCount / countList;

if (totalCount % countList > 0) {

    totalPage++;

}

if (totalPage < page) {

    page = totalPage;

}

int startPage = ((page - 1) / countPage) * countPage + 1;

int endPage = startPage + countPage - 1;

//  여기서 마지막 페이지를 보정해줍니다.
if (endPage > totalPage) {

    endPage = totalPage;

}

// [paging]
// 이 부분은 아래에서 추가로 설명합니다.
for (int iCount = startPage; iCount <= endPage; iCount++) {

    System.out.print(" " + iCount + " ");

}


대충 끝이 보이네요. 위에서 [paging] 이라고 표시된 부분에서 페이지 번호를 출력하는데, 출력을 할 때 css 코드를 넣거나 <a> 태그를 이용해서 연결을 하면 페이지 이동이 가능합니다. 이 때 현재 페이지는 굵은 글씨체로 표시하고 <a> 태그를 빼기 위해 아래와 같이 처리도 가능하겠죠.


// [paging]
// 이렇게 개선됩니다.
for (int iCount = startPage; iCount <= endPage; iCount++) {
 
    if (iCount == page) {

        System.out.print(" <b>" + iCount + "</b> ;");

    } else {

        System.out.print(" " + iCount + " ");

    }

}


보통 첫페이지 이동이나 이전 페이지, 다음페이지, 끝페이지 이동 버튼도 추가로 달아줍니다. 그래야 해당 페이지 리스트에 없는 곳으로도 이동이 될테니까요.

첫 페이지는 현재 페이지가 1 페이지가 아닐 때 표시되게 하는 경우도 있고, 시작 페이지가 1페이지가 아닐 때 표시하는 경우도 있습니다. 취향 문제죠. 어쨌든 이동하는 페이지는 항상 page = 1 이 되겠죠. 이전 페이지도 마찬가지입니다. 1 페이지가 아닐 경우 현재 페이지보다 1 페이지 앞으로 이동하도록 page - 1 값을 가지고 이동하게 하면 되죠. 다음페이지는 totalPage 와 비교해서 마찬가지로 표시해주면 됩니다.


int page = 22;
int countList = 10;
int countPage = 10;
int totalCount = 255;

int totalPage = totalCount / countList;

if (totalCount % countList > 0) {

    totalPage++;

}

if (totalPage < page) {

    page = totalPage;

}

int startPage = ((page - 1) / countPage) * countPage + 1;

int endPage = startPage + countPage - 1;

if (endPage > totalPage) {

    endPage = totalPage;

}

if (startPage > 1) {

    System.out.print("<a href='?page=1'>[처음 페이지]</a>");

}

if (page > 1) {

    System.out.println("<a href='?page=" + (page - 1) + "'>[이전 페이지]</a>");

}

for (int iCount = startPage; iCount <= endPage; iCount++) {

    if (iCount == page) {

        System.out.print(" <b>" + iCount + "</b> ");

    } else {

        System.out.print(" " + iCount + " ");

    }

}

if (page < totalPage) {
    
    System.out.println("<a href='?page=" + (page + 1) + "'>[다음 페이지]</a>");

}



if (endPage < totalPage) {

    System.out.print("<a href='?page=" + totalpage + "'>[마지막 페이지]</a>");

}


이렇게 처리하면 페이징 표시를 할 수 있게 됩니다. 참 쉽죠~~~잉? (고 밥 로스 아저씨 따라하기)


2019/01/21 - [Programming/기타] - 페이징(Paging)에 대한 이해 - (2) ROW NUMBER 을 이용한 게시물 가져오기


반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함