Spring

[JPA] JPA로 페이지네이션 구현하기

0and24 2025. 1. 4. 18:48

페이지네이션(Pagination)은 대량의 데이터를 나눠서 클라이언트에게 전달할 수 있도록 하는 중요한 기능입니다. 이번 글에서는 Spring Data JPA를 활용하여 페이지네이션을 간단히 구현하는 방법입니다.

1. 페이지네이션이란?

페이지네이션은 데이터베이스로부터 데이터를 한 번에 모두 가져오지 않고, 원하는 크기만큼 나눠 가져오는 기능을 말합니다. 이를 통해 서버 성능을 최적화하고, 클라이언트에서는 필요한 만큼의 데이터를 효율적으로 처리할 수 있습니다.

예시 사진

예시

  • 한 번에 1000개의 데이터를 가져오는 대신, 한 페이지에 10개의 데이터를 표시하고, 원하는 페이지 번호를 선택해 데이터를 나눠서 가져옵니다.

2. JPA에서 페이지네이션 기본 구조

Spring Data JPA는 페이지네이션을 지원하는 기본 메서드를 제공합니다. 이 과정은 Pageable 객체와 Page 인터페이스를 사용하여 간단히 처리할 수 있습니다.

기본적인 흐름

  1. Pageable 객체 생성
  2. JPA 리포지토리 메서드 호출
  3. 반환된 데이터를 DTO에 담아 클라이언트에 전달 (DTO에 담아 반환하지 않아도 작동하지만 직렬화 경고 발생)

3. 구현 방법

3.1. 프로젝트 세팅

아래와 같이 의존성을 추가합니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

3.2. 데이터베이스 설정

application.yml 파일에 H2 데이터베이스를 설정합니다.

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

3.3. 엔티티 생성

페이지네이션 대상이 될 엔티티를 작성합니다.

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private int price;

    // Getter, Setter, Constructor
}

3.4. DTO 작성

클라이언트에 반환할 데이터를 담을 DTO를 작성합니다.

public class ProductDto {
    private Long id;
    private String name;
    private int price;

    public ProductDto(Long id, String name, int price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    // Getter, Setter
}

3.5. JPA 리포지토리 작성

Spring Data JPA는 JpaRepository 인터페이스를 통해 페이지네이션 메서드를 제공합니다.

public interface ProductRepository extends JpaRepository<Product, Long> {
}

3.6. 서비스 계층 작성

페이지네이션 구현 로직을 서비스 계층에서 작성하며, 엔티티를 DTO로 변환하여 반환합니다.

@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public Map<String, Object> getProducts(Pageable pageable) {
        Page<Product> productPage = productRepository.findAll(pageable);

        List<ProductDto> content = productPage.getContent().stream()
                .map(product -> new ProductDto(product.getId(), product.getName(), product.getPrice()))
                .collect(Collectors.toList());

        Map<String, Object> response = new HashMap<>();
        response.put("content", content);
        response.put("currentPage", productPage.getNumber() + 1);
        response.put("totalItems", productPage.getTotalElements());
        response.put("totalPages", productPage.getTotalPages());
        response.put("pageSize", productPage.getSize());

        return response;
    }
}

3.7. 컨트롤러 작성

클라이언트의 요청을 받아 DTO로 변환된 페이지네이션 결과를 반환합니다.

@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping
    public ResponseEntity<Map<String, Object>> getProducts(
            @RequestParam(name = "page", defaultValue = "0") int page,
            @RequestParam(name = "size", defaultValue = "10") int size
    ) {
        Pageable pageable = PageRequest.of(page, size);
        Map<String, Object> response = productService.getProducts(pageable);
        return ResponseEntity.ok(response);
    }
}

4. 실행 및 결과 확인

  1. 서버를 실행합니다.
  2. API 호출:
    GET http://localhost:8080/api/products?page=0&size=5
    
  3. JSON 응답 예시:
    {
      "content": [
        {"id": 1, "name": "Product 1", "price": 1000},
        {"id": 2, "name": "Product 2", "price": 2000}
      ],
      "currentPage": 1,
      "totalItems": 50,
      "totalPages": 10,
      "pageSize": 5
    }
    

5. 추가 기능

5.1. 정렬

정렬 기능을 추가하려면 PageRequest 생성 시 Sort 객체를 추가합니다.

Pageable pageable = PageRequest.of(page, size, Sort.by("price").descending());

5.2. 필터링

필터링은 JPQL 또는 Specification을 사용하여 구현할 수 있습니다.

public Page<Product> findByNameContaining(String keyword, Pageable pageable) {
    return productRepository.findByNameContaining(keyword, pageable);
}

6. 결론

Spring Data JPA를 사용하면 페이지네이션 기능을 매우 쉽게 구현할 수 있습니다. Pageable과 Page 객체를 활용하면 성능 최적화와 사용자 경험을 모두 만족시키는 API를 만들 수 있습니다. 정렬과 필터링 같은 추가 기능도 간단히 구현할 수 있으니, 프로젝트에 꼭 활용해 보세요!