-
#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());
'Spring Data JPA' 카테고리의 다른 글
#Spring Data JPA - 9 쿼리메소드 -JPA Hint & Lock (0) 2021.03.24 #Spring Data JPA - 8 쿼리메소드 -@EntityGraph (0) 2021.03.24 #Spring Data JPA - 5 쿼리메소드 - 반환타입 (0) 2021.03.23 #Spring Data JPA - 4 쿼리 메소드 -파라미터 바인딩 (0) 2021.03.23 #Spring Data JPA - 3 쿼리 메소드 기능 (0) 2021.03.23