25.04.04 TIL Pager

신성훈·2025년 4월 4일
0

TIL

목록 보기
162/162

1. 페이징이 필요한 이유

  • 대용량 데이터를 한 번에 모두 가져오면 성능 이슈 발생
  • 클라이언트에 필요한 만큼만 나눠서 전송 → 네트워크/DB 부하 감소
  • 사용자 입장에서도 스크롤 또는 페이지별로 보기가 훨씬 효율적

2. 개념

개념설명
Pageable페이징 요청 정보를 담는 인터페이스 (page, size, sort 등)
Page<T>전체 데이터 수, 페이지 수 등 메타데이터 포함한 페이징 결과
Slice<T>다음 페이지 여부만 제공하는 경량 페이징 결과
List<T>단순 목록 (페이징 정보 없음)

3. 기본 사용법

public interface MemberRepository extends JpaRepository<Member, Long> {
    Page<Member> findByAge(int age, Pageable pageable);
}
Pageable pageable = PageRequest.of(0, 10, Sort.by("username").descending());
Page<Member> result = memberRepository.findByAge(20, pageable);
  • PageRequest.of(page, size) → page: 0부터 시작
  • Sort는 여러 필드 조합 가능

4. Page와 Slice 차이

구분PageSlice
전체 count 조회OX
다음 페이지 존재 여부OO
총 페이지 수OX
성능낮음 (count 쿼리 포함)높음 (count 없음)
Slice<Member> findByUsername(String username, Pageable pageable);
  • 무한 스크롤처럼 다음 페이지가 있는지만 판단할 때는 Slice가 유리

5. 응답 데이터 변환 (Page → DTO)

엔티티 그대로 반환하면 안 되므로, DTO로 변환 필요

Page<MemberDto> dtoPage = result.map(m -> new MemberDto(m.getUsername(), m.getAge()));

6. 커스텀 count 쿼리 분리

복잡한 조인 등으로 인해 count 쿼리 최적화가 필요할 때는 다음과 같이 분리

@Query(
  value = "SELECT m FROM Member m LEFT JOIN m.team t",
  countQuery = "SELECT count(m) FROM Member m"
)
Page<Member> findCustomPage(Pageable pageable);

7. 컨트롤러에서 사용 예시

@GetMapping("/members")
public Page<MemberDto> getMembers(@PageableDefault(size = 10) Pageable pageable) {
    return memberRepository.findAll(pageable)
            .map(MemberDto::new);
}
  • @PageableDefault로 기본 사이즈 지정 가능
  • 프론트에서 ?page=0&size=10&sort=username,desc와 같이 요청

8. 마무리

처음엔 그냥 List<T>로 다 가져오는 게 편했지만 사용자 수가 늘어나고 데이터가 많아지면서 페이징 처리가 꼭 필요하다는 걸 알게 되었다. Page, Slice, Pageable을 잘 활용하면 성능과 사용자 경험 모두를 잡을 수 있다는 점에서 꼭 숙지해야 할 기능이다.
QueryDSL과 함께 사용할 때도 .offset().limit()로 수동 페이징이 가능하니 추후에 그 내용도 함께 익혀야겠다.

profile
조급해하지 말고, 흐름을 만들고, 기록하면서 쌓아가자.

0개의 댓글