[TIL] Spring Boot Pageable, PageableDefault 어노테이션

2025-04-22 TIL

📝 TIL (Today I Learned)
🔗 원본 이슈: #46
📅 작성일: 2025-04-22
🔄 최종 수정: 2025년 05월 02일


🍀 새롭게 배운 것

  • Spring Boot Paging

    프로젝트에서 커뮤니티 부분을 구현하며 게시글 목록을 조회할 때, 어떤 식으로 반환해야하는지 찾아보다 Pagination을 알게 되었다.

    • Spring JPA 라이브러리의 Pageable을 이용하는 방법을 사용했다.

      • Pageable
        • Spring JPA에서 DB 쿼리에 쉽고 유연하게 limit쿼리를 사용할 수 있게 해준다.
        • 특히 JPA를 사용할 때, 자동으로 Pageable 타입의 변수를 넘겨주면 JPA가 DB에 접근해 데이터를 가져올 때 자동으로 limit조건을 붙여 데이터를 가져온다.
    • 참고 블로그

PageableDefault 어노테이션의 역할

@PageableDefault(size = 10) 어노테이션은 다음과 같은 역할을 합니다:

  1. 기본 페이지 크기 설정: 클라이언트가 별도의 페이지 크기(size 파라미터)를 지정하지 않았을 때 기본적으로 페이지당 10개의 항목을 반환하도록 설정합니다.

  2. 기본값 제공: 클라이언트가 페이징 관련 파라미터를 명시적으로 지정하지 않았을 때 사용할 기본값을 제공합니다.

  3. 컨트롤러 메서드 간소화: 매번 페이징 관련 파라미터를 검증하고 기본값을 설정하는 코드를 작성하지 않아도 됩니다.

PageableDefault의 주요 속성

@PageableDefault는 다음과 같은 속성을 지원합니다:

@PageableDefault(
    size = 10,       // 페이지당 항목 수 (기본값: 10)
    page = 0,        // 시작 페이지 번호 (기본값: 0, 첫 페이지는 0부터 시작)
    sort = {},       // 정렬 기준 필드명
    direction = Sort.Direction.ASC  // 정렬 방향 (기본값: 오름차순)
)

Pageable 관련 알아야 할 개념들

1. Pageable 인터페이스

Pageable은 페이징 정보를 캡슐화하는 Spring Data의 인터페이스입니다. 주요 메서드는 다음과 같습니다:

  • getPageNumber(): 현재 페이지 번호 (0부터 시작)
  • getPageSize(): 페이지 크기
  • getSort(): 정렬 정보
  • getOffset(): 전체 결과에서의 오프셋 (첫 항목의 위치)
  • next(), previous(), first(): 다음/이전/첫 페이지의 Pageable 객체 생성

2. Page 인터페이스

Page<T>는 페이징된 결과를 담는 인터페이스로, 다음과 같은 메서드를 제공합니다:

  • getContent(): 현재 페이지의 내용(리스트)
  • getTotalElements(): 전체 항목 수
  • getTotalPages(): 전체 페이지 수
  • getNumber(): 현재 페이지 번호 (0부터 시작)
  • getSize(): 페이지 크기
  • hasNext(), hasPrevious(): 다음/이전 페이지 존재 여부

3. 요청 파라미터

클라이언트는 다음과 같은 요청 파라미터를 통해 페이징 정보를 전달할 수 있습니다:

  • page: 페이지 번호 (0부터 시작)
  • size: 페이지 크기
  • sort: 정렬 기준과 방향 (예: sort=createdAt,desc)

4. Sort 클래스

Sort는 정렬 정보를 나타내는 클래스로, 다음과 같은 방식으로 사용됩니다:

Sort.by(Direction.DESC, "createdAt")

여러 필드로 정렬할 때:

Sort.by(
    Sort.Order.desc("createdAt"),
    Sort.Order.asc("title")
)

5. PageRequest 클래스

PageRequestPageable의 구현체로, 다음과 같이 생성할 수 있습니다:

