[Spring JPA] Spring Data JPA 에게 페이징이란?

최동근·2022년 12월 24일
0

JPA

목록 보기
2/13
post-custom-banner

안녕하세요!
쇼핑몰 프로젝트를 진행 중에 관리자 기능으로 회원 목록 API 를 구현 중에 있습니다.
구현하다 보니, 추후 수 많은 데이터가 데이터베이스에 저장될텐데, 어떻게 분할해서 관리자에게 보여줄까 고민하게 되었습니다.
그러던 중 가볍게 알고 있었던(?) 페이징 기법에 대해 공부하기로 결심했고, 이번 글을 통해
스프링 데이터 JPA 를 이용한 페이징 기법에 대해 정리하기로 했습니다.

1. 페이징(Paging) 이란?

페이징이란 사용자가 어떠한 데이터를 요청했을 때, 전체 데이터 중 일부를 원하는 정렬 방식으로 보여주는 방식입니다.
일반적인 규모의 시스템은 많은 데이터를 데이터베이스에 저장하고 있을테니, 모든 데이터를 한번에 응답으로 보내는데 무리가 있겠죠??
이해를 위해 예를 들어보겠습니다.

A 회사 개발자 스프링 군은 기획자의 요구대로 회원의 기본 정보를 보여주는 API 를 구현하려고 합니다.
A 회사는 스타트업 회사지만 사용자에게 매우 편리한 기능을 제공하는 서비스를 제공하고 있기 때문에 매일마다 이용자가 큰 폭으로 증가하고 있습니다.
모든 가입자를 새보니 10000명의 가입자가 존재하군요. 스프링군은 아무 생각 없이 기획자의 요구대로 한번에 10000개의 데이터를 보여주도록 서비스를 설계했습니다.
어떤 결과가 나타날까요?

아마 해당 서비스를 사용하는 클라이언트는 느릭 로딩 속도의 서비스 결과를 얻게 될것입니다.
또한 만약 클라이언트가 보고 싶은 데이터가 거의 마지막에 존재하는 데이터라면 어떨까요? 🥸
원하는 데이터가 나올때까지 하염없이 밑으로 스크롤해야겠죠?
따라서 스프링군은 효과적인 서비스 제공을 위해 '페이징(Paging)' 기법을 생각해냅니다 👨‍💻

사진출처

2. 페이징 기법 구현을 위한 선행 지식

기본적으로 페이징 기능을 구현하기 위해 조금 복잡한 과정을 거쳐야 했습니다 🤔
하지만, 스프링 데이터 JPA 는 개발자가 이용하기 편리하게 페이징 기법을 제공합니다.

자 여기서, 클라이언트가 Http 요청을 날리는 과정을 잠시 생각해 보겠습니다.
클라이언트가 브라우저 주소창에 원하는 자원을 얻기 위해 URI 를 타이핑 합니다.
이때 클라이언트가 요구하는 자원 마지막에 QueryParameter(쿼리 스트링) 이 붙습니다.
만약 페이징 기법을 사용하는 요청이라면 page=3&size=10&sort=id.DESC 이런 식의 QueryParameter 이 붙게됩니다.

  • page -> 페이징 기법이 적용되었을 때, 원하는 페이지
  • size -> 해당 페이지에 담을 수 있는 데이터 개수
  • sort -> 정렬 기준

이런식으로 클라이언트가 Http 요청을 보내게 되면, 해당 데이터는 Pageable 이라는 객체로 매핑되게 됩니다.
지금 부터 페이징을 위해 알고 있어야 하는 클래스 및 인터페이스에 대해 설명드리겠습니다.

  • 스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬 기능을 사용할 수 있도록 2가지 파라미터를 제공한다.
     1. org.springframework.data.domain.Sort : 정렬 기능
     2. org.springframework.data.domain.Pageable : 페이징 기능(내부에 sort 포함)
  • Pageable 사용 시 리턴 타입은 Page 이다.
    1. org.springframework.data.domain.Page : 전체 데이터 건수를 조회하는 count 쿼리 결과를 포함하는 페이징
    2. org.springframework.data.domain.Slice : 추가 count 쿼리 없이 다음 페이지만 확인 가능 (내부적으로 limit + 1조회)
    
    3. List (자바 컬렉션): 추가 count 쿼리 없이 결과만 반환

해당 그림은 Pageable 과 PageRequest 의 관계를 설명하기 위한 이미지 입니다.

  • Pageable 은 인터페이스 이므로 실제로 사용할 때에는 인터페이스를 구현한 PageRequest 객체를 사용한다.
  • PageRequest 생성자의 파라미터는 현재 페이지, 조회할 데이터 수, 정렬 정보를 파라미터로 사용 할 수 있다.

