[Querydsl] Kotlin Gradle프로젝트에 Querydsl 설정 (feat. kapt)

by 스뎅(thDeng) on

Kotlin과 함께 Querydsl을 쓰려고 설정하는데, 정말 후우 T_T (그래서 기록용으로 메모)

Gradle 설정

Gradle 설정 파일은 kotlin DSL로 작성되었다. spring-data-jpa가 함께 설정된 프로젝트에 Querydsl만 추가했고, 기타 spring 관련 코드는 샘플에서 제외했다.

// /build.gradle.kts

buildscript {
  repositories {
    maven("https://plugins.gradle.org/m2/")
    mavenCentral()
  }

  dependencies {
    classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10")
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61")
  }
}

plugins {
  kotlin("jvm") version "1.3.61
  kotlin("kapt") version "1.3.61" // annotation processing을 위한 kapt

  idea
}

dependencies {
  api("com.querydsl:querydsl-jpa:4.2.2")
  implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

  // kapt로 dependency를 지정해 준다.
  // kotlin 코드가 아니라면 kapt 대신 annotationProcessor를 사용한다.
  kapt("com.querydsl:querydsl-apt:4.2.2:jpa") // ":jpa 꼭 붙여줘야 한다!!"
  kapt("org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final")
}

// 생성된 QClass들을 intelliJ IDEA가 사용할 수 있도록 소스코드 경로에 추가해 준다.
idea {
  module {
    val kaptMain = file("build/generated/source/kapt/main")
    sourceDirs.add(kaptMain)
    generatedSourceDirs.add(kaptMain)
  }
}

여기서 중요한 점 은 annotation processing을 할 수 있도록 kapt를 사용하는 것과, querydsl-apt dependency 마지막에 :jpa를 붙여주는 것이다.

kapt("com.querydsl:querydsl-apt:4.2.2:jpa")

마지막에 :jpa를 붙여주는 것은 JPAAnnotationProcessor를 설정해 주는 것이다. (Integrating Spring Boot + QueryDSL into Gradle Build 참고)

Grenerate QClasses

다음 명령을 실행해서 Querydsl의 도메인 클래스인 QClass를 생성한다.

$ ./gradlew clean compileKotlin

build/generated/source/kapt/main에 클래스 파일들이 생성되는 것을 볼 수 있다.

Querydsl 사용하기

이제 생성된 QClass들을 사용한다. 조심할 점은 Spring Data JPA 문서에 설명된 것 처럼 custom 인터페이스와 구현한 클래스의 이름을 잘 맞춰줘야 한다.

The most important part of the class name that corresponds to the fragment interface is the Impl postfix.

CustomizedOrderRepository라고 인터페이스를 만들었다면, 구현체 이름은 인터페이스에 Impl을 붙인 형태인 CustomizedOrderRepositoryImpl이라고 해야 한다. (아.. 구현체만 rename 했다가 클래스 못 찾아서 겁나 삽질;; 이 내용은 글 가장 아래에 다시 다룬다.)

// /src/main/kotlin/kr/leocat/test/order/OrderRepository.kt
package kr.leocat.test.order

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface OrderRepository : JpaRepository<Order, Long>, CustomizedOrderRepository
// /src/main/kotlin/kr/leocat/test/order/CustomizedOrderRepository.kt
package kr.leocat.test.order

interface CustomizedOrderRepository {
  fun findMyOrder(userId: Long): List<Order>
}
// /src/main/kotlin/kr/leocat/test/order/CustomizedOrderRepositoryImpl.kt
package kr.leocat.test.order

import kr.leocat.test.order.QOrder.order
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport

class CustomizedOrderRepositoryImpl() : QuerydslRepositorySupport(Order::class.java), CustomizedOrderRepository {
  fun findMyOrder(userId: Long): List<Order> =
    from(order)
      .where(order.userId.eq(userId))
      .fetch()
}

Spring Data JPA 네이밍 문제

구현체의 이름을 변경하는 과정에서 인터페이스 이름과 구현체 Impl 클래스 이름이 맞지 않았다. CustomizedOrderRepository 인터페이스와 CustomizedOrderRepositoryImpl 구현 클래스의 이름이 맞지 않으면 아래와 같은 오류를 만날 수 있다. 인터페이스와 클래스 이름을 꼭 다시 확인하자. 인터페이스 이름 마지막에 Impl이 붙어 있는지..

java.lang.IllegalStateException: Failed to load ApplicationContext

      .. (생략) ..

Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List kr.leocat.test.order.CustomizedOrderRepository.findMyOrder(long)! No property findMyOrder found for type Order!
	at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:102)
	at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:106)

      .. (생략) ..

Caused by: org.springframework.data.mapping.PropertyReferenceException: No property findMyOrder found for type Order!
	at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:94)
	at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:382)

Spring Data JPA 문서에는 인터페이스 이름에 Impl을 붙인 이름으로 구현 클래스를 만들라고 하지만, 예전부터 아래처럼 인터페이스는 Custom으로 끝나고 구현 클래스는 Impl로 끝나도록 사용했었고 아직 까지도 문제는 없다. 그래도 언제 바뀔지 모르니 이제는 뒤에 Impl 붙여 보기로..

The most important part of the class name that corresponds to the fragment interface is the Impl postfix.

참고

별도로 명시하지 않을 경우, 이 블로그의 포스트는 다음 라이선스에 따라 사용할 수 있습니다: Creative Commons License CC Attribution-NonCommercial-ShareAlike 4.0 International License