Woori.log홈화면의 블로그 및 프로젝트들을 제공하는 API를 확장하여, 페이지네이션(Pagination) 을 통해 모든 블로그 및 프로젝트들을 사용자들이 확인할 수 있도록 기획 방향이 정해졌다.
이에Spring Data JPA를 통해 쉽게Pagination을 구현하고 정리해보려 한다.
Spring에서 제공하는 인터페이스로, 페이지에 대한 정보를 담고있는 객체이다.
getPageNumber() : 현재 페이지 번호 (0부터 시작)getPageSize() : 한 페이지당 최대 항목 수getSort() : 정렬 정보를 반환핵심적인 메서드는 위와 같으며, Page에 대한 다양한 정보를 얻을 수 있다.
Pageable의 구현체 중 하나로, 페이지 정보를 생성하는 클래스이다.
페이지 번호, 페이지당 항목 수, 정렬 정보를 지정하여 Pageable인터페이스를 구현할 수 있다.
page : 조회할 페이지 번호 (0부터 시작)size : 한 페이지 당 최대 항목 수sort : 정렬 기준 (Optional)direction : 정렬 방향 (ASC, DESC)properties : 정렬 대상 속성명Pageable 객체를 JpaRepository에 매서드 파라미터로 전달하면 Page 객체를 반환한다.
이를 이용하여 Pagination을 구현할 수 있다.
Woori.log에는 어떻게 적용하였는지 알아보자.
@Cacheable(value = CacheNames.HOME_BLOGS)
public BlogHomeRes getBlogBasicInfos(int page) {
Page<Blog> blogPage = blogRepository.findAll(
PageRequest.of(page - 1, BLOG_PAGE_SIZE, Sort.by(SORT_CRITERIA).descending())
);
List<BlogBasicInfoDto> blogBasicInfoDtoList = blogPage.stream()
.map(BlogBasicInfoDto::create)
.toList();
return BlogHomeRes.of(blogPage.getNumber() + 1, blogPage.getTotalPages(), blogBasicInfoDtoList);
}
page를 파라미터로 전달받은 뒤, PageRequest.of 메서드를 통해 Pageable 객체를 생성한 뒤, findAll의 메서드 파라미터로 전달하고 있다.
PageRequest.of(page - 1, BLOG_PAGE_SIZE, Sort.by(SORT_CRITERIA).descending());
BLOG_PAGE_SIZE : 조회할 사이즈를 지정한다. (현재 6으로 지정하였다.)Sort.by(SORT_CRITERIA) : 정렬 기준을 설정하는 것으로, 현재는 생성일인 createdAt을 기준으로 내림차순 정렬하였다. (최신 블로그)프로젝트의 경우도 동일한 방식으로 로직을 구현하여 페이지네이션을 적용하였다.
JPA에서 Pageable을 통해 페이지네이션을 적용할 경우 Offset을 이용해 쿼리문을 작성하게 된다.
이를 offset-based pagination이라 한다.
offset-based pagination
limit: size, 조회할 record의 수를 지정offset: 건너뛸 개수
위 두가지를 통해 페이지를 매기는 방식이다.SELECT * FROM Blog LIMIT 10 OFFSET 0; // 0 페이지위와 같은 쿼리를 통해 데이터를 조회하게 된다.
Offset-based pagination의 경우 offset이 커질 경우, 성능이 나빠지는 단점이 존재하는데, 이는 추후cursor-based pagination을 소개하며 설명할 예정이다.
LIMIT과 OFFSET을 이용한 쿼리로 페이지네이션을 구현하여 Page 객체를 제공하며, 해당 Page 객체에서 전체 페이지 수, 현재 페이지 수, 현재 페이지의 요소의 갯수 등의 정보를 얻을 수 있다.
적용예시와 같이 간단하게 생성일 기준으로 페이지네이션을 구현할 수도 있지만, 다양한 조건과 정렬기준을 통해 폭넓게 활용 가능하다.
예시로, 내가 작성한 글중에서 댓글이 많이 달린 블로그를 기준으로 정렬할 수 있을 것이다.
public BlogRes getBlogAtMyPage(Long userId, int page) {
Page<Blog> blogPage = blogRepository.findAllByAuthorId(
userId,
PageRequest.of(page - 1, BLOG_PAGE_SIZE, Sort.by("commentCount").descending())
);
List<BlogBasicInfoDto> blogBasicInfoDtoList = blogPage.stream()
.map(BlogBasicInfoDto::create)
.toList();
return BlogRes.of(blogPage.getNumber() + 1, blogPage.getTotalPages(), blogBasicInfoDtoList);
}
Pageable과Page,PageRequest에 대해 정리하며 JPA에서 제공하는 페이지네이션 관련 객체들의 메서드와 쿼리, 작동원리에 대해 학습할 수 있어 추후 JPA를 사용하지 않을 때의 페이지네이션 구현시에 많은 도움이 될 것같다.
또한,offset-based Pagination의 특징을 알아보며, 장단점을 고려하며 도입해야함을 인지하였고, 추후에는 기회가 된다면cursor-based Pagination도 적용해보고 싶다.
참고