페이지네이션, 페이징, 페이지 내비게이션 등 다양한 이름으로 불리는 이것은 웹 페이지에서 흔히 볼 수 있는 페이지 번호를 클릭하면 특정 페이지로 이동하는 부분이나 기법을 통칭하는 용어입니다.

위의 HTML 요소 전부를 편의상 블럭이라고 하겠습니다. 이 예제는 (1) 현재 페이지, (2) 블럭 당 페이지 개수, (3) 전체 글 개수, (4) 페이지당 글 개수에 따라 블럭에 몇 페이지부터 몇 페이지를 표시할지 숫자들을 알려줍니다.

 

코드


package com.example.awsboard.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 페이지네이션을 위한 숫자 리스트 생성기
*
* 사용 방법:
* 1. 인스턴스 생성: new Paginator(Integer pagesPerBlock, Integer postsPerPage, Long totalPostCount)
* – pagesPerBlock = 한 블럭당 들어갈 페이지 개수 (예: 5인 경우 한 블럭에 [1 2 3 4 5] 등으로 표시)
* – postsPerPage = 페이지 하나 당 보여지는 Post(row)의 개수
* – totalPostCount = 테이블에 등록된 총 Post 개수
* – 위의 변수들은 setter/getter를 가지고 있습니다.
*
* 2. getTotalLastPageNum() 으로 총 페이지 개수를 확인합니다.
*
* 3. getFixedBlock(Integer currentPageNum) 또는 getElasticBlock(Integer currentPageNum)으로 페이지 리스트 생성합니다.
* – currentNum = 현재 페이지
* – 결과는 Map<String, Object> 형태로 반환되며, pageList키의 값이 페이지 리스트입니다.
* – 예) {totalPostCount=5555, isPrevExist=false, isNextExist=true, blockLastPageNum=11, postsPerPage=12,
* totalLastPageNum=462, currentPageNum=1, pagesPerBlock=11, pageList=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
* blockFirstPageNum=1}
*
* 4. getFixedBlock()은 현재 페이지 위치가 항상 고정되어 있으며, getElasticBlock()은 현재 페이지가 가능하면 블럭의 한가운데 위치하도록 합니다.
* – 예) 현재페이지가 6이고 블럭당 페이지 수가 5인 경우 getFixedBlock()에서는 [6, 7, 8, 9, 10]로 표시되지만,
* getElasticBlock()인 경우 [4, 5, 6, 7, 8]로 표시됩니다.
* – getElasticBlock()은 pagesPerBlock이 홀수일 때에만 사용할 수 있습니다.
*
*/
public class Paginator {
// 생성
private Integer pagesPerBlock;
private Integer postsPerPage;
private Long totalPostCount;
private Integer totalLastPageNum;
public Paginator(Integer pagesPerBlock, Integer postsPerPage, Long totalPostCount) {
this.pagesPerBlock = pagesPerBlock;
this.postsPerPage = postsPerPage;
this.totalPostCount = totalPostCount;
this.setTotalLastPageNum();
}
public Integer getPagesPerBlock() {
return pagesPerBlock;
}
public Integer getPostsPerPage() {
return postsPerPage;
}
public Long getTotalPostCount() {
return totalPostCount;
}
public Integer getTotalLastPageNum() {
return this.totalLastPageNum;
}
public void setPagesPerBlock(Integer pagesPerBlock) {
this.pagesPerBlock = pagesPerBlock;
}
public void setPostsPerPage(Integer postsPerPage) {
this.postsPerPage = postsPerPage;
this.setTotalLastPageNum();
}
public void setTotalPostCount(Long totalPostCount) {
this.totalPostCount = totalPostCount;
this.setTotalLastPageNum();
}
private void setTotalLastPageNum() {
// 총 게시글 수를 기준으로 한 마지막 페이지 번호 계산
// totalPostCount 가 0인 경우 1페이지로 끝냄
if(totalPostCount == 0) {
this.totalLastPageNum = 1;
} else {
this.totalLastPageNum = (int) (Math.ceil((double)totalPostCount / postsPerPage));
}
}
private Map<String, Object> getBlock(Integer currentPageNum,
Boolean isFixed) {
if(pagesPerBlock % 2 == 0 && !isFixed) {
throw new IllegalStateException("getElasticBlock: pagesPerBlock은 홀수만 가능합니다.");
}
if(currentPageNum > totalLastPageNum && totalPostCount != 0) {
throw new IllegalStateException("currentPage가 총 페이지 개수(" + totalLastPageNum + ") 보다 큽니다.");
}
// 블럭의 첫번째, 마지막 페이지 번호 계산
Integer blockLastPageNum = totalLastPageNum;
Integer blockFirstPageNum = 1;
// 글이 없는 경우, 1페이지 반환.
if(isFixed) {
Integer mod = totalLastPageNum % pagesPerBlock;
if(totalLastPageNum – mod >= currentPageNum) {
blockLastPageNum = (int) (Math.ceil((float)currentPageNum / pagesPerBlock) * pagesPerBlock);
blockFirstPageNum = blockLastPageNum – (pagesPerBlock – 1);
} else {
blockFirstPageNum = (int) (Math.ceil((float)currentPageNum / pagesPerBlock) * pagesPerBlock)
– (pagesPerBlock – 1);
}
// assert blockLastPageNum % pagesPerBlock == 0;
// assert (blockFirstPageNum – 1) % pagesPerBlock == 0;
} else {
// 블록의 한가운데 계산 (예: 5->2, 9->4)
Integer mid = pagesPerBlock / 2;
// 블럭의 첫번째, 마지막 페이지 번호 계산
if(currentPageNum <= pagesPerBlock) {
blockLastPageNum = pagesPerBlock;
} else if(currentPageNum < totalLastPageNum – mid) {
blockLastPageNum = currentPageNum + mid;
}
blockFirstPageNum = blockLastPageNum – (pagesPerBlock – 1);
if(totalLastPageNum < pagesPerBlock) {
blockLastPageNum = totalLastPageNum;
blockFirstPageNum = 1;
}
// assert blockLastPageNum == currentPageNum + mid;
// assert (blockFirstPageNum – 1) % pagesPerBlock == 0;
}
// 페이지 번호 할당
List<Integer> pageList = new ArrayList<>();
for(int i = 0, val = blockFirstPageNum; val <= blockLastPageNum; i++, val++) {
pageList.add(i, val);
}
Map<String, Object> result = new HashMap<>();
result.put("isPrevExist", (int)currentPageNum > (int)pagesPerBlock);
result.put("isNextExist", blockLastPageNum != 1 ? (int)blockLastPageNum != (int)totalLastPageNum : false);
result.put("totalLastPageNum", totalLastPageNum);
result.put("blockLastPageNum", blockLastPageNum);
result.put("blockFirstPageNum", blockFirstPageNum);
result.put("currentPageNum", currentPageNum);
result.put("totalPostCount", totalPostCount);
result.put("pagesPerBlock", pagesPerBlock);
result.put("postsPerPage", postsPerPage);
result.put("pageList", pageList);
return result;
}
public Map<String, Object> getElasticBlock(Integer currentPageNum) {
return this.getBlock(currentPageNum, false);
}
public Map<String, Object> getFixedBlock(Integer currentPageNum) {
return this.getBlock(currentPageNum, true);
}
public static void main(String[] args) throws Exception {
final int PAGES_PER_BLOCK = 5;
final int POST_PER_PAGE = 10;
// 총 게시글 수
long totalPostCount = 446;
// 인스턴스 생성
Paginator paginator = new Paginator(PAGES_PER_BLOCK, POST_PER_PAGE, totalPostCount);
try {
for(int i = 1; i <= paginator.getTotalLastPageNum(); i++) {
System.out.println(paginator.getElasticBlock(i));
}
} catch (Exception e) {
System.err.println(e);
}
}
}

