Post

[JPA] Paging

[JPA] Paging

📌 Paging이란?

Paging 은 전체 데이터를 한 번에 조회하는 것이 아닌, 일정 크기의 페이지 단위로 분할하여 조회하는 방법이다. 대량의 데이터를 한 번에 조회할 때 발생하는 메모리 사용량 문제를 해결할 수 있다.

📌 구성 요소

Pageable

Pageable 는 페이징 정보를 추상화한 인터페이스이다. 페이지 번호, 하나의 페이지에 포함될 데이터의 수, 데이터 정렬 기준 및 방향과 같은 정보를 하나의 객체로 묶어서 관리한다.

1
2
3
4
@GetMapping("/users")
public Page<UserDto> getUsers(Pageable pageable) {
    return userService.getUsers(pageable);
}

Spring MVC 는 HTTP request 파라미터의 쿼리를 분석하여 자동으로 Pageable 객체를 만들 수 있다.

ex) /users?page=0&size=10&sort=id,desc

1
2
3
4
5
6
7
@GetMapping("/users")
public Page<UserDto> getUsers(
    @PageableDefault(size = 10, sort = "id", direction = Sort.Direction.DESC)
    Pageable pageable) {
    
    return userService.getUsers(pageable);
}
1
2
3
4
5
6
7
spring:
  data:
    web:
      pageable:
        default-page-size: 10
        max-page-size: 100
        one-indexed-parameters: false

만약 페이징 파라미터의 기본 값을 설정하고 싶다면 @PageableDefault 어노테이션을 사용하거나, 애플리케이션 설정 파일에 전역 설정을 적용한다.

1
2
3
4
5
6
7
8
9
@GetMapping("/dashboard")
public String getDashboard(
    @Qualifier("user") @PageableDefault(size = 5) Pageable userPageable,
    @Qualifier("post") @PageableDefault(size = 10) Pageable postPageable,
    Model model) {
    
    // ...
}

만약 하나의 컨트롤러에 여러 개의 Pageable 객체를 사용해야 하는 경우, @Qualifier 어노테이션을 사용한다. 파라미터 접두사를 보고 올바른 Pageable 객체에 매핑될 수 있도록 한다.

ex) /dashboard?user_page=0&user_size=5&post_page=1&post_size=10

PageRequest

PageRequestPageable 인터페이스의 구현체로, of 메서드를 통해 페이지 정보를 직접 생성한다. page 는 0부터 시작해야 하며, size 는 1 이상이어야 한다.

1
2
// static PageRequest of(int page, int size)
PageRequest request= PageRequest.of(0, 10);

Sort

Sort 는 JPA에서 데이터 정렬을 위한 정보를 캡슐화하는 클래스이다. 여러 정렬 조건을 하나의 객체로 관리할 수 있다. 이후 쿼리로 변환될 때 ORDER BY 절과 상응된다.

1
2
3
4
Pageable pageable = PageRequest.of(0, 10, 
    Sort.by("name").ascending().and(Sort.by("createdAt").descending()));

Page<User> users = userRepository.findAll(pageable);

PageRequest 와 같이 페이징과 정렬을 동시에 처리할 때 같이 사용될 수 있다.

📌 페이징 리턴 타입

Page

Page 객체는 다양한 형태의 페이징 정보를 제공한다. 전체 원소의 수와 전체 페이지 수를 계산한다.

1
2
SELECT  FROM  ORDER BY  LIMIT :size OFFSET :offset
SELECT COUNT(*) FROM 

내부적으로 두 개의 쿼리를 실행하는데, 데이터를 조회하는 쿼리, 데이터 전체 개수를 조회하는 쿼리를 실행한다.

1
2
3
4
5
6
@Transactional(readOnly = true)
public Page<UserDto> findUsers(Pageable pageable) {
    Page<User> userPage = userRepository.findAll(pageable);
    
    return userPage.map(UserDto::new);
}

map 메서드를 사용하면 Page<Entity>Page<DTO> 로 쉽게 변환할 수 있다. 변환 시 페이지네이션 관련 메타데이터를 그대로 유지된다.

Slice

Slice 객체는 전체 데이터 개수를 계산하지 않고 다음 페이지의 존재 여부만 확인한다. Page 객체는 전체 페이지 수를 조회하는 COUNT 쿼리를 실행하지만 Slice 객체를 실행하지 않는다. 즉, 조회 쿼리 한 번만 실행한다.

대량의 데이터에서 COUNT 쿼리는 병목 현상이 발생할 수 있다.

List

List 타입으로 리턴되면, 페이징 메타데이터 없이 단순히 데이터만 리턴한다.


  • Page 객체는 페이지 번호, 전체 데이터 수를 사용해야 할 때, 특정 페이지로 이동해야 할 때 유용하다.
  • Slice 객체는 무한 스크롤과 같이 다음 데이터의 유무만 확인할 때 유용하다.
  • List 타입은 고정된 개수의 데이터만 사용할 때, 페이징 메타데이터가 필요 없을 때 유용하다.
This post is licensed under CC BY 4.0 by the author.