Spring Data JPA에서는 페이징 처리를 쉽게하기 위한 Pageble객체를 제공한다.
사용법은 다음과 같다.
PageRequest.of((페이지), (페이지 사이즈), (정렬))
ex)
Pageable pageable = PageRequest.of(0, 10, Sort.by(DESC, "id");
위와 같이 @Query와 @EntityGraph로 Member와 ManyToOne관계인 Team까지 탐색하도록 쿼리를 작성했다.
Pageable객체를 생성하여 테스트 해보자
이름이 "memberA"인 테스트용 Member 엔티티 120개를 생성 후, Pageable 객체에 0페이지, 페이지당 10건씩, id를 기준으로 오름차순 정렬을 하도록 생성 후 findMembers메소드를 호출했다.
쿼리는 다음과 같다.
여기서 중요한 점은 밑에 생성하지 않았던 count쿼리가 호출되었다는 것이다.
페이징 처리를 할때는 count 쿼리가 꼭 필요하다. count 쿼리가 있어야 전체 페이지를 구할 수 있기 때문이다.
하지만 Pageable객체를 사용할 때 주의할 점이 있다.
위 예제는 @EntityGraph로 탐색할 객체를 정했고, 실제 @Query에 작성된 JPQL을 SQL로 변환 하자면 select m from member m where m.id = id이기 때문에 카운트 쿼리도 이 JPQL을 사용하여 select count(m) from member m where m.id = id로 만들어 졌다.
Spring Data JPA가 생성해주는 count 쿼리는 @Query에 명시된 JPQL을 그대로 사용하여 count 쿼리를 생성한다. @Query에서 페치 조인을 사용였기 때문에, count 쿼리에도 페치 조인이 사용된다. 페치 조인은 객체 그래프를 탐색하여 조회하는 기능이기 때문에 조회 결과도 객체 그래프의 엔티티 여야한다. 하지만 카운트 쿼리는 Long타입의 정수를 조회하기 때문에 예외가 발생하는 것이다.
엔티티가 아닌, DTO로 조회할 경우 페치 조인을 사용했을때 예외가 발생하는 것과 같은 이유다.
@EntityGraph로 객체 그래프를 조회할 경우 left outer join이 발생하게 되므로 페치 조인 쿼리가 빠진 JPQL로 count 쿼리가 생성되어도 상관 없다.
innerJoin의 경우 join에 따라 count 쿼리의 결과값이 달라져 join이 필요하지만, leftJoin의 경우 count 쿼리에 영향을 주지 않아서 count 쿼리에 join을 할 필요가 없다. 하지만 Spring Data JPA가 생성해주는 count 쿼리는 @Query에 명시된 JPQL을 사용하여 불필요한 join이 걸릴 수 있다. 이 경우 쿼리 최적화를 위해 별도의 count 쿼리를 작성하여 사용하도록 하자.
Querydsl을 사용 할 경우 카운트 쿼리가 자동으로 생성되지 않는다.
반드시 별도의 카운트 쿼리를 작성해야 한다.
PageableExecutionUtils를 사용하여 반환하게 되면 총 카운트가 페이지 사이즈 보다 작은경우와 마지막 페이지를 조회하는 경우에 count 쿼리를 생략한다.
덕분에 좋은 정보 잘 보고 갑니다.
감사합니다.