본문 바로가기

Java/JPA

SpringDataJPA - 페이징을 쉽게 하는 법

Page 객체

Repository 인터페이스에 Page<T> 메서드 이름(Pageable 변수이름)을 작성한다.


public interface MemberRepository extends JpaRepository<Member, Long> {

    // 페이징
    Page<Member> findByAge(int age, Pageable pageable);

 

SpringDataJPA로 페이징 처리를 하면 페이징 계산을 할 필요가 없다. 이미 구현되어있다,.

 

메서드 호출 시 Pageable 구현체(PageRequest)를 매개변수로 넣어준다.

*Page 인덱스가 1이 아니라 0부터 시작함. 유의

 

아래는 test 코드이다.

int age = 10;

// SPRING DATA JPA는 page index가 1이 아니라 0부터 시작한다 (PageRequest = Pageable의 구현체)
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"));

// when
Page<Member> page = memberRepository.findByAge(age, pageRequest);

 

Page 객체의 주요 메서드: 

 

getContent() 메서드 : 현재 페이지의 데이터 목록을 반환

getTotalElements() : 전체 데이터 수 반환

getNumber() : 현재 페이지 번호 반환

getTotalPages() : 총 페이지 수 반환

isFirst() :  현재 페이지가 첫번째 페이지인지 여부 반환

hasNext() : 다음 페이지가 존재하는지 여부 반환

 

// then
List<Member> content = page.getContent();
long totalElements = page.getTotalElements();
for (Member member : content) {
    System.out.println("member = " + member);
}
System.out.println("totalElements = " + totalElements);

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(); // 다음 페이지가 있는가

 

실제 컨트롤러 사용 예) 

기본 JpaRepository메서드에 pageable 매개변수로 던져도 됨

Entity로 노출하면 안됨 DTO로 반환하는거 주의하기!

// 예시1 : localhost:8080/members?page=0&size=3 으로 GET 요청시 injection해줌
// 예시2 : localhost:8080/members?page=0&size=3&sort=id,desc&sort=username,desc
// application.yml에 디폴트 설정 변경 시 글로벌 설정도 가능 또는 @PageableDefault 사용
@GetMapping("/members")
public Page<MemberDto> list(Pageable pageable){
    Page<Member> page = memberRepository.findAll(pageable);
    Page<MemberDto> map = page.map(member -> new MemberDto(member.getId(), member.getUsername(), null));
    return map;
}

 

MemberDto생성자에 Member 엔티티가 들어갈 수 있으면 아래와 같이 코드 단축가능

@GetMapping("/members")
public Page<MemberDto> list(@PageableDefault(size = 5) Pageable pageable){
    Page<Member> page = memberRepository.findAll(pageable);
    return page.map(MemberDto::new);
}

 

메서드에 디폴트 설정을 하고 싶을 때: @PageableDefault 사용

@GetMapping("/members")
public Page<MemberDto> list(@PageableDefault(size = 5) Pageable pageable){
    Page<Member> page = memberRepository.findAll(pageable);
    Page<MemberDto> map = page.map(member -> new MemberDto(member.getId(), member.getUsername(), null));
    return map;
}

 

* 실무에서 TotalCount 쿼리가 성능이슈가 발생할 가능성이 있음.

쿼리가 복잡해지면 count 쿼리를 분리해야함(성능테스트시 성능이 안나오는 경우가 있음)

해결법 : 카운트 쿼리 분리

 

예제) left outer join 및 where 조건이 없는 경우 최적화

// 카운트 쿼리를 분리한다 (left outer join인 경우 join 안해도 카운트 결과는 같음)
@Query(value = "select m from Member m left join m.team t",
        countQuery = "select count(m.username) from Member m")
Page<Member> findByAge(int age, Pageable pageable);

 

Sort 조건도 복잡해지면 안풀리는데 value에 같이 직접 작성해주면 됨.

 

*API 변환할 때 사용하는 DTO로 쉽게 변환해보기(map 활용)

// when
Page<Member> page = memberRepository.findByAge(age, pageRequest);

// Dto로 변환된 값.
Page<MemberDto> toMap = page.map(m -> new MemberDto(m.getId(), m.getUsername(), null));

 

컨트롤러에서 Page<MemberDto>로 반환가능(JSON으로 변환됨)


Slice 객체

 

데이터 목록 가져올 때, 1개 더 가져와서 1개 있으면 [더보기] 만들어서 누르면 추가로 호출하기

이런거 구현할 때 사용함. (totalCount는 가져오지 않음.주의)

 

Repository 에 메서드 생성

Slice<Member> findByAge(int age, Pageable pageable);

 

아래와 같이 사용 가능

// when
Slice<Member> page = memberRepository.findByAge(age, pageRequest);

// then
List<Member> content = page.getContent();

for (Member member : content) {
    System.out.println("member = " + member);
}

assertThat(content.size()).isEqualTo(3);
assertThat(page.getNumber()).isEqualTo(0); // 현재 페이지 번호 가져옴
assertThat(page.isFirst()).isTrue(); // 첫번째 페이지인가
assertThat(page.hasNext()).isTrue(); // 다음 페이지가 있는가

 


 

 

 

 

 

본 포스팅은 김영한의 실전! 스프링 데이터 JPA(인프런)를 참고하였습니다