[JPA] Spring Data Jpa + Pageable로 Pagination 쉽게 구현하기

Doyeon·2023년 2월 28일
4
post-thumbnail
post-custom-banner

리뷰 글을 특정 조건으로 뽑아서 정렬 기준을 선택하여 조회하는 기능을 구현하고자 한다.
전체 리뷰글을 조회하거나, 내가 좋아요를 누른 리뷰글만 조회하거나, 내가 쓴 리뷰글만 조회하거나, 원하는 카테고리의 글만 조회하고 싶다면? → Spring Data JPA를 사용하여 구현할 수 있다!
게시글을 조회할 때 정렬 기준(최신순, 좋아요순)과 정렬방식(오름차순, 내림차순)을 선택하여 조회를 하고 싶다면? → 페이지네이션을 사용하면 쉽게 구현할 수 있다!
지금부터 Pageable 인터페이스를 사용하여 Spring Data JPA에서 페이지네이션을 구현해보자!

Pageable 인터페이스

  • Spring에서는 Pagination을 지원하는 Pageable 인터페이스를 제공한다.
    • getPageNumber() : 현재 페이지 번호를 반환(0부터 시작)
    • getPageSize() : 한 페이지당 최대 항목 수를 반환
    • getOffset() : 현재 페이지의 시작 위치를 반환
    • getSort() : 정렬 정보를 반환
    • next() : 다음 페이지 정보를 반환
    • previous() : 이전 페이지 정보를 반환
  • Pageable 을 이용해서 페이지 번호, 페이지당 항목 수, 필요에 따라 정렬 정보를 추가로 지정할 수 있다.
  • Pageable 로 지정한 정보들을 가지고 Page 객체를 반환할 수 있고, Page 객체는 조회된 데이터와 페이지 정보를 함께 갖게 된다.

PageRequest 클래스

  • Spring Data JPA에서 제공하는 Pageable 구현체 중 하나로, 페이지 정보를 생성하는 클래스이다.
  • 페이지 번호, 페이지당 항목 수, 정렬 정보를 이용하여 Pageable 인터페이스를 구현한다.
    • page : 조회할 페이지 번호(0부터 시작)
    • size : 한 페이지당 최대 항목 수
    • sort : 정렬 정보(생략 가능)
    • direction : 정렬 방향(ASC, DESC)
    • properties : 정렬 대상 속성명
  • PageRequest 객체를 생성하고 JpaRepository 메서드 파라미터로 전달하면, Page 객체를 반환하므로, Pagination을 구현할 수 있다.
    // 생성자
    PageRequest(int page, int size)
    PageRequest(int page, int size, Sort sort)
    PageRequest(int page, int size, Sort.Direction direction, String... properties)
    
    // 객체 생성
    PageRequest pageRequest = PageRequest.of(0, 10, Sort.by("id").ascending());

Pagination 구현

1. 모든 리뷰 글 조회 페이지네이션

  • Controller
    • Parameter로 페이지번호(pageNo), 정렬 기준(criteria)을 받는다.
    • default 값은 페이지 번호 0, 정렬 기준 createdAt(작성일자)이다.
    • /api/reviews?pageNo=1&criteria=likeCount
@GetMapping("/api/reviews")
public SuccessResponseDto<List<ReviewResponseDto>> getReviews(@RequestParam(required = false, defaultValue = "0", value = "page") int pageNo,
                                                              @RequestParam(required = false, defaultValue = "createdAt", value = "criteria") String criteria) {
    return reviewService.getReviews(pageNo, criteria);
}
  • Service
    • Pageable 객체를 만들어서 reviewRepository.findAll 메서드 파라미터로 전달한다.
    • page 객체로 리턴 받아서 ReviewResponseDto 로 변환한 후, getContent() 로 List만 추출해서 리턴한다.
//전체 게시글 조회
@Transactional(readOnly = true)
public SuccessResponseDto<List<ReviewResponseDto>> getReviews(int pageNo, String criteria) {

    Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.by(Sort.Direction.DESC, criteria));
    Page<ReviewResponseDto> page = reviewRepository.findAll(pageable).map(ReviewResponseDto::from);

    return ResponseUtils.ok(page.getContent());
}
  • Repository
    • findAll 전체 데이터를 조회한 후, page로 만든다.
public interface ReviewRepository extends JpaRepository<Review, Long> {
    Page<Review> findAll(Pageable pageable);
}

