danuri
오늘의 기록
danuri
전체 방문자
오늘
어제
  • 오늘의 기록 (307)
    • java (150)
      • java (33)
      • spring (63)
      • jpa (36)
      • querydsl (7)
      • intelliJ (9)
    • kotlin (8)
    • python (24)
      • python (10)
      • data analysis (13)
      • crawling (1)
    • ddd (2)
    • chatgpt (2)
    • algorithm (33)
      • theory (9)
      • problems (23)
    • http (8)
    • git (8)
    • database (5)
    • aws (12)
    • devops (10)
      • docker (6)
      • cicd (4)
    • book (44)
      • clean code (9)
      • 도메인 주도 개발 시작하기 (10)
      • 자바 최적화 (11)
      • 마이크로서비스 패턴 (0)
      • 스프링으로 시작하는 리액티브 프로그래밍 (14)
    • tistory (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

태그

  • 자바 최적화
  • S3
  • AWS
  • reactive
  • connection
  • 트랜잭션
  • Kotlin
  • Saving Plans
  • nuribank
  • mockito
  • 등가속도 운동
  • docker
  • Thymeleaf
  • Database
  • CICD
  • 마이크로서비스패턴
  • gitlab
  • RDS
  • DDD
  • Java
  • 도메인 주도 설계
  • Jackson
  • JPA
  • PostgreSQL
  • Bitmask
  • ChatGPT
  • Spring
  • POSTGIS
  • SWAGGER
  • Security

최근 댓글

최근 글

hELLO · Designed By 정상우.
danuri

오늘의 기록

java/spring

[Spring] Spring Data JPA의 페이징

2023. 6. 21. 23:35

인자 타입 - Pageable

findAll()과 같이 컬렉션 조회를 하게 되면 페이징이 필요할 때가 있다.

페이징을 위해서는 기본적으로 몇 번째 페이지인지에 대한 정보와(page) 한 페이지당 보여줄 데이터의 개수가(size) 필요하다.

 

Spring Data JPA는 이러한 정보를 위해 Pageable 인터페이스를 사용한다.

@GetMapping
public ResponseEntity<?> page(@RequestParam int page, @RequestParam int size) {
    Pageable pageable = PageRequest.of(page, size);
    Page<Member> members = memberRepository.findAll(pageable);
    return new ResponseEntity<>(members, HttpStatus.OK);
}
 
  • PageRequest는 Pageable의 구현체이다.
  • pageable을 레포지토리 메서드의 인자로 넘기면 JPA가 알아서 페이징을 처리해준다.

 

또한 이렇게 컨트롤러에서 Pageable을 직접 인자로 설정할 수도 있다.

@GetMapping
public ResponseEntity<?> page(Pageable pageable) {
    Page<Member> members = memberRepository.findAll(pageable);
    return new ResponseEntity<>(members, HttpStatus.OK);
}
 

이러면 GET: /members?page=0&size=3과 같은 API를 호출하면 Spring이 알아서 Pageable 객체에 매핑해준다.

또한 GET: /members 처럼 쿼리 파라미터를 따로 지정하지 않아도, JPA 자체 default 값(page=0&size=20)에 따라 페이징을 처리해준다.

 

defulat 값 변경하는 법

1. 글로벌 설정 → application.yml

spring.data.web.pageable.default-page-size=20
 

 

2. 개별 설정 → @PageableDefault

@GetMapping
public ResponseEntity<?> page(@PageableDefault(size = 10) Pageable pageable) {
    Page<Member> members = memberRepository.findAll(pageable);
    return new ResponseEntity<>(members, HttpStatus.OK);
}
 

 

반환 타입 - Page, Slice, List

Spring Data JPA는 페이징을 위한 반환타입도 제공한다.

  • Page: count 쿼리 결과를 포함하는 페이징
  • Slice: count 쿼리 없이 다음 페이지만 확인 가능 (내부적으로 limit + 1 조회)

 

다음 테스트 예시를 보자.

@Test
public void paging() {
    //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));
  
    Pageable pageable = PageRequest.of(0, 3);
  
    //when
    Page<Member> page = memberRepository.findAll(pageable);
    Slice<Member> slice = memberRepository.findAll(pageable);
  
    //then
    assertThat(page.getTotalElements()).isEqualTo(5);  // 페이지 상관 없이 조회할 수 있는 element의 총 개수
    assertThat(page.getNumber()).isEqualTo(0);  // 현재 페이지 번호
    assertThat(page.getTotalPages()).isEqualTo(2);  // 조회할 수 있는 페이지 개수
}
 

DB에 member 데이터 5개를 save해놓고,

page=0&size=3에 대한 Pageable에 대해

각 반환 타입에 맞게 findAll 메서드를 호출했다.

 

실제 조회 쿼리를 찍어보자.

먼저 Page타입은 다음과 같다.

select
    member0_.member_id as member_i1_0_,
    member0_.age as age2_0_,
    member0_.team_id as team_id4_0_,
    member0_.username as username3_0_
from
    member member0_
order by
    member0_.age asc limit ? offset ?


select
    count(member0_.member_id) as col_0_0_
from
    member member0_
 

이렇게 카운트 쿼리를 통해 페이징시 전체 데이터 개수 등 필요한 적정한 데이터를 저장한다. (테스트 코드 then절 참고)

 

Slice타입은 다음과 같다.

select
    member0_.member_id as member_i1_0_,
    member0_.age as age2_0_,
    member0_.team_id as team_id4_0_,
    member0_.username as username3_0_
from
    member member0_
order by
    member0_.age asc limit ? offset ?
 

카운트 쿼리가 따로 없다. Slice는 limit(size) + 1 만큼만 값을 가져오기 때문이다.

 

실무

활용

Q: Page와 Slice는 각각 어느 상황에 사용하면 좋을까?

우리가 웹에서 게시판을 볼 때, 10개의 게시글마다 페이지 단위로 넘겨서 볼 때가 있는 반면에, (Page)

10개의 게시글이 쭉 나오고 맨 아래 "더보기"와 같은 버튼이 있어 클릭하면 다음 10개의 게시글을 보여주는 방식도 있다. (Slice)

즉, 각자 화면을 구성하는 상황에 따라 다른 반환 타입을 사용할 수 있다.

 

+) Page, Slice 대신 List를 사용하면 페이징을 하되 결과를 페이징 정보 없이 단순 List로 받을 수 있다.

