1. 의존성 설정
2. JPAQueryFactory 빈으로 등록하기
3. Querydsl을 사용할 인터페이스와 구현 클래스 만들기
4. 확인해 보기
kotlin으로 querydsl을 사용하기 위해 작업했던 내용을 기록으로 남겨보고자 합니다.
실제 코드는 https://github.com/gyuhwanhwang/my-book-gallery-kotlin 여기에서 확인하실 수 있습니다.
1. 의존성 설정
먼저, querydsl을 사용하기 위해선 `jakarta.persistence.@Entity` 애너테이션을 스캔하여 Q-types를 만들어내야 합니다.
그러기 위해선 kapt(Kotlin Annotation Processing Tool) 플러그인을 사용하여 Kotlin에서도 Java 애너테이션 프로세서를 사용할 수 있게 해줘야 합니다.
https://kotlinlang.org/docs/kapt.html
kapt compiler plugin | Kotlin
kotlinlang.org
kapt가 공식적으로 추가 개발이 없는 유지 모드에 들어갔다고 이야기하며, ksp로 대신할 것을 권장하고 있습니다. 그런데 querydsl github에 ksp 마이그레이션 문제가 아직 오픈 이슈로 남아있고, 공식 문서랑 해외 쪽 자료 찾아봐도 ksp가 querydsl을 지원한다는 것을 찾을 수가 없네요 ㅜㅜ 아직은 kapt를 사용해야 될 것 같습니다.
먼저 `build.gradle.kts`에 kapt 플러그인을 추가해 줍니다. 저는 이전에 이미 추가해 놓아서 properties라고 주석이 적혀있는데, properties도 Java 애너테이션 프로세서를 사용해야 해서 그렇습니다.
위에서와 같이 querydsl 버전을 변수로 선언해 주고, 디펜던시에 추가해 주었습니다.
querydsl-apt에서 kapt 플러그인 사용하여 디펜던시를 추가해 줍니다.
gradle을 새로고침 해주고, 스프링 부트를 다시 실행시키면 다음과 같이 Q-type들이 만들어지게 됩니다.
2. JPAQueryFactory 빈으로 등록하기
querydsl을 사용하는 클래스에서 엔티티 매니저를 주입받고, 생성자에서 JPAQueryFactory를 초기화해서 사용해도 되지만, 좀 더 편하게 사용하기 위해 설정 클래스에 빈을 선언해 주었습니다.
아래 빈 선언처럼 간단한 문법을 작성할 때 코틀린이 참 매력적으로 느껴지는 것 같습니다.
3. Querydsl을 사용할 인터페이스와 구현 클래스 만들기
Querydsl을 위한 인터페이스와 이를 구현한 클래스, 즉 querydsl을 직접 사용하는 클래스를 만들 차례입니다.
인터페이스 선언은 특별할 게 없습니다. 페이징 기능을 사용할 거라 Spring Data의 Pageable을 인자로, Page를 반환값으로 설정했습니다.
위에서 만든 인터페이스를 구현하는, 실제 querydsl을 사용하는 클래스입니다. 위에서 빈으로 등록해 주었던 JPAQueryFactory를 주입받아 사용합니다.
여기서는 문제가 없는 것처럼 보이지만, 처음에 기존에 java에서 사용하던 대로 코드를 작성하려니 아래와 같은 두 가지 문제가 있었습니다.
1) Type missmatch
limit은 인자로 Long 타입을 받는데, pageable.getPageSize() 메서드는 int 값을 반환하고 있었습니다.
저기서 바로 .toLong() 메서드를 호출해도 되지만, 다른 querydsl 쿼리를 작성할 때도 동일한 문제가 반복될 거기에 Kotlin의 확장 함수 기능을 사용해 보았습니다.
위와 같이 JPAQuery 수신 객체가 limit 함수에 대해 내부적으로 toLong()을 호출하도록 하여, toLong() 메서드를 반복적으로 사용하지 않도록 작성해 보았습니다.
2) LongSupplier
마찬가지로 Type missmatch 문제가 또 있었는데, PageableExecutionUtils.getPage() 메서드의 마지막 인자가 다음과 같이 LongSupplier로 되어 있습니다. 문제는 fetchOne() 메서드의 결과가 null을 허용하기 때문에 타입 불일치가 발생하고 있었습니다.
LongSupplier는 아래와 같이 함수형 인터페이스로 되어있습니다.
실제 getPage() 메서드를 확인해 보면, total count를 계산해야 하는 경우에만 LongSupplier를 호출하여 값을 계산하고 있습니다.
그런데 AbstarctJPAQuery 추상 클래스의 fetchOne() 메서드를 확인해 보면 null을 반환할 수 있게 되어있습니다.
여기서는 엘비스 연산자 `?:` 를 사용하여 fetchOne()이 null을 반환할 경우에는 무조건 0L을 반환하게 하여 null 가능성을 제거하고 LongSupplier의 조건을 충족시켜 주었습니다. 그리고 Kotlin의 문법 중 하나인, 메서드의 마지막 인자가 함수를 받게 되면 람다식을 분리할 수 있는 문법을 적용해 보았습니다.
4. 확인해 보기
in-going 어댑터인 웹 컨트롤러에서 요청이 들어오면서부터, out-going 어댑터인 영속성 계층까지 요청이 전달되는 과정은 해당 포스트에서 굳이 다루진 않겠습니다.
스프링 부트를 재실행하고 설정한 엔드포인트를 호출하면 아래와 같이 결과가 반환됨을 확인할 수 있습니다.
실제 쿼리도 잘 수행되고 있습니다. count 쿼리를 따로 작성하였기 때문에 쿼리가 따로 호출됩니다.
총 테스트 데이터를 30개를 넣었는데 size를 40개 요청하면 위에서 살펴봤던 LongSupplier의 getAsLong()이 호출되지 않을 것입니다. 따라서 count 쿼리도 나가지 않습니다.
일단 querydsl이 정상 작동하는지 확인하기 위해서 다른 최적화들을 진행하지 않았는데, 다음에는 적용할 수 있는 최적화들에 대해서 다뤄보겠습니다.
'JPA > Querydsl' 카테고리의 다른 글
[Querydsl] SpringBoot 3 Querydsl 적용해보기 (Dto반환) (0) | 2023.06.21 |
---|