java/spring

[Spring] Spring Data JPA의 페이징

danuri 2023. 6. 21. 23:35

인자 타입 - 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

https://gksdudrb922.tistory.com/115#%EC%-A%A-%ED%--%--%EB%A-%--%--%EB%-D%B-%EC%-D%B-%ED%--%B-%--JPA%--%ED%-E%--%EC%-D%B-%EC%A-%--%EA%B-%BC%--%EC%A-%--%EB%A-%AC

 

Hola