블로그

Spring Data JPA에서 @Query를 통한 페이징 처리 시 주의점 - [1부]

등록일
2024-08-07 15:36:06
조회수
432

안녕하세요, Softeer 개발자 김동환입니다.


본 글에서는 Spring Data JPA @Query를 사용하여 페이징 처리를 할 때 발생했던 이슈와 해결 과정을 공유 드리려 합니다.


결론부터 말씀 드리면,

'NativeQuery를 사용하면서 페이징 처리를 하려고 한다면 countQuery를 반드시 작성하자!' 입니다.

이는 spring-data-jpa 공식 문서에도 적혀져 있는 내용입니다.

(https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html#jpa.query-methods.at-query.native)


이제부터는 마주쳤던 문제 상황 / @Query 동작 프로세스 확인 / 문제 발생 원인 파악 및 대책 순으로 이어집니다.


[문제 상황]

그냥 예시로 만든 코드이기 때문에, 양해 부탁 드립니다.


만약 아래와 같이 @Query 내에 countQuery를 따로 지정해주지 않고 페이징 처리를 하게 되면,


다음과 같이 잘못된 동적 쿼리가 만들어집니다.

count(h)과 같이 엉뚱한 쿼리가 들어가게 됩니다.


countQuery를 직접 지정하지 않아, 대신 쿼리를 생성해준 것 같은데, 그 프로세스를 살펴봐야 할 것 같습니다.


[@Query 동작 프로세스]

1. @Query를 사용 시, 쿼리 객체를 생성하기 위해 JpaQueryLookUpStrategy를 호출하게 됩니다.

2. 만드는 전략은 여러 가지가 있는데, 내부 로직을 살펴보면 @Query 사용 시 DeclaredQueryLookUpStrategy를 채택합니다.

3. 내부 메커니즘에 따라, DeclaredQueryLookUpStrategy 내 오버라이드된 함수인 resolveQuery를 호출하게 됩니다. (쿼리 실행을 위함)

4. 아래 그림과 같이 method.getAnnotatedQuery()를 통해 @Query 내 value 값이 있는 것을 확인하고, 있으면 fromMethodWithQueryString 메서드를 호출합니다.


5. JpaQueryFactory Enum class에 정의된 fromMethodWithQueryString 메서드가 호출되며, 네이티브쿼리이므로 아래와 같이 NativeJpaQuery 객체를 새로 생성합니다.


6. NativeJpaQuery 생성자에서 부모인 AbstractStringBasedJpaQuery를 호출하게 됩니다.


7. AbstractStringBasedJpaQuery 생성자 단에서 this.countQuery에 값을 넣어주는 과정에서, deriveCountQuery를 호출합니다.


8. deriveCountQuery 메서드는 DeclaredQuery interface에 등록되어 있는데, 그 중 StringQuery 구현체에 있는 deriveCountQuery 메서드가 실행됩니다.

이는 AbstractStringBasedJpaQuery 생성자에서 this.query에 넣어준 것이 StringQuery를 상속 받은 ExpressionBasedStringQuery 타입이기 때문입니다.


9. 위 코드를 보면 해결책이 어렴풋이 보이는데요,

1) countQuery를 직접 작성했을 경우, queryEnhancer의 힘을 빌리지 않고 바로 countQuery를 반영하는 것을 알 수 있습니다.

2) 추후에 countQueryProjection이 사용되는 곳이 나오는데, CTE를 사용하지 않았을 경우에 사용할 수 있는 방식입니다.


10. 저는 countQuery를 작성하지 않았기 때문에, createCountQueryFor 함수를 호출하게 됩니다. (countQueryProjection 역시 설정하지 않아 null입니다.)


-- 2부로 이어집니다..

최신 블로그