2. 내가 쓴 리뷰 조회 페이지네이션

  • Controller
    • api/myreviews?pageNo=1&criteria=createdAt
    • 내가 쓴 리뷰를 찾아야하기 때문에 인증객체인 @AuthenticationPrincipal UserDetailsImpl userDetail 가 파라미터로 필요하다.
@GetMapping("/api/myreviews")
public SuccessResponseDto<List<ReviewResponseDto>> getMyReviews(@AuthenticationPrincipal UserDetailsImpl userDetails,
                                                                @RequestParam(required = false, defaultValue = "0", value = "page") int pageNo,
                                                                @RequestParam(required = false, defaultValue = "createdAt", value = "criteria") String criteria) {
    return reviewService.getMyReviews(pageNo, criteria, userDetails.getUser());
}
  • Service
    • Pageable 객체를 만들어서 reviewRepository.findAllByUser 메서드 파라미터로 전달한다.
    • page 객체로 리턴 받아서 ReviewResponseDto 로 변환한 후, getContent() 로 List만 추출해서 리턴한다.
@Transactional(readOnly = true)
public SuccessResponseDto<List<ReviewResponseDto>> getMyReviews(int pageNo, String criteria, User user) {

    Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.by(Sort.Direction.DESC, criteria));
    Page<ReviewResponseDto> page = reviewRepository.findAllByUser(user, pageable).map(ReviewResponseDto::from);

    return ResponseUtils.ok(page.getContent());
}
  • Repository
    • findAllByUser : ReviewUser 가 파라미터와 일치하는 데이터를 찾는다.
    • 파라미터에 Pageable 을 같이 넣어주면 Page 객체로 리턴할 수 있다.
public interface ReviewRepository extends JpaRepository<Review, Long> {
    Page<Review> findAllByUser(User user, Pageable pageable);
}

3. 내가 좋아요한 리뷰 조회 페이지네이션

  • Controller
    • api/reviews/likes?pageNo=1&criteria=likeCount
    • 내가 좋아요한 리뷰를 찾아야하기 때문에 인증객체인 @AuthenticationPrincipal UserDetailsImpl userDetail 가 파라미터로 필요하다.
@GetMapping("/api/reviews/likes")
public SuccessResponseDto<List<ReviewResponseDto>> getMyLikeReviews(@AuthenticationPrincipal UserDetailsImpl userDetails,
                                                                    @RequestParam(required = false, defaultValue = "0", value = "page") int pageNo,
                                                                    @RequestParam(required = false, defaultValue = "createdAt", value = "criteria") String criteria) {
    return reviewService.getMyLikeReviews(pageNo, criteria, userDetails.getUser());
}
  • Service
    • Pageable 객체를 만들어서 reviewRepository.findAllByLikeReviewListUser 메서드 파라미터로 전달한다.
    • page 객체로 리턴 받아서 ReviewResponseDto 로 변환한 후, getContent() 로 List만 추출해서 리턴한다.
@Transactional(readOnly = true)
public SuccessResponseDto<List<ReviewResponseDto>> getMyLikeReviews(int pageNo, String criteria, User user) {

    Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.by(Sort.Direction.DESC, criteria));
    Page<ReviewResponseDto> page = reviewRepository.findAllByLikeReviewListUser(user, pageable).map(ReviewResponseDto::from);

    return ResponseUtils.ok(page.getContent());
}
  • Repository
    • findAllByLikeReviewListUser : ReviewLikeReviewList 안에 있는 User 와 일치하는 데이터를 찾는다.
    • 파라미터에 Pageable 을 같이 넣어주면 Page 객체로 리턴할 수 있다.
public interface ReviewRepository extends JpaRepository<Review, Long> {
    Page<Review> findAllByLikeReviewListUser(User user, Pageable pageable);
}

Spring Data JPA 의 Query Method를 통해서 원하는 조건의 데이터를 조회하고 페이지로 보여줄 수 있도록 Page 객체를 만들어주는 것을 확인해보았다. 조건들이 더 복잡해지면 쿼리를 직접 작성해야겠지만, 메서드 하나로 원하는 조건의 데이터를 찾을 수 있다는 것이 참 편하고 신기하다. 다음엔 직접 쿼리를 써서 리턴하는 것을 시도해봐야겠다!

profile
🔥
post-custom-banner

0개의 댓글