아직 이해가 잘 안가시죠?
이제부턴 간단한 코드 예시로 페이징 사용법을 더욱 구체히 설명 드리겠습니다 🚀

3. 페이징 하는 방법

 // 1. Controller
 @RequestMapping("/companyList")
    public String companyList(Pageable pageable, Model model){
        Page<Company> companyList = companyService.getCompanyPage(pageable);
        model.addAttribute("companyList", companyList);
        return "companyList";
    }

Controller 층입니다.
해당 코드 처럼 사용자가 보낸 페이징 정보는 Pageable 객체로 매핑됩니다.

// 2. Service
    public Page<Company> getCompanyPage(Pageable pageable) {

        int page = (pageable.getPageNumber() == 0) ? 0 : (pageable.getPageNumber() - 1);
        int size = pageable.getPageSize();
        PageRequest.of(page, size, Sort.by(Sort.Direction.DESC));
        return companyRepository.findAll(pageable);
    }

Service 층으로 넘어왔습니다.
getCompanyPage 메소드 이름을 통해 해당 서비스가 회사 정보 리스트를 반환해주는 로직을 구현 한것을 것을 알 수 있겠죠?

  • 클라이언트로 부터 받은 pageNo 와 실제 접근 페이지는 다르다.
    실제 사용자는 페이지를 선택할 때 1,2,3,... 이렇게 선택하게 된다.
    하지만 Page 객체를 페이지가 0부터 시작한다.
  • PageRequest of 메소드에 페이지 번호, 데이터 수, 정렬 기준을 넘겨 Pageable 객체를 반환한다.
  @Repository
   public interface CompanyRepository extends JpaRepository<Company, Long> {
    
    Page<Company> findAll(Pageable pageable);
}

Repository 층입니다.
실제 페이징이 일어나는 부분이며, 앞단에서 설정했던 Pageable 설정에 따라서 페이징이 일어납니다.
여기서 주의깊게 봐야할 부분은 반환타입이 Page 타입이라는 것 입니다.
물론 앞에서 설명드렸던 것처럼 Slice 나 List 타입으로 반환도 가능합니다 💡

4. @ PageDefault 어노테이션

만약 클라이언트가 요청시, Page,Size 그리고 Sort 정보를 쿼리 스트링으로 넘겨주지 않는다면
어떤 일이 발생할까요?

실제 테스트를 해보면, 정렬되지 않은 20개씩 분리된 페이지 중 첫 페이지를 반환하는 것을 확인 할 수 있습니다.
내부 구현을 들여다 보면 PageableHandlerMethodArgumentResolverSupport 에 정의된 fallbackPageable 의 형식으로 반환하는 것을 확인할 수 있습니다.

  • fallbackPageable 은 스프링 데이터 JPA 에 이미 저장된 기본 페이징 정보이다.

즉, 아무 설정 없이 @PageDefault 어노테이션을 사용시, default 설정으로 페이징 기법이 발생합니다.(fallbackPageable)

// Controller
@RequestMapping("/companyList")
    public String companyList(@PageableDefault Pageable pageable, Model model){
        Page<Company> companyList = companyService.getCompanyPage(pageable);

        model.addAttribute("companyList", companyList);
        return "companyList";
    }

해당 코드를 보시면, Controller 단에서 Pageable 객체를 @PageableDefault 어노테이션으로 전달하고 있는 것이 보이시죠? 😎
그런데 만약 개발자가 default 설정을 변경하고 싶다면 어떻게 해야 할까요?

  @RequestMapping("/companyList")
    public String companyList(@PageableDefault(
            page = 0, size = 10, sort = "id", direction = Sort.Direction.DESC)Pageable pageable
            , Model model
    ){
        Page<Company> companyList = companyService.getCompanyPage(pageable);

        model.addAttribute("companyList", companyList);
        return "companyList";
    }

위의 코드 처럼 @PageableDefault 어노테이션에는 다양한 속성이 존재합니다 😎

5. DefaultPage vs FallBackPage

혼동될만한 두 개념을 구분하여 설명하고자 합니다.
DefaultPage의 경우, 개발자가 정한 기본 Page의 형식입니다. 별도로 어노테이션을 통해 설정해주지 않으면 FallbackPage의 설정으로 실행됩니다.
Fallback의 경우 적합한 방식이 없는 경우, 만일을 대비해 만들어 둔 설정입니다. PageableHandlerMethodArgumentResolverSupport에는 FallbackPage만 설정되어 있습니다. DefaultPage는 @PageDefault 어노테이션으로 설정합니다 🙆🏻

참고

페이징/페이징 기법
스프링부트 게시판 만들기

profile
비즈니스가치를추구하는개발자
post-custom-banner

0개의 댓글