spring에서 JPA를 활용한 cursor based pagination 구현

보람찬하루·2024년 1월 18일
0

개발 배경

앱잼 중 앱 커뮤니티 프로덕트에 참여하게 되었다. 커뮤니티 특성 상 전체조회가 되는 API가 있었고 기존에 사용하던 전체 조회 방식은 한번에 모든 데이터에 관한 정보가 전송되어서 서버에 부하가 심해진다고 판단하였다! 그래서 한번에 전송하지 않고 데이터를 끊어서 보내는 pagination을 사용하게 되었고 Offset기반 방식의 문제점과 프로덕트 특성 상 무한 스크롤을 구현해야 하기 때문에 cursor기반 페이지네이션이 적합하다고 판단하여 cursor기반으로 진행하였다!

Pagination / Offset Based Pagination / Cursor Based Pagination 알아보기

Pagination에 대해 따로 정리할 예정입니다. 다음 아티클을 참고 해주세요! To be continued..

기본 개념 알아보기

페이지네이션을 구현하기 위해 사용하는 개념 및 인터페이스를 먼저 알아보겠습니다~!

  1. Cursor

    사용자에게 응답해준 마지막의 데이터의 식별자 값

  2. Limit

    사용자에게 한번에 전달할 데이터 개수

  3. Page<T>

    페이지 정보를 담게 되는 인터페이스

    public interface Page<T> extends Slice<T> {
        int getTotalPages();// 전체 페이지 개수
        long getTotalElements();// 전체 요소 개수
        <U> Page<U> map(Function<? super T, ? extends U> converter); // 변환기
    }
  4. Pageable

    페이지 처리에 필요한 정보를 담게 되는 인터페이스

    public interface Pageable {
    	int getPageNumber();
    	int getPageSize();
    	long getOffset();
    	Sort getSort();
    	Pageable next();
    	Pageable previousOrFirst();
    	Pageable first();
    	Pageable withPage(int pageNumber);
    	boolean hasPrevious();
    }
    1. PageRequest 클래스

      : Spring Data JPA에서 제공하는 Pageable 구현체

      • 변수 의미
        • page : 조회할 페이지 번호(0부터 시작)
        • size : 한 페이지당 최대 항목 수
        • sort : 정렬 정보(생략 가능)
        • direction : 정렬 방향(ASC, DESC)
        • properties : 정렬 대상 속성명
      • 메소드
        • of(int page, int size) : 0부터 시작하는 페이지 번호와 개수. 정렬 X
        • of(int page, int size, Sort sort) : 페이지 번호와 개수, 정렬 관련 정보
        • of(int page int size, Sort sort, Direction direction, String ... props) : 0부터 시작하는 페이지 번호와 개수
  5. Slice

    특정 데이터베이스 조회 등의 작업을 수행하고 그 결과를 반환하는 데 사용되는 객체

    public interface Slice<T> extends Streamable<T> {
        int getNumber(); // 현재 페이지
        int getSize(); // 페이지 크기
        int getNumberOfelements(); // 현재 페이지에 나올 데이터 수
        List<T> getContent(); // 조회된 데이터
        boolean hasContent(); // 조회된 데이터 존재 여부
        Sort getSort(); // 정렬 정보
        boolean isFirst(); // 현재 페이지가 첫 번째 페이지인지 여부
        boolean isLast(); // 현재 페이지가 마지막 페이지인지 여부
        boolean hasNext(); // 다음 페이지 여부
        boolean hasPrevious(); // 이전 페이지 여부
        Pageable getPageable(); // 페이지 요청 정보
        Pageable nextPageable(); // 다음 페이지 객체
        Pageable previousPageable(); // 이전 페이지 객체
        <U> Slice<U> map(Function<? super T, ? extends U> convert); // 변환기
    }

개발 환경

  • 클라이언트와 협업
  • Spring Boot '3.2.1'
  • Rest API

구현하기

게시물에 해당하는 답글 전체 조회를 Cusor기반 페이지네이션을 적용해서 구현해보겠습니다

  • controller - CommentController.java

    • cursor : 저희가 필요한 cursor값을 클라이언트에서 보내주기 때문에 @RequestParam으로 받아와야 합니다. 그리고 해당 값을 commentQueryService의 getCommentAll 메소드로 넘겨줍니다.
  • service - CommentQueryService클래스 안의 getCommentAll메소드

    • pageRequest : 페이지네이션 구현을 위해 PageRequest 객체를 생성해줍니다!
      1. JpaRepository 메서드 파라미터로 전달하면, Page 객체를 반환해주기 때문입니다.
      2. 저는 repository에서 불러올 때 정렬을 진행해서 of(int page, int size)를 사용했습니다. 이때 0부터 시작하고 한 페이지 안에 불러올 개수를 지정해줍니다. 개수는 DEFAULT_PAGE_SIZE를 사용하고 상단에서 private로 선언했습니다.
    • comment 테이블에 저장 된 comment를 불러와서 slice를 이용해 commentList를 선언해줍니다.
    • if문
      1. 페이지가 0부터(코드상에선 -1인데 0으로 바꿔주세요!) 시작하는데 첫 페이지일 땐 이전에 불러온 마지막 데이터 data(여기선 comment)가 없기 때문에 조건문을 걸어서 정렬을 진행한 comment 중 top으로 불러옵니다.
      2. 이 때 페이지네이션을 위해 pageRequest도 같이 넘겨줍니다.
      3. repository에서 반환된 값을 commentList에 저장해줍니다.
    • else 문
      1. 첫 페이지가 아닌 경우 클라이언트에서 넘겨준 cusor값(마지막 조회된 데이터 값)을 이용하여 repository에서 comment를 찾아옵니다.
      2. if문과 동일하게 pageRequest도 같이 넘겨주고 반환 값을 commentList로 저장합니다.
    • return값
      1. repository에서 반환된 commetList를 DTO와 매핑하기 위해서 stream과 map을 이용합니다.
      2. CommentAllResponseDto에 필요한 값과 매핑을 진행합니다.
      3. collect 연산을 사용하여 스트림의 결과를 리스트로 변환해줍니다.
  • repository

    • findCommentNextPage : 이전 페이지에선 반환된 cursor값(마지막 comment의 id값)을 사용하여 쿼리를 통해 다음 페이지를 불러옵니다! - @Query 어노테이션 사용
    • findCommentsTopByContentIdOrderByCreatedAtDesc : 첫 페이지일 경우(cursor값이 0일 경우) 생성 시간순으로 정렬 후 상단에서 잘라냅니다!
  • DTO

마무리

커서 기반 페이지네이션을 구현해보았는데요..!! 처음에 그냥 페이지네이션을 해야한다고 하다가 고생했었는데 구현에 앞서 page, slice, pageRequest 등등 관련 개념을 먼저 공부하고 진행하면 더욱 정확하게 이해할 수 있을 것 같습니다~!

profile
를 만들어 가자

0개의 댓글