스프링 DATA JPA 페이징 처리

seungho choi·2022년 8월 1일
0

인프런 김영한님 강의 실전! 스프링 데이터 JPA를 보고 모르는 내용만 개인적으로 정리한 내용입니다.

스프링 DATA JPA에서는 페이징에 대한 강력한 기능들을 제공한다. PagingAndSortingRepository 인터페이스를 상속받으면 굉장히 유용한 페이징과 정렬에 대한 유용한 기능들을 사용할 수 있다.

Page

Spring Data Jpa에서 쿼리 메소드를 생성할 때 Page 타입으로 지정하면 된다 만약 페이지의 컨디션(조건)이 필요하면 Pageable을 파라미터로 지정하면 된다.

Page 반환 타입의 메소드의 특징은 count 쿼리를 날린다는 특징이 있다.

레포지토리

// JpaRepository는 PagingAndSortingRepository를 상속 받고 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
	
    // 기본적으로 findAll 이라는 페이징 메소드를 제공해서
    // 실습을 위한 member의 age
    Page<Member> findByAge(Integer age, Pageable pageable);

}

테스트 코드

@Test
void findByPage() {
        // 20개 데이터 생성
        for (int i = 1; i <= 20; i++) {
        	// age는 기본적으로 모두 10이다.
            Member member = new Member("Member" + i, 10);
            memberRepository.save(member);
        }

        // 보통 Pageable 구현체인 PageRequest를 사용한다
        // 10개의 사이즈인 0번째 페이지인 PageRequest 생성
        // 페이지는 0 페이지부터 시작한다. 
        PageRequest pageRequest = PageRequest.of(0, 10);
        Page<Member> page = memberRepository.findByPage(pageRequest);

        assertThat(page.getTotalElements()).isEqualTo(20);
        // 20개의 데이터가 있기때문에 size가 2니까 2 * 20 = 2개의 페이지가 나온다.
        assertThat(page.getTotalPages()).isEqualTo(2);
        
        // Page는 Slice를 자식 타입이다. 밑에있는 메소드는 Slice 제공하는 메소드다.
        assertThat(page.isEmpty()).isFalse();
        assertThat(page.hasNext()).isTrue();
        assertThat(page.isFirst()).isTrue();
        assertThat(page.isLast()).isFalse();
}

실제 쿼리

  • 0번째 페이지니까 offset은 따로 설정을 하지 않는다. 만약 1번째 페이지면 offset을 10으로 설정한다. 2번째 페이지면 20으로 설정된다.

Count 쿼리 최적화

하지만 복잡한 query를 사용할 때 count 쿼리가 원하는대로 동작을 안할 수가 있다.

예를 들어 MemberTeam 이랑 조인 하는 페이징 메소드를 사용한다고 했을 때

레포지토리

public interface MemberRepository extends JpaRepository<Member, Long> {

	@Query(value = "select m from Member m join m.team")
    Slice<Member> findByPage(Integer Age, Pageable pageable);

}

실제 카운트 쿼리는 team 을 조인을 할 필요가 없는데도 조인을 해 성능상 문제를 일으킬 수 있다.

@QuerycountQuery 속성을 이용해서 해결할 수 있다.

    @Query(value = "select m from Member m join m.team"
            , countQuery = "select count(m.id) from Member m")
    Page<Member> findByAge(Integer age, Pageable pageable);

Slice

Page의 리턴하는 방식의 단점은 모든 데이터를 확인하는 count 쿼리를 날린다.

정통적인 페이지 네이션 방식이 아니라 무한 스크롤이나 더보기 페이징 방식같이 토탈 데이터의 갯수가 필요없을 경우 Slice 를 리턴하는 방식을 사용하는 메소드를 만들면 된다.

Slice 타입 리턴하는 방식을 사용하면 모든 데이터를 확인하지 않고 현재 페이지에서 +1개의 데이터만 가져와서 다음 데이터가 있는지 없는지 확인한다.

레포지토리

public interface MemberRepository extends JpaRepository<Member, Long> {
	
    Slice<Member> findByPage(Integer Age, Pageable pageable);

}

테스트 코드

@Test
void findBySlice() {
        // 20개 데이터 생성
        for (int i = 1; i <= 20; i++) {
        	// age는 기본적으로 모두 10이다.
            Member member = new Member("Member" + i, 10);
            memberRepository.save(member);
        }
 
        PageRequest pageRequest = PageRequest.of(0, 10);
        Slice<Member> page = memberRepository.findByPage(pageRequest);

        assertThat(page.isEmpty()).isFalse();
        assertThat(page.hasNext()).isTrue();
        assertThat(page.isFirst()).isTrue();
        assertThat(page.isLast()).isFalse();
}

결과

  • 사이즈를 1로 설정했는데 limit 쿼리를 +1 해서 11개의 데이터를 가져온다.
  • 마지막 1개의 데이터로 다음 페이지가 있는지 없는지 판단할 수 있다.

1개의 댓글

comment-user-thumbnail
2022년 8월 8일

qd

답글 달기