JPA

Spring Data JPA 페이징 처리

빈코 2022. 10. 14. 14:02

지난 포스팅에서는 Spring Data JPA를 이용해 쿼리 메서드를 처리하는 방법에 대해 알아보았습니다. 오늘은 페이징 처리에 관해 이야기해보려 해요. 대게 실무에서는 페이징 처리를 거의 필수적으로 사용하는데, 복잡한 코드들이 Spring Data JPA에서는 얼마나 간편하게 해결할 수 있는지 살펴볼게요😄

 

로고
Paging

개요

페이징 처리를 한번이라도 직접 구현해보셨다면 생각보다 복잡한 것을 아실 텐데요. 예를 들어, 단순히 1페이지에 10개씩 데이터만 가져오는 것이 아닌 조건에 맞는 데이터의 총 개수와 마지막 페이지가 몇 인지, 다음 페이지가 있는지에 대한 유무 등 클라이언트의 요구 조건에 맞게 구현해야 합니다.

 

저도 처음 회사 입사했을 때 JPA를 사용하지 않았기 때문에 손수 이전에 계셨던 선배님들이 짜놓았던 페이징 처리 코드들을 가져다 쓰는 것만으로 끝났었는데, 이번에 Spring Data JPA를 공부하면서 그런 복잡한 코드들을 다 처리해주는 점에 상당히 놀라웠습니다.

 

그럼 기존 JPASpring Data JPA 각각의 페이징 처리 방법이 어떻게 다른지 알아볼까요?

 

순수 JPA Paging📒

먼저 직접 구현해보는 순수 JPA 페이징 처리에 대해 알아볼게요. 페이징의 조건으로는 아래와 같습니다.

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

 

📌 이전 포스팅에서 만들었던 MemberJpaRepository에 코드를 작성하시면 됩니다.

 

public List<Member> findByPage(int age, int offset, int limit) {
    return em.createQuery("select m from Member m where m.age = :age order by m.username desc")
            .setParameter("age", age)
            .setFirstResult(offset)
            .setMaxResults(limit)
            .getResultList();
}

 

JPQLcreateQuery를 사용해서 검색 조건과 정렬조건에 맞게 쿼리를 작성하고, offsetlimit 매개변수를 넘겨 받아 각각 항목에 맞게 설정한 후 반환해 주는 로직으로 구성했어요.

 

하지만, 여기서 끝난 것이 아닙니다. 검색 조건에 맞는 데이터의 총개수도 구해야 하기 때문에 따로 totalCount 함수를 구성해야 합니다.

 

public long totalCount(int age) {
    return em.createQuery("select count(m) from Member m where m.age = :age", Long.class)
            .setParameter("age", age)
            .getSingleResult();
}

 

📌 totalCount는 한 개의 값을 반환하기 때문에 getSingleResult를 써야 한다는 점 참고해주세요😀


쿼리가 알맞게 나가는지 테스트 코드를 작성해보겠습니다. 

@Test
public void paging() throws Exception {
    //given
    memberJpaRepository.save(new Member("member1", 10));
    memberJpaRepository.save(new Member("member2", 10));
    memberJpaRepository.save(new Member("member3", 10));
    memberJpaRepository.save(new Member("member4", 10));
    memberJpaRepository.save(new Member("member5", 10));
    int age = 10;
    int offset = 0;
    int limit = 3;
    
    //when
    List<Member> members = memberJpaRepository.findByPage(age, offset, limit);
    long totalCount = memberJpaRepository.totalCount(age);
    
    //then
    assertThat(members.size()).isEqualTo(3);
    assertThat(totalCount).isEqualTo(5);
}

 

데이터 5개를 저장하고 검색조건과 정렬조건, 페이징 조건에 맞게 변수를 선언한 후 이전에 만든 findByPagetotalCount의 함수들을 각각 호출하였습니다.

 

쿼리
쿼리

 

쿼리도 확인해본 결과 문제 없이 요구조건에 맞는 쿼리가 나오는 것을 확인할 수 있습니다, 또한, assertThat으로 비교했던 값들도 알맞게 나오기 때문에 테스트는 문제없이 끝나게 됩니다.

 

하지만, 페이징 처리가 여기서 끝이 날까요? 

페이징 처리가 되려면 총 페이지의 개수, 최초/마지막 페이지의 숫자 등 계산하는 로직도 필요하게 됩니다. 예를 들어, 총페이지의 개수는 총 데이터 수/한 페이지당 보여줄 데이터 수의 계산식이 들어갑니다.

 

이러한 계산식들을 일일히 작성하지 않도록 도와주는 것이 Spring Data JPA입니다. 한번 확인해볼까요?

 

Spring Data JPA Paging📘

상속관계
상속 관계

