SpringDataJPA - 페이징과 정렬

박민수·2023년 11월 14일
0

JPA

목록 보기
17/24
post-thumbnail

페이징과 정렬

이번 포스팅에서는 SpringDataJPA에서 제공하는 페이징과 정렬 기능을 구현해보고자 한다.

파라미터 타입

  • 정렬 기능 : org.springframework.data.domain.Sort
  • 페이징 기능 : org.springframework.data.domain.Pageable (내부에 Sort 포함)

특별한 반환 타입

  • 추가 count 쿼리 결과를 포함하는 페이징 : org.springframework.data.domain.Page
  • 추가 count 쿼리 없이 다음 페이지만 확인 가능 : org.springframework.data.domain.Slice (내부적으로 limit + 1 조회)
  • 추가 count 쿼리 없이 결과만 반환 : List (자바 컬렉션)
Page<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 O
Slice<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 X
List<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 X
List<Member> findByUsername(String name, Sort sort);

예제 코드

다음 조건으로 페이징과 정렬을 사용하는 예제 코드를 보자.

  • 검색 조건: 나이가 10살 정렬
  • 조건: 이름으로 내림차순
  • 페이징 조건: 첫 번째 페이지, 페이지당 보여줄 데이터는 3건
public interface MemberRepository extends Repository<Member, Long> {
    Page<Member> findByAge(int age, Pageable pageable);
}
//페이징 조건과 정렬 조건 설정
@Test
public void page() throws Exception {
    //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));
    
    //when
    PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
    Page<Member> page = memberRepository.findByAge(10, pageRequest);

    //then
    List<Member> content = page.getContent(); //조회된 데이터
    assertThat(content.size()).isEqualTo(3); //조회된 데이터 수
    assertThat(page.getTotalElements()).isEqualTo(5); //전체 데이터 수
    assertThat(page.getNumber()).isEqualTo(0); //페이지 번호
    assertThat(page.getTotalPages()).isEqualTo(2); //전체 페이지 번호
    assertThat(page.isFirst()).isTrue(); //첫번째 항목인가?
    assertThat(page.hasNext()).isTrue(); //다음 페이지가 있는가?
}

findByAge() 메서드에서 두 번째 파라미터로 받은 Pageable은 인터페이스다. 따라서 실제 사용할 때는 해당 인터페이스를 구현한 org.springframework.data.domain.PageRequest 객체를 사용한다. PageRequest 생성자의 첫 번째 파라미터에는 현재 페이지를, 두 번째 파라미터에는 조회할 데이터 수를 입력한다. 여기에 추가로 정렬 정보도 파라미터로 사용할 수 있다.

주의 : Page는 1부터 시작이 아니라 0부터 시작이다.

Page 인터페이스

public interface Page<T> extends Slice<T> {
    int getTotalPages(); //전체 페이지 수
    long getTotalElements(); //전체 데이터 수
    <U> Page<U> map(Function<? super T, ? extends U> converter); //변환기
}

Slice 인터페이스

public interface Slice<T> extends Streamable<T> {
    int getNumber(); //현재 페이지
    int getSize(); //페이지 크기
    int getNumberOfElements(); //현재 페이지에 나올 데이터 수
    List<T> getContent(); //조회된 데이터
    boolean hasContent(); //조회된 데이터 존재 여부
    Sort getSort(); //정렬 정보
    boolean isFirst(); //현재 페이지가 첫 페이지 인지 여부
    boolean isLast(); //현재 페이지가 마지막 페이지 인지 여부
    boolean hasNext(); //다음 페이지 여부
    boolean hasPrevious(); //이전 페이지 여부
    Pageable getPageable(); //페이지 요청 정보
    Pageable nextPageable(); //다음 페이지 객체
    Pageable previousPageable();//이전 페이지 객체
    <U> Slice<U> map(Function<? super T, ? extends U> converter); //변환기
}

Count 쿼리 분리

전체 Count 쿼리는 매우 무겁기 때문에, 복잡한 sql에서는 Count 쿼리를 분리하는 것이 좋다. 예를들면 left join을 해서 데이터를 조회하는 경우, Count 쿼리에서는 left join을 빼더라도 결과가 똑같기 때문에 이런 경우 Count 쿼리를 분리하는 것이 좋다.

@Query(value = “select m from Member m”,
	countQuery = “select count(m.username) from Member m”)
Page<Member> findMemberAllCountBy(Pageable pageable);

Top, First 사용

아래와 같이 Top, First 등 을 사용할 수 있다.

List<User> findTop10ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);

엔티티 DTO로 변환하기

조회한 Page 객체를 .map() 메서드를 통해 곧바로 DTO로 변환할 수 있다.

Page<Member> page = memberRepository.findByAge(10, pageRequest);
Page<MemberDto> dtoPage = page.map(m -> new MemberDto());

참조
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.limit-query-result
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-%EC%8B%A4%EC%A0%84/dashboard

profile
안녕하세요 백엔드 개발자입니다.

0개의 댓글