페이지네이션(Pagination)은 대량의 데이터를 나눠서 클라이언트에게 전달할 수 있도록 하는 중요한 기능입니다. 이번 글에서는 Spring Data JPA를 활용하여 페이지네이션을 간단히 구현하는 방법입니다.
1. 페이지네이션이란?
페이지네이션은 데이터베이스로부터 데이터를 한 번에 모두 가져오지 않고, 원하는 크기만큼 나눠 가져오는 기능을 말합니다. 이를 통해 서버 성능을 최적화하고, 클라이언트에서는 필요한 만큼의 데이터를 효율적으로 처리할 수 있습니다.
예시
- 한 번에 1000개의 데이터를 가져오는 대신, 한 페이지에 10개의 데이터를 표시하고, 원하는 페이지 번호를 선택해 데이터를 나눠서 가져옵니다.
2. JPA에서 페이지네이션 기본 구조
Spring Data JPA는 페이지네이션을 지원하는 기본 메서드를 제공합니다. 이 과정은 Pageable 객체와 Page 인터페이스를 사용하여 간단히 처리할 수 있습니다.
기본적인 흐름
- Pageable 객체 생성
- JPA 리포지토리 메서드 호출
- 반환된 데이터를 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. 실행 및 결과 확인
- 서버를 실행합니다.
- API 호출:
GET http://localhost:8080/api/products?page=0&size=5
- 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를 만들 수 있습니다. 정렬과 필터링 같은 추가 기능도 간단히 구현할 수 있으니, 프로젝트에 꼭 활용해 보세요!
'Spring' 카테고리의 다른 글
@Transactional(readOnly = true)란? (4) | 2024.12.13 |
---|---|
JPA (Java Persistence API)란? (0) | 2024.12.10 |
Spring의 7가지 요청 데이터 처리 어노테이션 (0) | 2024.12.07 |
Spring Boot에서 H2 Console 'localhost에서 연결을 거부했습니다' 에러 해결 (1) | 2024.12.06 |
[Spring] Optional의 활용: Repository, Service, 그리고 ResponseEntity의 역할 (0) | 2024.12.06 |