과거 프로젝트를 진행했을 때, 클라이언트 단에 페이지네이션을 위해 Page
객체를 직접 만들어 구현했었다.
다음은 내 프로젝트 Page 객체 코드
이다. (직접 구현해보았지만, 원하는 대로 오프셋이 맞추어지지 않고 페이지 크기가 제멋대로였다.)
public class Pagination {
private int totalRecordCount; // 전체 데이터 수
private int totalPageCount; // 전체 페이지 수
private int startPage; // 첫 페이지 번호
private int endPage; // 끝 페이지 번호
private int limitStart; // LIMIT 시작 위치
private boolean existPrevPage; // 이전 페이지 존재여부
private boolean existNextPage; // 다음 페이지 존재여부
public Pagination(int totalRecordCount, SearchDto params) {
if(totalRecordCount > 0) {
this.totalRecordCount = totalRecordCount;
calculation(params);
}
}
private void calculation(SearchDto params) {
// 전체 페이지 수 계산
totalPageCount = ((totalRecordCount - 1) / params.getRecordSize()) + 1;
if(params.getPage() > totalPageCount) {
params.setPage(totalPageCount);
}
// 첫 페이지 번호 계산
startPage = ((params.getPage() - 1) / params.getPageSize()) * params.getPageSize() + 1;
// 끝 페이지 번호 계산
endPage = startPage + params.getPageSize() - 1;
// 끝 페이지가 전체 페이지 수보다 큰 경우, 끝 페이지 전체 페이지 수 저장
if(endPage > totalPageCount) {
endPage = totalPageCount;
}
// LIMIT 시작 위치 계산
limitStart = (params.getPage() - 1) * params.getRecordSize();
// 이전 페이지 존재 여부 확인
existPrevPage = startPage != 1;
// 다음 페이지 존재 여부 확인
existNextPage = (endPage * params.getRecordSize()) < totalRecordCount;
}
}
전체 데이터 수(totalElements)
, 전체 페이지 수(totalPages)
, 페이지 번호(pageNumber)
, 페이지 크기(pageSize)
등 구글링을 통해, 고대 개발자들의 유산을 재활용 중이었다. 이전, 다음페이지의 오프셋을 일일이 계산해야 하는 선배 개발자들의 피땀눈물이다.
그러던 중, 스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용할 수 있다는 걸 알게 되었다.
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
return page;
}
API EndPoint에는 파라미터를 매핑하는 쿼리나 Value가 전혀 없다. 하지만 Pageable이 PageRequest 객체를 생성한다.
Test 하기 전 Member 100명을 DB에 Insert 하고 다음 메서드 실행.
http://localhost:8080/members?page=0&size=3&sort=id // Postman에서 get 메서드 "send"
페이지 번호 2, 페이지 별 노출할 데이터 건수 3, member_id 오름차순 정렬로 설정했다.
page: 현재 페이지, 0부터 시작한다.
{
"content": [
{
"id": 7,
"username": "user6",
"teamName": null
},
{
"id": 8,
"username": "user7",
"teamName": null
},
{
"id": 9,
"username": "user8",
"teamName": null
}
],
"pageable": {
"sort": {
"unsorted": false,
"sorted": true,
"empty": false
},
"pageNumber": 2,
"pageSize": 3,
"offset": 6,
"paged": true,
"unpaged": false
},
"totalPages": 34,
"totalElements": 100,
"last": false,
"numberOfElements": 3,
"size": 3,
"number": 2,
"sort": {
"unsorted": false,
"sorted": true,
"empty": false
},
"first": false,
"empty": false
}
Page 관련 모든 정보를 볼 수 있다. (여태 힘들게 다 구현했었는데...)
쿼리 조건으로 하지 않고, @PageableDefalut
를 사용하여 파라미터 안에서 설정도 가능하다.
부가적으로 .map()을 활용하여 엔티티를 직접 노출하지 않고, 엔티티를 Dto로 감싸야 한다. 외부에 노출 시 중요한 데이터 노출 및 도메인 스펙 자체가 바뀔 수 있기 때문이다.
@GetMapping("/members")
public Page<MemberDto> list(@PageableDefault(size = 4, sort = "username") Pageable pageable) {
return memberRepository.findAll(pageable)
.map(MemberDto::new);
}
시작 Page를 0이 아닌 1로 하고 싶으면 PageRequest class를 오버라이딩하여 사용해야 한다.
PageRequest는 정적 팩토리 메서드 of를 사용하여 인스턴스를 생성할 수 있다.
page : 0부터 시작하는 페이지 인덱스 번호
size : 한 페이지에 반환할 데이터의 개수
sort : 정렬 방식
글이 많은 도움이 되었습니다, 감사합니다.