Spirng Data JPA Pageable 카운트 쿼리

theonde·2022년 11월 1일
0
post-thumbnail

Pageable

  • Spring Data JPA에서는 페이징 처리를 쉽게하기 위한 Pageble객체를 제공한다.

  • 사용법은 다음과 같다.

PageRequest.of((페이지), (페이지 사이즈), (정렬))

ex)
Pageable pageable = PageRequest.of(0, 10, Sort.by(DESC, "id");
  • 위의 Pageable객체를 Spring Data JPA Repository에 인자로 전달하면, 사용자가 페이징 쿼리(JPQL)을 만들지 않아도 Spring Data JPA가 파라미터로 받은 Pageble객체로 페이징, 정렬 쿼리를 만들어 준다.

  • 위와 같이 @Query와 @EntityGraph로 Member와 ManyToOne관계인 Team까지 탐색하도록 쿼리를 작성했다.

  • Pageable객체를 생성하여 테스트 해보자

  • 이름이 "memberA"인 테스트용 Member 엔티티 120개를 생성 후, Pageable 객체에 0페이지, 페이지당 10건씩, id를 기준으로 오름차순 정렬을 하도록 생성 후 findMembers메소드를 호출했다.

  • 쿼리는 다음과 같다.

  • 여기서 중요한 점은 밑에 생성하지 않았던 count쿼리가 호출되었다는 것이다.

Count Query

  • 페이징 처리를 할때는 count 쿼리가 꼭 필요하다. count 쿼리가 있어야 전체 페이지를 구할 수 있기 때문이다.

  • 하지만 Pageable객체를 사용할 때 주의할 점이 있다.

Spring Data JPA는 @Query에서 작성한 JPQL을 그대로 사용하여 카운트 쿼리를 만든다.

@EntityGraph 사용

  • 위 예제는 @EntityGraph로 탐색할 객체를 정했고, 실제 @Query에 작성된 JPQL을 SQL로 변환 하자면 select m from member m where m.id = id이기 때문에 카운트 쿼리도 이 JPQL을 사용하여 select count(m) from member m where m.id = id로 만들어 졌다.

    • 이 경우엔 전혀 문제가 되지 않는다. 불필요한 join쿼리가 발생하지도 않고, count 쿼리에 꼭 필요한 쿼리만 들어간다.

fetch join 사용

  • @EntityGraph를 사용하지 않고 @Query에서 페치 조인을 사용한 경우 예외가 발생한다.

  • 위와 같이 페치 조인을 사용한 쿼리를 실행하여 보자

  • 결과는 java.lang.IllegalStateException: Failed to load ApplicationContext가 발생한다.

이유는?

Spring Data JPA가 생성해주는 count 쿼리는 @Query에 명시된 JPQL을 그대로 사용하여 count 쿼리를 생성한다. @Query에서 페치 조인을 사용였기 때문에, count 쿼리에도 페치 조인이 사용된다. 페치 조인은 객체 그래프를 탐색하여 조회하는 기능이기 때문에 조회 결과도 객체 그래프의 엔티티 여야한다. 하지만 카운트 쿼리는 Long타입의 정수를 조회하기 때문에 예외가 발생하는 것이다.
엔티티가 아닌, DTO로 조회할 경우 페치 조인을 사용했을때 예외가 발생하는 것과 같은 이유다.

해결

  1. @EntityGraph 사용
  • @EntityGraph를 사용하게되면 @EntityGraph가 페치 조인의 역할을 하기 때문에@Query(JPQL)에 페치 조인을 작성하지 않아도 된다. 따라서 countQuery가 자동으로 생성된다. (젤 위의 예시 참조)

@EntityGraph로 객체 그래프를 조회할 경우 left outer join이 발생하게 되므로 페치 조인 쿼리가 빠진 JPQL로 count 쿼리가 생성되어도 상관 없다.

  1. 엔티티 조회 방식을 DTO 조회 방식으로 변경

  • 위와 같이 DTO로 직접 조회하게 되면 페치 조인을 할 필요가 없게 되어 자동으로 만들어지는 count 쿼리에 예외가 발생하지 않게 된다.
  1. 별도의 count 쿼리를 작성
  • 위와 같이 count 쿼리에 별도의 카운트 쿼리를 작성한다.

innerJoin의 경우 join에 따라 count 쿼리의 결과값이 달라져 join이 필요하지만, leftJoin의 경우 count 쿼리에 영향을 주지 않아서 count 쿼리에 join을 할 필요가 없다. 하지만 Spring Data JPA가 생성해주는 count 쿼리는 @Query에 명시된 JPQL을 사용하여 불필요한 join이 걸릴 수 있다. 이 경우 쿼리 최적화를 위해 별도의 count 쿼리를 작성하여 사용하도록 하자.

QueryDSL

  • Querydsl을 사용 할 경우 카운트 쿼리가 자동으로 생성되지 않는다.

  • 반드시 별도의 카운트 쿼리를 작성해야 한다.

PageableExecutionUtils를 사용하여 반환하게 되면 총 카운트가 페이지 사이즈 보다 작은경우와 마지막 페이지를 조회하는 경우에 count 쿼리를 생략한다.

profile
개발자ㅋ.ㅋ

1개의 댓글

comment-user-thumbnail
2024년 1월 26일

덕분에 좋은 정보 잘 보고 갑니다.
감사합니다.

답글 달기