인자 타입 - Pageable
findAll()과 같이 컬렉션 조회를 하게 되면 페이징이 필요할 때가 있다.
페이징을 위해서는 기본적으로 몇 번째 페이지인지에 대한 정보와(page) 한 페이지당 보여줄 데이터의 개수가(size) 필요하다.
Spring Data JPA는 이러한 정보를 위해 Pageable 인터페이스를 사용한다.
@GetMapping
public ResponseEntity<?> page(@RequestParam int page, @RequestParam int size) {
Pageable pageable = PageRequest.of(page, size);
Page<Member> members = memberRepository.findAll(pageable);
return new ResponseEntity<>(members, HttpStatus.OK);
}
- PageRequest는 Pageable의 구현체이다.
- pageable을 레포지토리 메서드의 인자로 넘기면 JPA가 알아서 페이징을 처리해준다.
또한 이렇게 컨트롤러에서 Pageable을 직접 인자로 설정할 수도 있다.
@GetMapping
public ResponseEntity<?> page(Pageable pageable) {
Page<Member> members = memberRepository.findAll(pageable);
return new ResponseEntity<>(members, HttpStatus.OK);
}
이러면 GET: /members?page=0&size=3과 같은 API를 호출하면 Spring이 알아서 Pageable 객체에 매핑해준다.
또한 GET: /members 처럼 쿼리 파라미터를 따로 지정하지 않아도, JPA 자체 default 값(page=0&size=20)에 따라 페이징을 처리해준다.
defulat 값 변경하는 법
1. 글로벌 설정 → application.yml
spring.data.web.pageable.default-page-size=20
2. 개별 설정 → @PageableDefault
@GetMapping
public ResponseEntity<?> page(@PageableDefault(size = 10) Pageable pageable) {
Page<Member> members = memberRepository.findAll(pageable);
return new ResponseEntity<>(members, HttpStatus.OK);
}
반환 타입 - Page, Slice, List
Spring Data JPA는 페이징을 위한 반환타입도 제공한다.
- Page: count 쿼리 결과를 포함하는 페이징
- Slice: count 쿼리 없이 다음 페이지만 확인 가능 (내부적으로 limit + 1 조회)
다음 테스트 예시를 보자.
@Test
public void paging() {
//given
memberRepository.save(new Member("member1", 10));
memberRepository.save(new Member("member2", 10));
memberRepository.save(new Member("member3", 10));
memberRepository.save(new Member("member4", 10));
memberRepository.save(new Member("member5", 10));
Pageable pageable = PageRequest.of(0, 3);
//when
Page<Member> page = memberRepository.findAll(pageable);
Slice<Member> slice = memberRepository.findAll(pageable);
//then
assertThat(page.getTotalElements()).isEqualTo(5); // 페이지 상관 없이 조회할 수 있는 element의 총 개수
assertThat(page.getNumber()).isEqualTo(0); // 현재 페이지 번호
assertThat(page.getTotalPages()).isEqualTo(2); // 조회할 수 있는 페이지 개수
}
DB에 member 데이터 5개를 save해놓고,
page=0&size=3에 대한 Pageable에 대해
각 반환 타입에 맞게 findAll 메서드를 호출했다.
실제 조회 쿼리를 찍어보자.
먼저 Page타입은 다음과 같다.
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
order by
member0_.age asc limit ? offset ?
select
count(member0_.member_id) as col_0_0_
from
member member0_
이렇게 카운트 쿼리를 통해 페이징시 전체 데이터 개수 등 필요한 적정한 데이터를 저장한다. (테스트 코드 then절 참고)
Slice타입은 다음과 같다.
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
order by
member0_.age asc limit ? offset ?
카운트 쿼리가 따로 없다. Slice는 limit(size) + 1 만큼만 값을 가져오기 때문이다.
실무
활용
Q: Page와 Slice는 각각 어느 상황에 사용하면 좋을까?
우리가 웹에서 게시판을 볼 때, 10개의 게시글마다 페이지 단위로 넘겨서 볼 때가 있는 반면에, (Page)
10개의 게시글이 쭉 나오고 맨 아래 "더보기"와 같은 버튼이 있어 클릭하면 다음 10개의 게시글을 보여주는 방식도 있다. (Slice)
즉, 각자 화면을 구성하는 상황에 따라 다른 반환 타입을 사용할 수 있다.
+) Page, Slice 대신 List를 사용하면 페이징을 하되 결과를 페이징 정보 없이 단순 List로 받을 수 있다.
따라서 카운트 쿼리는 실행되지 않는다.
성능
Slice는 카운트 쿼리가 나가지 않고 다음 slice가 존재하는지 여부만 확인할 수 있기 때문에, 데이터 양이 많을수록 slice를 사용하는 것이 성능상 유리하다.
정리하자면,
page는 게시판 같이 총 데이터 개수가 필요한 상황에서,
slice는 모바일과 같이 총 데이터 개수가 필요없는 상황에서(무한스크롤)
각각 필요한 용도에 알맞게 쓰면 된다.
엔티티를 DTO로 변환
API 통신을 하다보면 엔티티가 아닌 DTO로 반환하는 것이 좋다.
만약 Page<Memeber>를 페이징 정보를 유지하면서 Page<MemberDto>로 변환하고 싶다면 어떻게 해야 할까?
Page 혹은 Slice는 이를 위해 map() 메서드를 지원한다.
Page<Member> page = memberRepository.findAll(pageable);
Page<MemberDto> memberDtos = page.map(m -> new MemberDto(m.getId(), ...생성자 형식));
---
참고자료
https://zayson.tistory.com/entry/Spring-Data-JPA의-Page와-Slice
https://velog.io/@dltkdgns3435/SpringBoot-Spring-Data-JPA-에서-Page와-Slice
https://yoonbing9.tistory.com/38
'java > spring' 카테고리의 다른 글
[Spring] @TransactionalEventListener에서 예외가 발생하지 않는 이슈 (2) | 2023.06.23 |
---|---|
[Spring] 스프링 디렉터리 패키지 구조 (0) | 2023.06.22 |
[Spring] @ControllerAdvice, 특정 예외 발생 시 404에러가 발생하는 이슈 (0) | 2023.06.21 |
[Spring] Spring AOP와 실무 응용 (0) | 2023.01.12 |
[Spring] PostgreSQL - PostGIS, JPA를 통해 공간 데이터 다루기 (0) | 2023.01.12 |