view raw

Paginator.java

hosted with ❤ by GitHub

 

위의 예제 코드는 스프링 등 웹 서비스 프레임워크에서 사용할 수 있으며, 아래는 컨트롤러에서 사용한 간략한 예제입니다. (Lombok 사용됨)

예제 (일부)

import com.example.awsboard.service.posts.PostsService;
import com.example.awsboard.util.Paginator;
import com.example.awsboard.web.dto.PostsListResponseDTO;
import com.example.awsboard.web.dto.PostsResponseDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;
import java.util.Map;

@RequiredArgsConstructor
@Controller
public class IndexController {

    private final PostsService postsService;

    private static final Integer POSTS_PER_PAGE = 10;
    private static final Integer PAGES_PER_BLOCK = 5;

    @GetMapping("/")
    public String index(Model model, @LoginUser SessionUser user,
                        @RequestParam(value = "page", defaultValue = "1") Integer page) {


        // 글 목록 전송
        model.addAttribute("boardTitle", "자유게시판");
        model.addAttribute("requestFrom", "posts");

        // 페이지네이션
        try {
            Paginator paginator = new Paginator(PAGES_PER_BLOCK, POSTS_PER_PAGE, postsService.count());
            Map<String, Object> pageInfo = paginator.getFixedBlock(page);

            model.addAttribute("pageInfo", pageInfo);
        } catch(IllegalStateException e) {
            model.addAttribute("pageInfo", null);
            System.err.println(e);
        }

        model.addAttribute("posts", postsService.findAllByOrderByIdDesc(page, POSTS_PER_PAGE));

// .... 이하 생략 .... //

참고로 첫 번째 그림은 getFixedBlock()을 적용한 것이며, 6페이지를 getElasticBlock()으로 적용하면 다음과 같습니다.

문의 | 코멘트 또는 yoonbumtae@gmail.com




0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다