프로젝트가 3차 스프린트로 들어서면서 1, 2차 스프린트 때 클라이언트 파트 팀원의 리소스를 고려해 구현하지 않았던 페이지네이션을 이번 스프린트에서 적용하기로 결정했습니다. 이를 계기로 페이지네이션이 적용되어 있지 않은 API에 기능을 도입하는 과정에서 배운 점을 정리하고, 개념과 구현 방법을 함께 공유하고자 이 글을 작성했습니다.
페이징 처리(Pagination)는 데이터를 여러 페이지로 나누어 클라이언트에 제공하는 방법입니다. 대량의 데이터를 한 번에 로드하지 않고 필요한 부분만 요청하여 성능을 최적화할 수 있습니다.
페이징 처리의 주요 목표는 다음과 같습니다:
페이징 처리 시 클라이언트는 보통 다음 정보를 요청하거나 받을 수 있습니다:
page: 요청할 페이지 번호.size: 한 페이지에 포함될 항목 수.sort: 정렬 기준.result: 요청된 페이지의 데이터.totalPages: 전체 페이지 수.totalCount: 전체 항목 수.hasNext: 다음 페이지 존재 여부.Spring Boot는 페이징 처리를 쉽게 구현할 수 있도록 Pageable과 Page 인터페이스를 제공합니다.
Pageable이란?Pageable은 다음 정보를 포함합니다:page): 0부터 시작.size): 기본값 또는 클라이언트 요청값.sort).Page란?Page는 다음 정보를 포함합니다:totalPages).totalCount).hasNext).Pageable 객체를 Controller 메서드에 추가하여 클라이언트 요청을 처리할 수 있습니다.
@GetMapping("/home")
public ResponseEntity<SuccessResponse<HomeAnnouncementsResponseDto>> getAnnouncements(
@RequestParam(value = "sortBy", required = false, defaultValue = "deadlineSoon") String sortBy,
@PageableDefault(size = 10) Pageable pageable) {
HomeAnnouncementsResponseDto announcements = homeService.getAnnouncements(sortBy, pageable);
return ResponseEntity.ok(SuccessResponse.of(SUCCESS_GET_ANNOUNCEMENTS, announcements));
}
Pageable 객체 생성:
@PageableDefault(size = 10) 어노테이션을 사용하여 기본 페이지 크기를 설정합니다.page, size, sort 파라미터를 전달하면 Pageable이 자동 생성됩니다.요청 예시:
GET /home?sortBy=mostViewed&page=1&size=5
Pageable 자동 처리:
page: 0부터 시작.size: 요청된 값(또는 기본값).sort: 정렬 기준.Service 계층에서는 Pageable 객체를 활용하여 페이징된 데이터를 처리하고 DTO로 반환합니다.
public HomeAnnouncementsResponseDto getAnnouncements(String sortBy, Pageable pageable) {
Page<Tuple> pagedAnnouncements = internshipRepository.findFilteredInternships(sortBy, pageable);
if (pagedAnnouncements.isEmpty()) {
return HomeAnnouncementsResponseDto.of(0, 0, false, List.of());
}
List<HomeResponseDto> responseDtos = pagedAnnouncements.getContent().stream()
.map(tuple -> HomeResponseDto.of(tuple))
.toList();
return HomeAnnouncementsResponseDto.of(
pagedAnnouncements.getTotalPages(),
(int) pagedAnnouncements.getTotalElements(),
pagedAnnouncements.hasNext(),
responseDtos
);
}
Page<Tuple>를 반환하는 Repository 메서드 호출.Page.getContent()를 활용하여 현재 페이지의 데이터를 변환.totalPages, totalCount, hasNext를 포함한 DTO를 반환.Spring Data JPA와 QueryDSL은 페이징 처리를 지원합니다. Pageable을 활용하여 데이터베이스에서 필요한 데이터를 가져옵니다.
public Page<Tuple> findFilteredInternships(String sortBy, Pageable pageable) {
List<Tuple> content = jpaQueryFactory
.select(internshipAnnouncement, scrap.id, scrap.color)
.from(internshipAnnouncement)
.where(getFilters())
.orderBy(getSortOrder(sortBy))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
JPAQuery<Long> countQuery = jpaQueryFactory
.select(internshipAnnouncement.count())
.from(internshipAnnouncement)
.where(getFilters());
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
}
fetch)와 개수 쿼리 (countQuery)를 분리하여 효율적으로 처리.PageableExecutionUtils.getPage()를 사용하여 페이징된 데이터를 반환.GET /home?sortBy=mostViewed&page=1&size=10
{
"totalPages": 5,
"totalCount": 50,
"hasNext": true,
"result": [ ... ]
}
Spring Boot의 Pageable과 Page는 페이징 처리에 필요한 개념과 구현을 모두 지원합니다. 이 문서를 통해 페이징 처리의 개념을 이해하고, 실제 프로젝트에 적용하는데 도움이 되었으면 좋겠습니다.