페이지네이션
데이터베이스에 회원 정보가 100건이 존재한다고 가정한다. 클라이언트가 이 많은 데이터를 모두 한 번에 요청하는 것이 아니라 특정 개수 만큼 나누어서 요청할 수 있다. 서버에 데이터를 요청할 때 클라이언트의 입장과 서버의 입장에서 특정 정렬 기준에 따라 지정한 개수의 데이터를 요청하고 응답하는 것이 필요하다.
오프셋 방식
- 가져와야 되는 데이터까지 오프셋 개수 만큼 카운팅해서 찾아가는 방식이다.
- 데이터베이스의 offset 쿼리를 사용하여 페이지 단위로 데이터를 응답한다.
- 직접 찾아가야 되므로 시간이 걸린다.
- 이미 읽었던 데이터를 다시 읽는 과정이 포함되어서 데이터가 많아지면 성능상 좋지 않다.
- 데이터가 100만 건 ~ 200만 건일 때 클라이언트가 심하게 느리다고 할 정도는 아니다.
- 데이터 중복 문제: 앞쪽에 최신 데이터가 들어오면 데이터가 뒤로 밀리므로 중복이 발생한다.
- JPA에서는 Pageable로 쉽게 구현할 수 있다.
- 인덱스 지정을 해 두면 성능이 빨라진다.
커서 방식
[Spring Data JPA] 커서 기반 페이지네이션(Pagination)
PagingAndSortingRepository
먼저 Member 클래스가 도메인 엔티티로 존재한다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
@Id
private Long memberId;
private String email;
private String name;
private String phone;
}
Member에 엑세스 하기 위한 MemberRepository이다.
PagingAndSortingRepository를 상속하면 페이징 및 정렬을 할 수 있는 findAll(Pageable pageable)와 findAll(Sort sort)메서드를 사용할 수 있다.
public interface MemberRepository extends PagingAndSortingRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
PageRequest
클라이언트로부터 컨트롤러의 핸들러 메서드가 page와 size 값을 입력받고,
서비스로 전달되도록 한다.
//MemberController.java
@GetMapping
public ResponseEntity getMembers(@Positive @RequestParam int page,
@Positive @RequestParam int size) {
List<Member> members = memberService.findMembers(page, size);
List<MemberResponseDto> response = mapper.membersToMemberResponseDtos(members);
return new ResponseEntity<>(response, HttpStatus.OK);
}
PagingAndSortingRepository의 메서드 findAll(Pageable pageable) 파라미터에 넣을 Pageable를 정의해야 한다. Pageable은 인터페이스로, 구현체로 PageRequest를 갖는다.
PageRequest.of(page, size) 메서드로 생성할 수 있다.
오버로딩을 통해 sort도 함께 사용할 수 있다. 파라미터로 엔티티 이름을 넣는다.
보통 데이터를 조회할 때 최신 데이터부터 필요한 경우가 많으므로 descending()을 적용했다.
- page: 조회하고자 하는 페이지 번호 (페이지는 0부터 시작하므로 - 1을 해야 한다.
- size: 페이지 당 로우 개수
//MemberService.java
public List<Member> findMembers(int page, int size) {
PageRequest pr = PageRequest.of(page - 1, size, Sort.by("memberId").descending());
return memberRepository.findAll(pr).getContent();
}
PageRequest 인스턴스를 PagingAndSortingRepository의 findAll() 메서드의 파라미터로 넣는다.
findAll()은 Page 객체를 리턴하는데, Page는 Slice 인터페이스를 사용하므로 getContent() 메서드를 사용할 수 있다. getContent()를 사용하면 List로 리턴 할 수 있다.
페이지 정보를 함께 출력하려면 아래 DTO를 만들어서 page 메서드를 사용하면 된다.
public class PageInfoResponseDtos {
private List data;
private PageInfo pageInfo;
public PageInfoResponseDtos(List data, Page page) {
this.data = data;
this.pageInfo =
new PageInfo(page.getNumber() + 1, page.getSize(),
page.getTotalElements(), page.getTotalPages());
}
}
📄 PageInfo
@AllArgsConstructor
@Getter
public class PageInfo {
private int page; //페이지 번호
private int size; //한 페이지에 포함되는 데이터 row의 개수
private long totalElements; //테이블에 저장되어 있는 데이터의 총 개수
private int totalPages; //총 페이지 수
}
📄 MemberController
@GetMapping
public ResponseEntity getMembers(@Positive @RequestParam int page,
@Positive @RequestParam int size) {
//페이지네이션 적용
Page<Member> pages = memberService.findMembers(page, size);
List<MemberResponseDto> response = mapper.membersToMemberResponseDtos(pages.getContent());
return new ResponseEntity<>(new MemberPageInfoResponseDto(response, pages), HttpStatus.OK);
}
📄 MemberService
public Member findMember(long memberId) {
return findVerifiedMember(memberId);
}
public Page<Member> findMembers(int page, int size) {
//페이지네이션 적용
PageRequest pr = PageRequest.of(page - 1, size, Sort.by("memberId").descending());
return memberRepository.findAll(pr);
}
PageImpl
/**
* PageImpl(List<T> content, Pageable pageable, long total)
* -> total: 리턴 하는 데이터 사이즈가 아니라 전체 데이터 사이즈를 넣는다
* 페이징이란 데이터 일부를 리턴하므로 전체 페이지가 몇 개인지 Client에게 알리는 용도이다
* PageRequest of(int page, int size, Sort sort)
* -> page: 페이지 출력 시작 페이지
*/
return new PageImpl<>(List.of(member1, member2),
PageRequest.of(0, 10, Sort.by("memberId").descending()), // 정렬 지원 안됨. DB에서 정렬하지 않으므로..
2);
📚 Reference
- https://www.baeldung.com/spring-data-jpa-pagination-sorting
- https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html
- https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/PageRequest.html
- https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Slice.html
'Back-End > JPA' 카테고리의 다른 글
[JPA] 엔티티 매핑과 연관 관계 매핑 : 단일 엔티티를 DB 테이블과 매핑하기 (0) | 2022.08.31 |
---|---|
JPA 기반 데이터 액세스 계층 (0) | 2022.08.31 |
[Spring Data JPA] 서비스, 리포지토리 구현 (0) | 2022.08.28 |
[Spring Data JPA] 도메인 엔티티 클래스 정의 (0) | 2022.08.28 |
Spring Data JDBC (0) | 2022.08.26 |
댓글