본문 바로가기
Java & Kotlin/Spring

Spring JPA Querydsl 세팅 (with Kotlin)

by devson 2021. 6. 7.

Spring Data JPA를 사용한 Spring Boot 프로젝트 기반으로 코드를 작성할 때

Command 작업은 큰 문제는 없지만 집계와 같은 Query 성 작업은 JPA 로는 어느정도 한계가 있다.

JPQL을 사용할 순 있겠지만 String base로 코드가 작성되기 때문에 작업을 하면서 실수가 날 여지가 있고 직접 실행하지 않는 이상 이 실수를 알기 힘든 경우가 있다.

 

Querydsl 를 사용하면 이러한 JPQL의 단점을 보완할 수 있다.

Querydsl 에서 생성해준 컴파일 된 코드 기반으로 쿼리를 작성하기 때문에 일단 쿼리 작성 자체가 매우 쉬워지고 컴파일 시점에서 오류 또한 찾을 수 있다.

 

이번 포스팅은 Kotlin 기반의 Spring Boot 프로젝트에 JPA 기반 Querydsl 을 세팅하고 간단한 사용법에 대해 알아보도록 하겠다.

 

해당 코드는 여기에서 확인할 수 있다.

 


 

Project 생성 및 Entity 생성

단순하게 Spring Data JPAMySQL Driver dependency만 추가하고 Spring Boot 프로젝트를 생성한다.

 

그리고 단순하게 제품(Product)를 가리키는 Entity와 이에 대한 Repository를 추가한다.

import java.time.LocalDateTime
import javax.persistence.*

@Entity(name = "products")
data class Product(
    @Column(name = "name")
    val name: String,

    @Column(name = "quantity")
    var quantity: Long,

    @Column(name = "registeredAt")
    val registeredAt: LocalDateTime,

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null
)

 

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

interface ProductRepository : JpaRepository<Product, Long>

 

Querydsl 설정

먼저 build.gradle.kts 파일 설정이다.

기본적으로 Spring Initializr 에서 설정된 것이고

추가 설정 이라고 표시해둔 곳만 추가해주면 된다.

 

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.5.0"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.5.10"
    kotlin("plugin.spring") version "1.5.10"
    kotlin("plugin.jpa") version "1.5.10"
    kotlin("kapt") version "1.3.61" // <= 추가 설정
}

group = "com.tistory.devs0n"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
val qeurydslVersion = "4.4.0" // <= 추가 설정

repositories {
    mavenCentral()
}

// 아래 블록 추가 설정
sourceSets["main"].withConvention(org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet::class) {
    kotlin.srcDir("$buildDir/generated/source/kapt/main")
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    // db
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("mysql:mysql-connector-java")

    // querydsl (추가 설정)
    implementation("com.querydsl:querydsl-jpa:$qeurydslVersion")
    kapt("com.querydsl:querydsl-apt:$qeurydslVersion:jpa")
    kapt("org.springframework.boot:spring-boot-configuration-processor")

    // test
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "11"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

 

그 다음 Querydsl 에서 생성해주는 Q class 를 확인하기 위해 gradle build 를 하면 다음과 같이 build 디렉토리 내에서 QProduct 를 확인할 수 있다.

 

그리고 Querydsl을 편리하게 쓰기위해 다음과 같이 JPAQueryFactorybean으로 등록하는 QuerydslConfig 를 생성한다.

import com.querydsl.jpa.impl.JPAQueryFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import javax.persistence.EntityManager
import javax.persistence.PersistenceContext

@Configuration
class QuerydslConfig(
    @PersistenceContext // <- annotation 확인!
    private val entityManager: EntityManager
) {
    @Bean
    fun jpaQueryFactory(): JPAQueryFactory {
        return JPAQueryFactory(this.entityManager)
    }
}

 

Test

이제 기본적인 세팅은 끝났다.

테스트를 통해 실제로 Querydsl이 잘 동작하는지 확인해보자.

 

다음과 같이 products 테이블(Product entity)에 데이터가 쌓여있을 때

 

> 1개월 내로 등록된 Product 중 수량이 적은 5개 찾기

라는 기능이 필요할 때 작성되는 SQL query는 다음과 같겠다.

SELECT
FROM products
WHERE DATE_SUB(NOW(), INTERVAL 1 MONTH) < registeredAt
ORDER BY quantity ASC
LIMIT 5

 

이제 이 SQL 문을 Querydsl 을 사용하여 작성하면 아래와 같다.

// when
val products = jpaQueryFactory // QuerydslConfig에서 bean으로 등록한 JPAQueryFactory
            .selectFrom(product)
            .where(product.registeredAt.after(LocalDateTime.now().minusMonths(1)))
            .orderBy(product.quantity.asc())
            .limit(5)
            .fetch()

// then
assertThat(products).hasSize(5)
assertThat(products[0].name).isEqualTo("Ginger")
assertThat(products[1].name).isEqualTo("Fish and Chips")
assertThat(products[2].name).isEqualTo("Egg")
assertThat(products[3].name).isEqualTo("Dragon Fruit")
assertThat(products[4].name).isEqualTo("Cap")

(전체 테스트 코드는 여기를 확인)

 

그리고 이 테스트 코드를 실행하면 테스트가 통과되고 다음과 같이 SQL query를 로그를 통해 확인할 수 있는데

SQL 문에서는 DATE_SUB 함수를 사용했지만 Querydsl 을 사용할 때는 날짜를 직접 계산할 수 있기 때문에 where 문이 조금 다르다.

댓글