본문 바로가기
[Spring Boot]/[JPA]

[JPA] 페이징(paging)

by 북방바다코끼리표범 2023. 10. 22.

복습

https://shins99.tistory.com/109

 

[JPA] 영속성 컨텍스트

영속성 컨텍스트란? 영속성 컨텍스트는 엔티티를 영구 저장하는 환경이라는 뜻이다. 영속성 컨텍스트는 애플리케이션과 DB 사이에서 객체를 보관하는 가상의 DB 역할을 한다. 엔티티 매니저(Entit

shins99.tistory.com


페이징 (Pageable, Page, Slice )

- 데이터베이스의 대용량 데이터를 처리할 때, 데이터를 효율적으로 로드하고 출력하기 위해 페이지 단위로 데이터를 분할하여 가져오는 기능

 

페이징 적용 코드

다음과 같은 순서와 방식으로 페이징을 적용할 수 있음

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

 

 
/**
 
* 1. PageRequest 객체 생성
 
* PageRequest.of() 메소드를 사용하여 페이지 번호와 페이지 크기를 전달하며 PageRequest 객체 생성 가능
 
* 이때 정렬 조건이 있는 경우 추가적으로 Sort 객체도 함께 인자로 전달 가능
 
*/
 
Pageable pageRequest = PageRequest.of(0, 10); // 페이지 번호: 0, 페이지당 크기: 10
 
 
 
/**
 
* 2. Repository에 Pageable 전달
 
* Pageable 객체를 Repository 메소드의 매개변수로 전달하여 페이징 처리를 적용
 
*/
 
Page<Member> members = memberRepository.findByAge(10, pageRequest);
 
 
 
/**
 
* 3. 결과 사용
 
* Repository에서 반환된 Page 객체를 사용하여 결과 정보와 페이징 메타데이터 등을 다룰 수 있음
 
* (엔티티를 직접 조회하지 않고 다음과 같이 DTO로 변환해 사용해야 안전.)
 
*/
 
Page<MemberDto> memberDtos = page.map(m -> new MemberDto(m.getId(), m.getUsername()));
 
 
 
int totalPages = memberDtos.getTotalPages(); // 총 페이지 수 얻기
 
int pageNumber = memberDtos.getNumber(); // 현재 페이지 번호 얻기
 
List<MemberDto> memberList = memberDtos.getContent(); // 조회된 데이터 얻기

 

페이징에 사용되는 Pageable, Page

 

Pageable

- Spring Data에서 페이징 및 정렬을 처리하기 위한 인터페이스 (내부에 Sort 포함)

- 데이터를 한 페이지 단위로 가져오고 정렬하는 작업에 필요한 정보(페이지 번호, 페이지 크기, 정렬 방법)를 담고 있음

- Pageable 객체를 사용하여 Repository에 페이징과 정렬 조건을 전달, 결과로 Page 또는 Slice 객체를 반환받아 사용

(참고로 Spring Data JPA는 페이지가 0부터 시작함)

 

Slice 와 Page

Slice

- 다음 페이지만 확인 가능함 ( ex: 모바일에 더보기 기능, 무한 스크롤 )

- 따라서, 전체 페이지 개수를 모르기 때문에 추가 totalCount 쿼리 실행 X ( 성능 낭비 발생하지 않음 )

또한, Slice는 내부적으로 limit + 1 수행 함

- limit + 1이란 페이지당 보여줄 데이터 수를 3으로 지정했다면  3 + 1이 되어 4개를 가져옴

 

 
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 3 이 아닌, limit 4 즉, 내부적으로 limit + 1해서 쿼리를 생성)

 

Page

- Page 인터페이스는 Slice 인터페이스를 상속

- Slice의 기능 외에 추가적인 기능(전체 페이지, 전체 데이터 수)들을 갖고 있음

 

 
public interface Page<T> extends Slice<T> {
 
int getTotalPages(); //전체 페이지 수
 
long getTotalElements(); //전체 데이터 수
 
<U> Page<U> map(Function<? super T, ? extends U> converter); //변환기
 
}

추가 totalCount 쿼리 결과 포함되어, 전체 페이지 수와 전체 데이터 개수를 알 수 있음

 

Slice와 Page 차이

Slice와 Page는 전체 페이지 수와 전체 데이터 개수를 조회하는지 안하는지에 대한 차이를 갖고 있음

- page는 전체 페이지와 전체 데이터 수를 계산하기 위해 DB 내에서 주어진 조건에 따른 Count 쿼리를 추가로 호출

- Slice는 다음 페이지가 있느지만 확인하기 때문에 Page보다 성능이 더 좋음

 

  • Page : 게시판과 같이 총 데이터 갯수가 필요한 환경
  • Slice :  모바일과 같이 총 데이터 갯수가 필요없는 환경에서(무한스크롤 등)
  •  

Count 쿼리 분리 - 성능 최적화

데이터를 가져오는 쿼리가 복잡하면, count 쿼리도 복잡해져서 성능 최적화가 필요할 때 사용하는 방법으로

count 쿼리를 다음과 같이 분리할 수 있다. (totalCount 쿼리는 매우 무겁다.)

 

 
// totalCount 성능 최적화를 위한, 쿼리 분리(값 가져오는 쿼리, totalCount 쿼리)
 
@Query(value = "select m from Member m", countQuery = "select count(m.username) from Member m")
 
Page<Member> findMemberAllCountBy(Pageable pageable);

다대일 left outer join을 할 때, 데이터를 가져올 때는 join을 해야하지만

totalCount 쿼리에서는 join을 하지 않아도 count 값은 같다.

 


정렬 (Sort)

- 데이터베이스로부터 가져온 데이터를 특정 필드에 기준으로 순서대로 정렬하는 기능

- Spring Data JPA에서는 Sort 클래스를 사용하여 정렬을 수행할 수 있음

- Sort 인스턴스를 생성하고 원하는 정렬 조건과 방향(오름차순 또는 내림차순)을 설정한 뒤, 레포지토리에 인수로 전달하여 정렬을 적용가능

 

 
Sort sort = Sort.by(Sort.Direction.DESC, "createdDate"); // 생성일 기준 내림차순 정렬
 
List<User> users = userRepository.findAll(sort);

 

페이징 + 정렬

페이징과 정렬은 다음과 같이 함께 사용 가능

PageRequest.of() 메서드를 사용해 페이지 번호와 페이지 크기를 지정하면서 Sort 인스턴스를 전달하여 페이징과 정렬을 동시에 적용가능

 

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

 

 
PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username")); // username 기준 내림 차순
 
Page<Member> page = memberRepository.findByAge(10, pageRequest);
 
Page<MemberDto> memberDtos = page.map(m -> new MemberDto(m.getId(), m.getUsername()));