Spring Data JPA - 페이징과 정렬


Spring Data JPA 페이징과 정렬

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

스프링 데이터 JPA에서는 페이징 및 정렬 기능을 지원합니다. 여기서는 이 기능들에 대한 간단한 사용 방법과 예제를 다룹니다.

페이징과 정렬 관련 파라미터

  • org.springframework.data.domain.Sort: 정렬을 위한 클래스입니다.
  • org.springframework.data.domain.Pageable: 페이징 기능을 제공합니다. 내부적으로 Sort를 포함하고 있습니다.

반환 타입에 따른 특징

  • org.springframework.data.domain.Page: 추가 count 쿼리의 결과를 포함하는 페이징 정보를 제공합니다.
  • org.springframework.data.domain.Slice: 추가 count 쿼리 없이 다음 페이지의 존재 여부만 확인할 수 있습니다. 내부적으로 limit + 1을 조회합니다.
  • List: 추가 count 쿼리 없이 결과만 반환합니다.

사용 예시:

1
2
3
4
fun findByUsername(name: String, pageable: Pageable): Page<Member>
fun findByUsername(name: String, pageable: Pageable): Slice<Member>
fun findByUsername(name: String, pageable: Pageable): List<Member>
fun findByUsername(name: String, sort: Sort): List<Member>

예제 코드

  • 검색 조건: 나이가 10살
  • 정렬 조건: 이름으로 내림차순
  • 페이징 조건: 첫 번째 페이지, 페이지당 보여줄 데이터는 3건

Repository 정의:

1
2
3
interface MemberRepository: Repository<Member, Long> {
fun findByAge(age: Int, pageable: Pageable): Page<Member>
}

테스트 코드:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
fun pageTest() {
// 데이터 저장
memberRepository.save(Member(username = "member1", age = 10))
memberRepository.save(Member(username = "member2", age = 10))
memberRepository.save(Member(username = "member3", age = 10))
memberRepository.save(Member(username = "member4", age = 10))
memberRepository.save(Member(username = "member5", age = 10))

// 페이징 및 정렬 조건 설정
val pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "username"))
val page = memberRepository.findByAge(10, pageRequest)

val content = page.content
assertThat(content.size).isEqualTo(3)
assertThat(page.totalElements).isEqualTo(5L)
assertThat(page.number).isEqualTo(0)
assertThat(page.totalPages).isEqualTo(2)
assertThat(page.isFirst).isTrue()
assertThat(page.hasNext()).isTrue()
}

Pageable은 인터페이스이므로, org.springframework.data.domain.PageRequest 객체를 이용하여 구현합니다. PageRequest 생성자의 첫 번째 인자는 페이지 번호, 두 번째 인자는 페이지당 데이터 수입니다. 페이지 번호는 0부터 시작합니다.

주의: Page의 페이지 번호는 0부터 시작입니다.

Page와 Slice 인터페이스

Page 인터페이스

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

Slice 인터페이스

Slice.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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); //변환기
}
``

추가 팁

  • 복잡한 쿼리의 경우, countQuery를 사용하여 count 쿼리를 분리할 수 있습니다.
1
2
3
@Query(value = "select m from Member m",
countQuery = "select count(m.username) from Member m")
fun findMemberAllCountBy(pageable: Pageable): Page<Member>
  • Top, First와 같은 키워드를 사용하여 결과의 제한된 수를 조회할 수 있습니다. 더 자세한 정보는 Spring Data JPA 공식 문서를 참조하세요.

  • Page 객체를 사용하여 현재 엔티티를 DTO로 변환하는 경우:

1
val dtoPage: Page<MemberDto> = page.map { m -> MemberDto(m) }

실습을 통해 Page, Slice, List에 대한 페이징 및 정렬의 차이를 경험해 보시기 바랍니다. 실무에서는 count 쿼리의 성능을 항상 고려해야 합니다. 전체 count 쿼리는 비용이 많이 들 수 있으므로 적절한 전략을 선택하는 것이 중요합니다.

소스코드

참조