페이지네이션, 페이징, 페이지 내비게이션 등 다양한 이름으로 불리는 이것은 웹 페이지에서 흔히 볼 수 있는 페이지 번호를 클릭하면 특정 페이지로 이동하는 부분이나 기법을 통칭하는 용어입니다.
위의 HTML 요소 전부를 편의상 블럭이라고 하겠습니다. 이 예제는 (1) 현재 페이지, (2) 블럭 당 페이지 개수, (3) 전체 글 개수, (4) 페이지당 글 개수에 따라 블럭에 몇 페이지부터 몇 페이지를 표시할지 숫자들을 알려줍니다.
코드
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
위의 예제 코드는 스프링 등 웹 서비스 프레임워크에서 사용할 수 있으며, 아래는 컨트롤러에서 사용한 간략한 예제입니다. (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()으로 적용하면 다음과 같습니다.
0개의 댓글