프로젝트를 하면서 Window Function 의 row_number()
을 사용하게 될 일이 생겼다. 해당 포스팅에서는 QueryDSL 에서 Window Function 의 row_number() 을 사용하는 법에 대해 소개하려 한다.
row_number 함수란
ROW_NUMBER()
는 특정한 Column 을 기준으로 같은 값을 갖고있는 row 에 번호를 명시하는 함수
이다. 아래 쿼리는 comment 의 parent_id
가 같은 row 에 순서대로 번호를 명시하라는 것이다.
QueryDSL 이 사용하는 객체 소개
QueryDSL 은 JPQL 기반으로 동작하기 때문에 JPQL 에서 Window Function 을 지원해야 한다. 운이 좋게도 Hibernate 6 버전이상부터는 Window Function 을 제공한다 하기 때문에 사용할 수 있다. 하지만 QueryDSL 의 설정만으로는 부족하여 별도의 설정을 더 해주어야 한다. 설정을 하기 전, QueryDSL 에서 사용되는 내부 객체들을 조금 알아보자.
Ops
Ops는 보다 유연한 Query 를 위해 제공되는 연산자이며, Enum 클래스로 정의되어 있다. 또한 해당 Ops 가 어떤 타입을 리턴하는지에 대한 값도 명시되어있다.
Template
Template 은 Ops 를 통해 어떠한 Query 로 변경시킬 것인가에 대한 쿼리 템플릿을 제공해주는 역할을 한다. 이러한 Template 에는 기본으로 사용될 템플릿들이 저장된 DEFAULT 인스턴스가 만들어져 있다.
또한, 이 Template 이 초기화 될 때, add 연산을 통해 IdentityHashMap<Operator, Template>
에 저장되게 된다.
JPQLTemplate
JPQLTemplate 은 앞서 앞서 소개한 Template 을 확장한 클래스이다. 마찬가지로 해당 JPQLTemplate 이 초기화될때 add 메서드를 통해 IdentityHashMap<Operator, Template>
에 저장되게 된다. 이 JPQLTemplate 은 우리가 QueryDSL 을 사용할 때 만들게 되는 JPAQueryFactory
에서 내부적으로 사용된다.
하지만 일반적으로 JPAQueryFactory 에는 EntityManger 하나만 넣어주게 되는데, 이렇게 되면JPAQueryFactory 가 사용할 template 에 null 이 들어가게 된다. 그렇기 때문에 이 상태로 transform
과 같은 것을 사용하게 되면 Exception
이 터지게 된다.
이를 해결하려면 JPAQueryFactory 를 만들때, JPQLTemplate 을 추가적으로 넣어주어야 tranform 과 같은 다른 연산자들을 사용할 수 있다.
QueryDSL 설정 (방법 1)
아 이제 윈도우 함수중 하나인 row_number 함수를 사용하기 위한 방법을 알아보자.
의존성 설치
우선 QueryDSL 에서 Window Function 을 사용하려면 Gradle 에 아래의 의존성을 설치해주어야 한다.
해당 의존성을 설치해주면 SQLOps
와 SQLExpressions
가 생기게 되고, row_number() 를 사용하기 위한 Operation 이 정의되어 있다. 참고로 SQLOps 는 앞서 소개한 Ops 가 구현하고 있던 Operator 인터페이스를 구현한 클래스이다.
커스텀 JPQLTemplate
하지만 QueryDSL 에서 바로 row_num() 을 사용하려하면 해당 메서드를 지원하지 않는다는 오류가 발생하게 된다.
JPAQueryFactory 설정
이를 해결하기 위해 JPQLTemplate 을 상속하여 SQLOps.ROWNUMBER
와 Template 패턴을 IdentityHashMap<Operator, Template>
에 add 시켜주고, 해당 Template 을 JPAQueryFactory 에 추가해주어야 한다.
결과 확인
이제 아래와 같이 querydsl 코드를 작성하고 결과를 보게 되면
row_number() 가 잘 나가는 것을 확인할 수 있고, rn 의 결과가 잘 나오는 것을 볼 수 있다.
parent comment id : 1
---> comment id : 6, row_number : 1
parent comment id : 1
---> comment id : 7, row_number : 2
parent comment id : 1
---> comment id : 8, row_number : 3
parent comment id : 1
---> comment id : 9, row_number : 4
parent comment id : 2
---> comment id : 10, row_number : 1
parent comment id : 2
---> comment id : 11, row_number : 2
parent comment id : 2
---> comment id : 12, row_number : 3
parent comment id : 2
---> comment id : 13, row_number : 4
parent comment id : 3
---> comment id : 14, row_number : 1
parent comment id : 3
---> comment id : 15, row_number : 2
parent comment id : 3
---> comment id : 16, row_number : 3
parent comment id : 3
---> comment id : 17, row_number : 4
Blaze Expressions 설정 (방법 2)
의존성 설치
querydsl-sql
을 사용한 방법은 커스텀 JQPLTemplate
을 만들어서 직접 SQLOps 와 Template 패턴을 IdentityHashMap<Operator, Template>
에 넣어주어야 했다. 하지만, blaze-persistence-integration
를 사용하면 이러한 별도의 설정 없이 row_number() 를 사용할 수 있다.
JPAQueryFactory 설정
해당 의존성을 설치하였으면 JPAQueryFactory 를 만들 때 JPQLNextTemplates.DEFAULT
를 넣어주면 된다.
이제 SQLExpressions 가 아니라 JPQLNextExpressions
를 사용하여 쿼리를 작성해주면 된다. Blaze Expressions 가 좋은점은 Order By 를 사용할 수 있다는 것이다.
이것이 가능한 이유는 blaze-persistence-integration
를 설치하고나서 생기는 JPQLNextTemplates
때문이다. JPQLNextTemplates 는 JPQLTemplates 를 확장한 클래스이기 때문에, JPQLTemplates 이 갖고있는 Template 도 갖고있고, JPQLNextTemplates 만이 갖고 있는 Template 들이 생성자에서 초기화 되기 때문이다.
ROW_NUMBER 에 대한 템플릿이 추가되어있는 것을 볼 수 있으며
또한, 물론 Order By 에 대한 템플릿도 추가되어있다.
둘다 사용 (방법 3)
querydsl-sql
과 Blaze Expressions
를 둘다 사용하려면 JPQLNextTemplates 를 확장해주고, querydsl-sql 의 템플릿을 추가시켜주면 된다.
Reference
https://programmingnote.tistory.com/85
https://stackoverflow.com/questions/40744385/querydsl-4-with-rownumber-window-function
https://stackoverflow.com/questions/30797892/querydsl-window-functions
https://www.tabnine.com/code/java/methods/com.querydsl.sql.WindowFunction/partitionBy
https://m.blog.naver.com/leejongcheol2018/222042407888
https://www.baeldung.com/blaze-persistence-tutorial
https://persistence.blazebit.com/documentation/1.6/core/manual/en_US/#subquery-in-from-clause