JpaRepository의 부모 인터페이스인 PagingAndSortingRepository에서 페이징소팅이라는 기능을 제공합니다. 

 

PagingAndSortingRepository
PagingAndSortingRepository

PagingAndSortingRepository의 findAll() 메서드의 반환 타입을 살펴보면 아래와 같은 기능을 제공합니다.

  • 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 쿼리 없이 결과만 반환

 

📌 결론적으로 쿼리 메서드에서 페이징 처리는 매개변수로 Pageable만 넘기면 페이징 처리가 가능하다는 것!


이전 포스팅에서 만든 MemberRepsitory에 코드를 추가합니다.

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

 

바로 테스트 코드를 작성해볼게요.

@Test
public void page() throws Exception {
    //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));

    //when
    PageRequest pageRequest = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC,
            "username"));
    Page<Member> page = memberRepository.findByAge(10, (Pageable) pageRequest);

    //then
    List<Member> content = page.getContent(); //조회된 데이터
    assertThat(content.size()).isEqualTo(3); //조회된 데이터 수
    assertThat(page.getTotalElements()).isEqualTo(5); //전체 데이터 수
    assertThat(page.getNumber()).isEqualTo(0); //페이지 번호
    assertThat(page.getTotalPages()).isEqualTo(2); //전체 페이지 번호
    assertThat(page.isFirst()).isTrue(); //첫번째 항목인가?
    assertThat(page.hasNext()).isTrue(); //다음 페이지가 있는가?
}

given 단계에서 데이터를 넣어주고, when 단계에서 pageRequest를 생성합니다. PageRequest는 시작하는 페이지, 한 페이지당 데이터 수로 구성해서 만들고 추가적으로 Sort 조건도 넣을 수 있습니다. 요구조건에 맞게 한 페이지당 3개의 결과물, 이름 순으로 내림차순 정렬로 구성했어요.

 

이때, 주의해야 할 것은 JPA의 페이징 처리는 0페이지부터 시작인 것을 주의해주세요.

 

그런 다음 Repository에 추가했던 findByAge에 나이와 PageRequest를 넣어주면 페이징 처리된 결과를 받을 수 있습니다. 나이 조건은 쿼리 메서드의 이름으로 작성하는 메서드로 처리가 되는 것은 아시죠!?

 

then 단계를 살펴보면 주석 처리된 것 처럼 다양한 페이징 기능들을 사용할 수 있습니다. 조회된 데이터부터 다음 페이지의 유무까지 많은 기능들을 JPA가 제공합니다.

 

📌 위 page는 Member Entity이기 때문에 API(Controller)에서 외부로 반환하면 안 됩니다. 밑에 방식으로 DTO 형식으로 변환하고 반환하셔야 합니다.

 

Page<MemberDto> DtoMap = page.map(member -> new MemberDto(member.getId(), member.getUsername(), null));

Dto는 이전 포스팅에서 작성했지만 어떤 로직에 사용하는가에 따라 달라지기 때문에 생성자에 무엇을 넣을지는 중요한 포인트는 아닌 것 같고, page.map을 이용하여 쉽게 Dto 형식으로 바꿀 수 있다는 것만 알아두시면 될 것 같네요.

 

 


👨‍👩‍👦‍👦 오픈채팅방 운영

취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁

 

참여코드 : 456456

https://open.kakao.com/o/gVHZP8dg

 

비전공 개발자 취업 준비방(질문&답변)

#비전공 #개발자 #취업 #멘토링 #부트캠프 #국비지원 #백엔드 #프론트엔드 #중소기업 #중견기업 #자바 #Java #sql

open.kakao.com

 


👨‍💻 전자책 출간

아울러 제가  🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁

https://kmong.com/gig/480954

 

비전공개발자 2년만에 중견기업 들어간 방법 | 14000원부터 시작 가능한 총 평점 0점의 전자책, 취

0개 총 작업 개수 완료한 총 평점 0점인 Binco의 전자책, 취업·이직 전자책 서비스를 0개의 리뷰와 함께 확인해 보세요. 전자책, 취업·이직 전자책 제공 등 14000원부터 시작 가능한 서비스

kmong.com


 

마치며

지금까지 Spring Data JPA의 Paging 처리에 대해 알아보았습니다😄 처음에는 익숙하지 않을 수 있지만, 사용하다 보면 정말 강력한 기능을 제공하는 것을 몸소 느낄 것 같아요. 다음 포스팅은 Spring Data JPA의 Bulk Update와 EntityGraph를 소개할게요.

 

Spring Data JPA Series

1. Spring Data JPA와 기존 JPA의 차이점 및 사용법

2. Spring Data JPA 쿼리 메서드

3. Spring Data JPA 페이징 처리

4. Spring Data JPA Bulk Update & Entity Graph

Reference

실전! 스프링 데이터 JPA - 김영한

 

반응형