게시글이 수천 개가 있는데, 다 가져오면 서버나 사이트에 부하가 오게 된다. 이를 해결하기 위해 페이징을 사용하여 깔끔하게 가져오는 방법을 구현해 보려고 한다.
[페이징이란]
데이터를 페이지 단위로 잘라서 가져오는 것을 말한다. 예를 들어, 총 100개의 데이터가 있을 때 이를 10개씩 10페이지로 나누어 전달하는 방식이다.
Spring Data JPA에서는 public interface Page<T> extends Slice <T> 형태로 정의되어 있으며, 단순 데이터 목록뿐 아니라 전체 페이지 수, 전체 항목 수, 현재 페이지 등의 정보도 함께 제공한다.
[페이징 주요 구성 클래스]
Page <T>
실제 페이징 결과가 담기는 결과 객체를 말한다.
List <T> 형태의 데이터 목록뿐 아니라 아래와 같은 부가 정보도 함께 포함된다.
| 항목 | 메서드 형태 |
| 전체 페이지 수 | getTotalPages() |
| 전체 데이터 개수 | getTotalElements() |
| 현재 페이지 번호 | getNumber() |
| 페이지 크기 (한 페이지 크기) | getSize() |
| 정렬 정보 | getSort() |
| 첫 페이지 여부 | isFirst() |
| 마지막 페이지 여부 | isLast() |
| 현재 페이지 데이터 목록 | getContent() -> List<T> |
| 현재 페이지에 데이터가 있는지 | hasContent() |
Pageable
페이징을 위한 요청 정보 인터페이스이다. -> public interface Pageable
'몇 페이지를 요청했는지' '한 페이지에 몇 개를 보여줄 것인지' 등의 조건을 담고 있다.
PageRequest
Pageble의 구현체로 페이지 번호, 사이즈(한 페이지에 보여줄 개수)를 지정해서 생성할 수 있으면 정렬 조건도 설정할 수 있다.
[예시]
Controller
서비스에서 가져온 데이터를 클라이언트에 응답값으로 보내기 위해 가공한다.
getReadingListByFilter를 통해 결과 객체 Page <ReadingList>를 받아온 후 정보들을 Map에 담아 리턴한다.
Page<ReadingList> readingPage = readingListService.getReadingListByFilter(userId, tabType, page, size);
//웅답값 구성
Map<String, Object> response = new LinkedHashMap<>();
response.put("success", true);
response.put("tabType", tabType);
Map<String, Object> pageInfo = new LinkedHashMap<>();
pageInfo.put("size", readingPage.getSize());
pageInfo.put("number", readingPage.getNumber());
pageInfo.put("totalElements", readingPage.getTotalElements());
pageInfo.put("totalPages", readingPage.getTotalPages());
response.put("page", pageInfo);
Service
Controller에 사용할 페이징 결과를 생성하는 역할을 한다.
페이징 처리를 위해 Pageable 인터페이스를 활용하며, 그 구현체인 PageRequest를 사용해 페이지 번호, 크기, 정렬 기준 등을 지정한다.
* PageRequest.of(page, size)에서 page는 0부터 시작한다. 따라서 클라이언트에서 1페이지라고 요청했다면 서버는 page-1로 처리해야 한다.
@Transactional
public Page<ReadingList> getReadingListByFilter(Integer userId, Integer tabType, int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
BookStatus status = null;
switch (tabType) {
case 1 -> status = BookStatus.IN_PROGRESS;
case 2 -> status = BookStatus.NOT_STARTED;
case 3 -> status = BookStatus.COMPLETED;
case 4 -> status = BookStatus.INTERESTED;
}
if (status == null) {
return readingListRepository.findByUserId(userId, pageable);
} else {
return readingListRepository.findByUserIdAndBookStatus(userId, status, pageable);
}
}
Repository
Pageable 파라미터를 받아 페이지 번호, 크기, 정렬 기준을 동적으로 지정할 수 있다.
@Repository
public interface ReadingListRepository extends JpaRepository<ReadingList, Long> {
// 유져 아이디 별 페이지 필터링
Page<ReadingList> findByUserId(Integer userId, Pageable pageable);
// 유저ID & 독서상태에따라 페이지 필터링
Page<ReadingList> findByUserIdAndBookStatus(Integer userId, BookStatus bookStatus, Pageable pageable);
}
[추가 수정]
controller에서 page 와 size를 pageable 객체로 받을 수 있다고 하여 controller와 service 를 수정 해보았다.
Controller
//리스트 가져오기
@GetMapping("/readingList")
public ResponseEntity<?> getReadingList(
@RequestParam("userId") Integer userId,
@RequestParam("tabType") Integer tabType,
@PageableDefault(size = 10, page = 0) Pageable pageable) {
try {
// 기본 정렬이 없으면 bookTitle 오름차순 설정
if (pageable.getSort().isUnsorted()) {
pageable = PageRequest.of(
pageable.getPageNumber(),
pageable.getPageSize(),
Sort.by("bookTitle").ascending()
);
}
Page<ReadingList> readingPage = readingListService.getReadingListByFilter(userId, tabType, pageable);
Map<String, Object> response = new LinkedHashMap<>();
response.put("success", true);
response.put("tabType", tabType);
Map<String, Object> pageInfo = new LinkedHashMap<>();
pageInfo.put("size", readingPage.getSize());
pageInfo.put("number", readingPage.getNumber());
pageInfo.put("totalElements", readingPage.getTotalElements());
pageInfo.put("totalPages", readingPage.getTotalPages());
response.put("page", pageInfo);
List<Map<String, Object>> readingList = ReadingListReaponseDto.convertToDtoList(readingPage.getContent());
response.put("readingList", readingList);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.internalServerError().body(ApiResponse.failure(e.getMessage()));
}
}
Service
//책 목록 검색하기
@Transactional
public Page<ReadingList> getReadingListByFilter(Integer userId, Integer tabType, Pageable pageable) {
BookStatus status = null;
switch (tabType) {
case 1 -> status = BookStatus.IN_PROGRESS;
case 2 -> status = BookStatus.NOT_STARTED;
case 3 -> status = BookStatus.COMPLETED;
case 4 -> status = BookStatus.INTERESTED;
}
if (status == null) {
return readingListRepository.findByUserId(userId, pageable);
} else {
return readingListRepository.findByUserIdAndBookStatus(userId, status, pageable);
}
}'spring 스프링' 카테고리의 다른 글
| iOS 업데이트 자동 확인: 웹 크롤링으로 이메일 알림 만들기 (0) | 2025.08.21 |
|---|---|
| Spring CORS 설정: WebConfig 작성부터 wildcard '*', withCredentials 문제 해결까지 (1) | 2025.06.26 |
| The method builder() is undefined for the type 오류 (해결2) (0) | 2025.04.05 |
| The method builder() is undefined for the type 오류 (해결1) (0) | 2025.04.04 |
| 왜 setter를 지양해야 하는가 (0) | 2025.04.02 |