ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • #Spring Data JPA - 6 쿼리메소드 - 페이징과 정렬
    Spring Data JPA 2021. 3. 24. 10:47

    순수 JPA 페이징과 정렬

     

    JPA에서 페이징을 어떻게 할 것인가?

     

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

     

    · 검색 조건: 나이가 10살

    · 정렬 조건: 이름으로 내림차순

    · 페이징 조건: 첫 번째 페이지, 페이지당 보여줄 데이터는 3건

     

    JPA 페이징 리포지토리 코드

    public List<Member> findByPage(int age, int offset, int limit) {
       return em.createQuery("select m from Member m where m.age = :age order by m.username desc")
               .setParameter("age", age)
               .setFirstResult(offset)
               .setMaxResults(limit)
               .getResultList();
    }
    
    public long totalCount(int age) {
     return em.createQuery("select count(m) from Member m where m.age = :age",Long.class)
                 .setParameter("age", age)
                 .getSingleResult();
    }

    JPA 페이징 테스트 코드

    @Test
    public void paging() throws Exception {
    
         //given
         memberJpaRepository.save(new Member("member1", 10));
         memberJpaRepository.save(new Member("member2", 10));
         memberJpaRepository.save(new Member("member3", 10));
         memberJpaRepository.save(new Member("member4", 10));
         memberJpaRepository.save(new Member("member5", 10));
         
         int age = 10;
         int offset = 0;
         int limit = 3;
         
         //when
         List<Member> members = memberJpaRepository.findByPage(age, offset, limit);
         long totalCount = memberJpaRepository.totalCount(age);
         //페이지 계산 공식 적용...
         // totalPage = totalCount / size ...
         // 마지막 페이지 ...
         // 최초 페이지 ..
         
         //then
         assertThat(members.size()).isEqualTo(3);
         assertThat(totalCount).isEqualTo(5);
    }
    

     

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

    페이징과 정렬 파라미터

    · org.springframework.data.domain.Sort : 정렬 기능

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

     

     

    특별한 반환 타입

    · org.springframework.data.domain.Page : 추가 totalCount 쿼리 결과를 포함하는 페이징 · org.springframework.data.domain.Slice : 추가 totalCount 쿼리 없이 다음 페이지만 확인 가능(더보기 기능)

      (내부적으로 limit + 1조회)

    · List (자바 컬렉션): 추가 totalCount 쿼리 없이 결과만 반환

     

    페이징과 정렬 사용 예제

    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);

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

    · 검색 조건: 나이가 10살

    · 정렬 조건: 이름으로 내림차순

    · 페이징 조건: 첫 번째 페이지, 페이지당 보여줄 데이터는 3건

     

     

    Page 사용 예제 정의 코드

    public interface MemberRepository extends Repository<Member, Long> {
       Page<Member> findByAge(int age, Pageable pageable);
    }

    Page 사용 예제 실행 코드

    //페이징 조건과 정렬 조건 설정
    @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);
               //0page에서 3개 가져와 sorting 조건은 username DESC
       //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(); //다음 페이지가 있는가?
    }

    · 두 번째 파라미터로 받은 Pagable인터페이스다.

      따라서 실제 사용할 때는 해당 인터페이스를 구현한 org.springframework.data.domain.PageRequest 객체를 사용한다.

    · PageRequest 생성자의 첫 번째 파라미터에는 현재 페이지를, 두 번째 파라미터에는 조회할 데이터 수를 입력한다.

      여기에 추가로 정렬 정보도 파라미터로 사용할 수 있다. 참고로 페이지는 0부터 시작한다.

     

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

     

     

    Page 인터페이스

    public interface Page extends Slice {
    
      int getTotalPages(); //전체 페이지 수
      long getTotalElements(); //전체 데이터 수
      <U> PAGE<U> map(Funciton<? 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); //변환기
    }

    limit +1

     

    참고: count 쿼리를 다음과 같이 분리할 수 있음

    분리 하는 이유 : 쿼리가 복잡해질경우 카운트쿼리도 복잡한 쿼리를 그대로 가져가서 성능이 느려짐

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

    countQuery를 분리하지 않고 돌렷을 경우

    count 쿼리까지 조인을 하게 됨.->최적화가필요

    countQuery를 분리하고 돌렷을 경우

    조인없이 count만 가지고 옴

     Top, First 사용 참고

    https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.limitquery-result

     

    Spring Data JPA - Reference Documentation

    Example 109. Using @Transactional at query methods @Transactional(readOnly = true) public interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") v

    docs.spring.io

    List findTop3By();

     

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

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

     

    댓글

Designed by Tistory.