JPA | Spring Data JPA + Pageable로 Pagination 구현하기

바다·2024년 6월 27일
0

DataBase

목록 보기
4/6
post-thumbnail

1. Pageable

📌 공식 문서 보러가기

  • org.springframework.data.domain 패키지에 속한 인터페이스
  • 페이지 처리를 하기 위해 가장 중요하다
  • Pageable은 페이지 처리에 필요한 정보를 전달하는 용도의 인터페이스 타입으로, 실제 객체를 구성할 때는 구현체인 PageRequests라는 클래스를 사용한다
  • Pageable을 이용해서 페이지 번호, 페이지당 항목 수, 필요에 따라 정렬 정보를 추가로 지정할 수 있다

2. PageRequest

📌 공식 문서 보러가기

  • Pageable구현 체 중 하나로, 페이지 정보를 생성하는 클래스이다
  • 생성자가 Protected이기 때문에 .of 라는 static 메소드를 이용하여 페이지 정보를 생성한다
  • PageRequest 객체를 생성하고, JpaRepository 의 메소드 파라미터로 전달하면 Page 객체를 반환한다
//0부터 시작하는 페이지 번호(pageNumber)와 개수(pageSize), 정렬이 지정되지 않음
of(int pageNumber, int pageSize)

//0부터 시작하는 페이지 번호(pageNumber)와 개수(pageSize), 정렬 관련 정보
of(int pageNumber, int pageSize, Sort sort)

//0부터 시작하는 페이지 번호(pageNumber)와 개수(pageSize), 정렬의 방향과 정렬 기준 필드들
of(int pageNumber, int pageSize, Sort.Direction direction, String .. props)

3. Page

📌 공식 문서 보러가기

  • org.springframework.data.domain 패키지에 속한 인터페이스
  • Object 목록의 하위 목록으로, 포함된 전체 목록에서 해당 항목의 위치에 대한 정보를 알 수 있다
  • 데이터베이스 쿼리에서 반환되는 대규모 결과 리스트를 페이지가 매겨진 형식으로 표현하고 관리하는 방법을 제공한다
  • 페이지 개체를 사용하여 사용자에게 필요한 레코드 수(총 개수)와 후속 페이지로 이동하기 위한 링크를 표시할 수 있다

4. PageImpl

📌 공식 문서 보러가기

  • Page 인터페이스를 편리하게 구현하여, 페이지 메타데이터를 포함한 쿼리의 결과 페이지를 표현하는 데 사용할 수 있다
  • Spring Data repository 인터페이스와 Pagenation 메커니즘과 함께 사용되어, 페이지화 가능한 방식으로 데이터를 검색하고 조작한다

🔍 구현하기

모든 가게 목록을 조회하는 페이지네이션

1. Controller

  • Parameter로 페이지 번호를 받는다
  • storeService의 getStorePage 메소드의 파리미터로 조회할 페이지 번호를 넘긴다
	@GetMapping
    public ResponseEntity<ResponseMessage<StorePageResponseDto>> getStorePage (
    @RequestParam(value = "page", required = false, defaultValue = "1")Integer page) {
  
        StorePageResponseDto storePageResponseDto = storeService.getStorePage(page);

        ResponseMessage<StorePageResponseDto> responseMessage = ResponseMessage.<StorePageResponseDto>builder()
                .statusCode(HttpStatus.OK.value())
                .message(page + "번 페이지 가게 리스트를 성공적으로 불러왔습니다.")
                .data(storePageResponseDto)
                .build();

        return ResponseEntity.status(HttpStatus.OK).body(responseMessage);
    }

2. Service

  • Pageable 객체를 만들어서 storeRepository의 findAll 메소드의 파라미터로 전달한다
  • Page 객체로 리턴 받아서 StorePageResponseDTO 로 변환하여 반환한다
  • 사용자가 요청한 페이지의 번호가 유효한지, 조회한 Page 객체가 비어있지는 않은지 확인한다
	//페이지네이션
	public StorePageResponseDto getStorePage(Integer page) {
        Pageable pageable = PageRequest.of(page - 1, 5, Sort.by("orderCount").descending());

        Page<Store> storePage = storeRepository.findAll(pageable);

        checkValidatePage(page, storePage);

        return new StorePageResponseDto(page, storePage);
    }
    
    //페이지 유효성 검사
    private static void checkValidatePage(Integer page, Page<Store> storePage) {
        if (storePage.getTotalElements() == 0) {
            throw new StoreException(ErrorType.NOT_FOUND_STORES);
        }
        
        if (page > storePage.getTotalPages() || page < 1) {
            throw new PageException(ErrorType.INVALID_PAGE);
        }
    }

3. Repository

  • findAll로 전체 데이터를 조회한 후, Page 객체로 반환한다
public interface StoreRepository extends JpaRepository<Store, Long> {
    Page<Store> findAll(Pageable pageable);
}

4. ResponseDTO

  • Page 객체 그대로 반환해도 되고, Page 객체에서 .getContent() 메소드를 통해 조회한 List만 반환해도 되지만, 나는 직접 DTO를 만들어서 반환했다
//반환하는 StorePageResponseDTO
@Getter
public class StorePageResponseDto {
    private Integer currentPage;
    private Long totalElements;
    private Integer totalPages;
    private Integer size;
    private List<StoreListResponseDto> storeList;

    public StorePageResponseDto(Integer currentPage, Page<Store> storePageList) {
        this.currentPage = currentPage;
        this.totalElements = storePageList.getTotalElements();
        this.totalPages = storePageList.getTotalPages();
        this.size = storePageList.getSize();
        this.storeList = storePageList.getContent().stream().map(StoreListResponseDto::new).toList();
    }
}

//반환 받은 Store의 데이터를 가공하는 StoreListResponseDTO
@Getter
public class StoreListResponseDto {
    private String name;
    private String address;
    public StoreListResponseDto(Store store) {
        this.name = store.getName();
        this.address = store.getAddress();
    }
}

5. 조회 결과

{
    "statusCode": 200,
    "message": "1번 페이지 가게 리스트를 성공적으로 불러왔습니다.",
    "data": {
        "currentPage": 1,
        "totalElements": 1,
        "totalPages": 1,
        "size": 5,
        "storeList": [
            {
                "name": "멋쟁이 닭발",
                "address": "서울시 강남구"
            }
        ]
    }
}

Spring Data Jpa 에서는 Pageable 만 잘 생성해서 넘겨주면 알아서 Page로 처리해주기 때문에, Pageable 에 페이지 번호와 사이즈 그리고 정렬하는 기준만 잘 입력한다면 페이징 처리가 어렵진 않은 것 같다!

➕ Page에서 다루고 싶은 내용이 있으면, 공식 문서 참고해서 가져다 쓰는 걸 추천한다! 굉장히 친절하기 때문에 구현하고 싶은 건 거의 다 가능 할 것 같다

➕ 참고하면 좋을 페이지 : https://www.baeldung.com/spring-data-jpa-convert-list-page

나중에는 QueryDSL에서 페이지네이션을 어떻게 구현하는지 다뤄볼 예정이다 (과연)

profile
ᴘʜɪʟɪᴘᴘɪᴀɴs 3:14

0개의 댓글