PageRequest.of(0, 10, Sort.by(Direction.DESC, "createdAt"))

6. JpaRepository에서의 사용

JpaRepository는 페이징을 지원하는 메서드를 제공합니다:

Page<Entity> findAll(Pageable pageable);
Page<Entity> findByField(String value, Pageable pageable);

코드에서 보이는 findAllByOrderByCreatedAtDesc(Pageable pageable)와 같은 메서드는 이러한 패턴을 따르고 있습니다.

7. 커스텀 쿼리와 페이징

@Query 어노테이션으로 작성한 JPQL 쿼리에서도 Pageable 파라미터를 사용할 수 있습니다:

@Query("SELECT p FROM Post p WHERE p.title LIKE %:keyword%")
Page<Post> searchByTitle(@Param("keyword") String keyword, Pageable pageable);

실제 동작 방식

제공된 코드에서 getPosts 메서드는 다음과 같이 동작합니다:

  1. 클라이언트가 /posts?page=1&size=20&sort=viewCount,desc와 같은 요청을 보내면, Spring은 이 파라미터를 Pageable 객체로 변환합니다.

  2. 만약 클라이언트가 size 파라미터를 지정하지 않았다면, @PageableDefault(size = 10)에 따라 기본값 10이 사용됩니다.

  3. PostServicegetPosts 메서드에 이 Pageable 객체가 전달되고, 이것은 다시 PostRepository의 적절한 메서드로 전달됩니다.

  4. 데이터베이스 쿼리는 LIMITOFFSET 절을 포함하여 페이징을 구현합니다. 예를 들어, page=1&size=10이라면 SQL에서는 LIMIT 10 OFFSET 10과 같이 변환됩니다.

  5. 결과는 Page<PostDto.ListResponse> 객체로 반환되며, 이 객체는 현재 페이지의 내용뿐만 아니라 전체 페이지 수, 전체 항목 수 등의 메타데이터도 포함합니다.

예제 응답 형태

API 응답은 다음과 같은 JSON 형태로 반환됩니다:

{
  "content": [
    { "id": 1, "title": "게시글 1", ... },
    { "id": 2, "title": "게시글 2", ... },
    ...
  ],
  "pageable": {
    "sort": {
      "empty": false,
      "sorted": true,
      "unsorted": false
    },
    "offset": 10,
    "pageNumber": 1,
    "pageSize": 10,
    "paged": true,
    "unpaged": false
  },
  "last": false,
  "totalElements": 42,
  "totalPages": 5,
  "size": 10,
  "number": 1,
  "sort": {
    "empty": false,
    "sorted": true,
    "unsorted": false
  },
  "first": false,
  "numberOfElements": 10,
  "empty": false
}

최적화 고려사항

  1. 카운트 쿼리 최적화: 항목이 많은 경우 totalElements를 계산하기 위한 COUNT 쿼리가 성능에 영향을 줄 수 있습니다. 필요에 따라 countQuery 최적화를 고려해야 합니다.

  2. 인덱스 활용: 정렬 및 필터링에 사용되는 필드에 적절한 인덱스를 생성해야 합니다.

  3. 페이지 사이즈 제한: 클라이언트가 지나치게 큰 페이지 크기를 요청하지 못하도록 제한할 수 있습니다.

Pageable 관련 추가 정보

  1. Pageable 파라미터를 사용하는 메서드는 자동으로 spring-data-commonsPageableHandlerMethodArgumentResolver에 의해 처리됩니다.

  2. 애플리케이션 전체에서 페이징 기본값을 변경하려면 application.properties 또는 application.yml에서 설정할 수 있습니다:

spring:
  data:
    web:
      pageable:
        default-page-size: 20
        max-page-size: 100
        one-indexed-parameters: true # 페이지 번호를 1부터 시작하도록 설정
  1. RESTful API에서 페이징 정보를 전달하는 다른 방법으로는 헤더 방식, 경로 변수 방식 등이 있지만, Spring Data는 기본적으로 쿼리 파라미터 방식을 사용합니다.