[JPA] 스프링 데이터 JPA 페이징과 정렬

컴공생의 코딩 일기·2023년 2월 2일
0

JPA

목록 보기
7/14
post-thumbnail

스프링 데이터 JPA 페이징과 정렬

스프링 데이터 JPA에서는 페이징과 정렬을 쉽게 구현 할 수 있는 강력한 방법을 제공한다.

페이징과 정렬 파라미터

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

반환 타입

  • org.springframework.data.domain.Page : count 쿼리 결과를 포함
  • org.springframework.data.domain.Slice : count 쿼리를 포함하지 않고 다음 페이지만 확인 가능(내부적으로 limit + 1 조회)
  • List : (자바 컬렉션) : count 쿼리 없이 결과만 조회할 때 사용

페이징과 정렬 사용 예제

Page<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용
Slice<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용
안함
List<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용
안함
List<Member> findByUsername(String name, Sort sort);

Page Test 코드 예제

@Test
    void paging() 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));
        memberRepository.save(new Member("member6", 10));
        memberRepository.save(new Member("member7", 10));

        int age = 10;
        PageRequest pageRequest =
                PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
        // when
        Page<Member> page = memberRepository.findByAge(age, pageRequest);

        // then
        List<Member> content = page.getContent();
        long totalElements = page.getTotalElements();
       assertThat(content.size()).isEqualTo(3);
       assertThat(page.getTotalElements()).isEqualTo(7);
       assertThat(page.getNumber()).isEqualTo(0);
       assertThat(page.getTotalPages()).isEqualTo(3);
       assertThat(page.isFirst()).isTrue();
       assertThat(page.hasNext()).isTrue();
    }

PageRequest는 몇 페이지, 한 페이지의 사이즈, Sorting 방법(Option) 을 가지고 Repository에 Paging을 요청할 때(Pageable 에 요청) 사용하는 객체이다.

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

Page<T> 메서드

  • int getNumber() : 현재 페이지 번호
  • int getSize() : 페이지 당 데이터 개수
  • int getTotalPages() : 총 페이지 수
  • int getNumberOfElements() : 현재 페이지에 데이터 개수
  • long getTotalElements() : 전체 데이터 개수
  • boolean hasPrevious() : 이전 페이지 여부
  • boolean isFirst() : 현재 페이지가 첫 페이지 인지 여부
  • boolean hasNext() : 다음 페이지 존재 여부
  • boolean isLast() : 현재 페이지가 마지막 페이지 인지 여부
  • Pageable nextPageable() : 다음 페이지 객체, 다음 페이지가 없으면 null
  • Pageable previousPageable() : 이전 페이지 객체, 이전 페이지 없으면 null
  • boolean hasContent() : 데이터 존재 여부
  • Sort getSort() : 정렬 정보
  • getContent(), get() : 실제 컨텐츠를 가지고 오는 메서드
    • getContent() : List<Entity> 반환
    • get() : Stream<Entity> 반환
  • <U> Slice<U> map(Function<? super T, ? extends U> converter) : 변환기

count 쿼리를 다음과 같이 분리할 수 있다.

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

join을 사용해 Member와 Team을 같이 조회할 경우 Count 쿼리도 join을 사용해 쿼리가 나간다. 만약 Member만 Count 쿼리를 사용할 경우 위와 같이 분리해서 쿼리를 작성할 수 있다.

페이지를 유지하면서 엔티티를 DTO로 변환

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

Slice Test 코드 예제

@Test
    void slice() 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));
        memberRepository.save(new Member("member6", 10));
        memberRepository.save(new Member("member7", 10));

        int age = 10;
        PageRequest pageRequest =
                PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));
        // when
        Slice<Member> page = memberRepository.findSliceByAge(age, pageRequest);
        // then
        List<Member> content = page.getContent();

        assertThat(content.size()).isEqualTo(3);
        assertThat(page.getNumber()).isEqualTo(0);
        assertThat(page.isFirst()).isTrue();
        assertThat(page.hasNext()).isTrue();
    }

Slice는 Page 인터페이스와 기능은 동일하지만 Count 쿼리를 사용하지 않기 때문에 getTotalElements(), getTotalPages() 메소드가 존재하지 않는다. 또한 limit + 1를 자동으로 조회하기 때문에 더보기 기능을 구현할 때 사용하면 편리하다.

Web 확장 - 페이징과 정렬

스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용할 수 있다.

 @GetMapping("/members")
 public Page<MemberDto> list(@PageableDefault(size = 5) Pageable pageable){
     Page<Member> page = memberRepository.findAll(pageable);
     return page.map((member -> new MemberDto(member.getId(), member.getUsername(), null)));
}
  • 파라미터로 Pageable 인터페이스를 받을 수 있다.
  • Pageable 인터페이스, 실제는 org.springframework.data.domain.PageRequest 객체 생성
  • Pageable 인터페이스를 파라미터로 받으면 아래와 같은 파라미터를 요청 할 수 있다.
    • 예) /members?page=0&size=3&sort=id,desc&sort=username,desc
      • page: 현재 페이지(0부터 시작)
      • size: 한 페이지에 보여질 데이터 수
      • sort: 정렬 조건 정의 ( 예)정렬할 속성,정렬조건(ASC|DESC)asc 생략 가능)
    • 기본 size 값은 20으로 설정되어 있다. 기본 값을 변경하고 싶은면 아래와 같은 설정으로 변경 가능하다.
spring.data.web.pageable.default-page-size=20 /# 기본 페이지 사이즈/
spring.data.web.pageable.max-page-size=2000 /# 최대 페이지 사이즈/

Default 설정

@PageableDefault 어노테이션을 사용하면 page,size, sort 를 기본으로 설정 가능하다.

 @GetMapping("/members")
 public Page<MemberDto> list(@PageableDefault(page = 1,size = 5,sort = "username", direction = Sort.Direction.DESC) Pageable pageable){
 }

접두사

  • 페이징 정보가 둘 이상이면 접두사로 구분
  • @Qualifier에 접두사명 추가 {접두사명}_xxx
  • 예) /members?member_page=0&order_page=1
public String list(
 @Qualifier("member") Pageable memberPageable,
 @Qualifier("order") Pageable orderPageable, ...
profile
더 좋은 개발자가 되기위한 과정

0개의 댓글