따라서 카운트 쿼리는 실행되지 않는다.

 

성능

Slice는 카운트 쿼리가 나가지 않고 다음 slice가 존재하는지 여부만 확인할 수 있기 때문에, 데이터 양이 많을수록 slice를 사용하는 것이 성능상 유리하다.

 

정리하자면,

page는 게시판 같이 총 데이터 개수가 필요한 상황에서,

slice는 모바일과 같이 총 데이터 개수가 필요없는 상황에서(무한스크롤)

각각 필요한 용도에 알맞게 쓰면 된다.

 

엔티티를 DTO로 변환

API 통신을 하다보면 엔티티가 아닌 DTO로 반환하는 것이 좋다.

만약 Page<Memeber>를 페이징 정보를 유지하면서 Page<MemberDto>로 변환하고 싶다면 어떻게 해야 할까?

 

Page 혹은 Slice는 이를 위해 map() 메서드를 지원한다.

Page<Member> page = memberRepository.findAll(pageable);
Page<MemberDto> memberDtos = page.map(m -> new MemberDto(m.getId(), ...생성자 형식));
 

---

 

참고자료

https://zayson.tistory.com/entry/Spring-Data-JPA의-Page와-Slice

https://velog.io/@dltkdgns3435/SpringBoot-Spring-Data-JPA-에서-Page와-Slice

https://yoonbing9.tistory.com/38

https://gksdudrb922.tistory.com/115#%EC%-A%A-%ED%--%--%EB%A-%--%--%EB%-D%B-%EC%-D%B-%ED%--%B-%--JPA%--%ED%-E%--%EC%-D%B-%EC%A-%--%EA%B-%BC%--%EC%A-%--%EB%A-%AC

 

Hola

저작자표시 비영리 동일조건 (새창열림)

'java > spring' 카테고리의 다른 글

[Spring] @TransactionalEventListener에서 예외가 발생하지 않는 이슈  (2) 2023.06.23
[Spring] 스프링 디렉터리 패키지 구조  (0) 2023.06.22
[Spring] @ControllerAdvice, 특정 예외 발생 시 404에러가 발생하는 이슈  (0) 2023.06.21
[Spring] Spring AOP와 실무 응용  (0) 2023.01.12
[Spring] PostgreSQL - PostGIS, JPA를 통해 공간 데이터 다루기  (0) 2023.01.12
    'java/spring' 카테고리의 다른 글
    • [Spring] @TransactionalEventListener에서 예외가 발생하지 않는 이슈
    • [Spring] 스프링 디렉터리 패키지 구조
    • [Spring] @ControllerAdvice, 특정 예외 발생 시 404에러가 발생하는 이슈
    • [Spring] Spring AOP와 실무 응용
    danuri
    danuri
    IT 관련 정보(컴퓨터 지식, 개발)를 꾸준히 기록하는 블로그입니다.

    티스토리툴바