Files
spring-boot-examples/docs/jpa/13_paging.md
2025-04-08 19:56:24 +09:00

7.2 KiB

JPA에서 페이지 처리는 대량의 데이터를 효율적으로 조회하고 사용자에게 필요한 만큼만 보여주기 위해 사용되는 기능입니다. 특히 웹 애플리케이션에서 목록 조회 시, 모든 데이터를 한 번에 가져오는 대신 페이징을 통해 데이터를 분할해서 처리하는 것이 일반적입니다. Spring Data JPA는 이를 간편하게 구현할 수 있도록 PagingAndSortingRepositoryPageable 인터페이스를 제공합니다. 아래에서 JPA의 페이지 처리에 대해 자세히 설명하겠습니다.


페이지 처리란?

페이지 처리는 데이터베이스에서 조회한 결과를 작은 단위(페이지)로 나누어 반환하는 기법입니다. 예를 들어, 게시판에 1,000개의 글이 있다면 한 페이지에 10개씩 보여주고, 사용자가 원하는 페이지를 선택해 해당 데이터만 조회합니다. 이를 통해 성능 최적화와 사용자 경험 개선을 동시에 달성할 수 있습니다.


Spring Data JPA에서의 페이지 처리

Spring Data JPA는 페이지 처리를 위해 다음과 같은 주요 구성 요소를 제공합니다:

  1. Pageable 인터페이스

    • 페이지 번호, 페이지 크기, 정렬 조건 등을 정의합니다.
    • PageRequest 클래스를 통해 구현체를 생성합니다.
    • 예: PageRequest.of(0, 10)는 첫 번째 페이지(0부터 시작)에 10개의 데이터를 요청합니다.
  2. Page 반환 타입

    • 조회된 데이터와 함께 페이징 관련 메타데이터(총 페이지 수, 총 데이터 수 등)를 제공합니다.
    • 주요 메서드:
      • getContent(): 현재 페이지의 데이터 목록
      • getTotalElements(): 전체 데이터 개수
      • getTotalPages(): 전체 페이지 수
      • getNumber(): 현재 페이지 번호
  3. PagingAndSortingRepository

    • Spring Data JPA에서 제공하는 기본 레포지토리 인터페이스로, 페이징과 정렬 기능을 지원합니다.
    • findAll(Pageable pageable) 메서드를 통해 페이징된 데이터를 조회할 수 있습니다.

페이지 처리 구현 방법

Spring Boot와 Spring Data JPA를 기준으로 페이지 처리 구현 과정을 단계별로 설명하겠습니다.

1. 엔티티와 레포지토리 정의

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Post {
    @Id
    private Long id;
    private String title;
    private String content;

    // Getter, Setter
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface PostRepository extends JpaRepository<Post, Long> {
    Page<Post> findAll(Pageable pageable); // 기본 제공 메서드와 동일
}

2. 서비스 계층에서 페이지 처리

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

@Service
public class PostService {

    private final PostRepository postRepository;

    public PostService(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    public Page<Post> getPosts(int page, int size) {
        Pageable pageable = PageRequest.of(page, size); // 페이지 번호(0부터), 페이지 크기
        return postRepository.findAll(pageable);
    }
}

3. 컨트롤러에서 결과 반환

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.data.domain.Page;

@RestController
public class PostController {

    private final PostService postService;

    public PostController(PostService postService) {
        this.postService = postService;
    }

    @GetMapping("/posts")
    public Page<Post> getPosts(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return postService.getPosts(page, size);
    }
}

4. 실행 결과

  • 요청: GET /posts?page=0&size=10
  • 응답: JSON 형태로 현재 페이지의 데이터와 페이징 정보가 반환됩니다.
    {
      "content": [
        {"id": 1, "title": "Post 1", "content": "Content 1"},
        {"id": 2, "title": "Post 2", "content": "Content 2"},
        ...
      ],
      "pageable": {
        "pageNumber": 0,
        "pageSize": 10,
        ...
      },
      "totalElements": 100,
      "totalPages": 10,
      "number": 0
    }
    

정렬 추가하기

Pageable은 정렬(Sorting)도 지원합니다. 예를 들어, 제목 기준으로 오름차순 정렬을 추가하려면:

Pageable pageable = PageRequest.of(0, 10, Sort.by("title").ascending());
Page<Post> posts = postRepository.findAll(pageable);

컨트롤러에서 요청 파라미터로 정렬 조건을 받을 수도 있습니다:

@GetMapping("/posts")
public Page<Post> getPosts(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(defaultValue = "title") String sortBy) {
    Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy).ascending());
    return postService.getPosts(pageable);
}

커스텀 쿼리와 페이지 처리

특정 조건으로 데이터를 조회하면서 페이징을 적용하려면 @Query 어노테이션이나 메서드 이름을 사용합니다.

public interface PostRepository extends JpaRepository<Post, Long> {
    Page<Post> findByTitleContaining(String keyword, Pageable pageable);

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

장점

  • 성능 최적화: 필요한 데이터만 조회하므로 메모리와 DB 부하를 줄입니다.
  • 사용자 경험 개선: 대량 데이터를 한 번에 로드하지 않고, 페이지 단위로 제공합니다.
  • 유연성: 정렬, 필터링 등과 결합해 다양한 요구사항을 처리할 수 있습니다.

주의점

  • 페이지 번호: Spring Data JPA는 페이지 번호가 0부터 시작합니다(예: 0은 첫 페이지).
  • 쿼리 최적화: 페이징 쿼리는 OFFSETLIMIT을 사용하므로, 데이터가 많아질수록 성능 저하가 발생할 수 있습니다. 이 경우 인덱스 사용이나 커서 기반 페이징을 고려해야 합니다.
  • 총 개수 계산: Page 객체는 totalElements를 계산하기 위해 별도의 COUNT 쿼리를 실행합니다. 성능에 민감한 경우 Slice를 사용하는 것도 대안입니다(totalElementstotalPages를 제공하지 않음).

결론

JPA의 페이지 처리는 Spring Data JPA를 통해 매우 간단하면서도 강력하게 구현할 수 있습니다. PageablePage를 활용하면 데이터 조회를 효율적으로 관리하고, 웹 애플리케이션에서 필수적인 페이징 기능을 손쉽게 제공할 수 있습니다. 대규모 데이터를 다룰 때 필수적인 이 기능을 잘 활용하면 성능과 사용자 경험 모두를 만족시킬 수 있습니다!