[Spring] Kotlin으로 JPA Querydsl 세팅
ㅁ 들어가며
ㅇ 새로운 프로젝트에서 코틀린을 처음 쓰게 되었다.
ㅇ 또한 Querydsl도 사용하고 있어서 연습을 위해 프로젝트를 생성하는 과정을 정리하였다.
ㅇ Querydsl은 SpringBoot 버전 마다 설정법이 달라 참조하는 설명마다 설정 방법이 달랐다.
ㅇ 내가 성공한 기준으로 이력을 남겨놓는다.
ㅁ 프로젝트 생성
ㅇ Spring Data JPA와 MySQL Driver, Elasticsearch를 추가하였다.
ㅁ 의존성 추가
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "3.1.0"
id("io.spring.dependency-management") version "1.1.0"
kotlin("jvm") version "1.8.21"
kotlin("plugin.spring") version "1.8.21"
kotlin("plugin.jpa") version "1.8.21"
kotlin("plugin.allopen") version "1.8.21"
kotlin("kapt") version "1.8.21"
}
group = "org.peterica"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
allOpen {
// Spring Boot 3.0.0
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.MappedSuperclass")
annotation("jakarta.persistence.Embeddable")
}
dependencies {
// Spring boot
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.springframework.boot:spring-boot-starter-batch")
// DB
runtimeOnly("com.mysql:mysql-connector-j")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
// QueryDSL 설정
implementation ("com.querydsl:querydsl-jpa:5.0.0:jakarta")
kapt ("com.querydsl:querydsl-apt:5.0.0:jakarta")
kapt ("jakarta.annotation:jakarta.annotation-api")
kapt ("jakarta.persistence:jakarta.persistence-api")
// test
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs += "-Xjsr305=strict"
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
ㅇ Spring Initializr에서 설정된 부분에서 kapt, querydsl 의존성을 추가하였다.
ㅁ MySQL 세팅
예전에 설치하였던 MySQL DB와 샘플 데이터를 활용하였다.
ㅇ MySQL8.0: [MySQL] Mysql Docker 설치, 8.0
ㅇ 샘플 데이터: [MySQL] 실습환경 구성하기, Mysql Docker 설치, 5.7
ㅁ JPA Entity 생성
ㅇ Intellij에서 JPA Entity를 자동생성할 수 있다.
ㅇ Intellij > Database > table에서 Generate Persistence Mapping을 클릭한다.
ㅇ JPA와 연결하고자하는 Table를 선택하고 package를 선택한다.
package org.peterica.springjpaquerydsl.domain;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.util.Objects;
@Entity
@Table(name = "products", schema = "northwind", catalog = "")
public class ProductsEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
@Column(name = "ProductID")
private int productId;
@Basic
@Column(name = "ProductName")
private String productName;
@Basic
@Column(name = "SupplierID")
private Integer supplierId;
@Basic
@Column(name = "CategoryID")
private Integer categoryId;
@Basic
@Column(name = "QuantityPerUnit")
private String quantityPerUnit;
@Basic
@Column(name = "UnitPrice")
private BigDecimal unitPrice;
@Basic
@Column(name = "UnitsInStock")
private Integer unitsInStock;
@Basic
@Column(name = "UnitsOnOrder")
private Integer unitsOnOrder;
@Basic
@Column(name = "ReorderLevel")
private Integer reorderLevel;
@Basic
@Column(name = "Discontinued")
private Byte discontinued;
// getters and setters....
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductsEntity that = (ProductsEntity) o;
return productId == that.productId && Objects.equals(productName, that.productName) && Objects.equals(supplierId, that.supplierId) && Objects.equals(categoryId, that.categoryId) && Objects.equals(quantityPerUnit, that.quantityPerUnit) && Objects.equals(unitPrice, that.unitPrice) && Objects.equals(unitsInStock, that.unitsInStock) && Objects.equals(unitsOnOrder, that.unitsOnOrder) && Objects.equals(reorderLevel, that.reorderLevel) && Objects.equals(discontinued, that.discontinued);
}
@Override
public int hashCode() {
return Objects.hash(productId, productName, supplierId, categoryId, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued);
}
}
ㅇ Table과 컬럼, get & set 구문 등이 자동으로 완성되었다.
ㅁ JPAQueryFactory 생성
package com.peterica.kotlinquerydsl.config
import com.querydsl.jpa.impl.JPAQueryFactory
import jakarta.persistence.EntityManager
import jakarta.persistence.PersistenceContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class QueryDslConfig {
@PersistenceContext
lateinit var entityManager: EntityManager
@Bean
fun jpaQueryFactory(): JPAQueryFactory {
return JPAQueryFactory(entityManager)
}
}
ㅇ 영속성을 체크하고 쿼리를 생성해줄 JPAQueryFactory를 생성한다.
ㅁ JpaRepository설정
package com.peterica.kotlinquerydsl.entity
import org.springframework.data.jpa.repository.JpaRepository
interface ProductRepository : JpaRepository<Product, Long>
ㅇ ProductEntity를 연결할 ProductRepository를 생성하였다.
ㅁ Test Code
package com.peterica.kotlinquerydsl
import com.peterica.kotlinquerydsl.entity.Product
import com.peterica.kotlinquerydsl.entity.ProductRepository
import com.peterica.kotlinquerydsl.entity.QProduct.product
import com.querydsl.jpa.impl.JPAQueryFactory
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import java.time.LocalDateTime
@SpringBootTest
class KotlinQuerydslApplicationTests {
@Autowired
private lateinit var productRepository: ProductRepository
@Autowired
private lateinit var jpaQueryFactory: JPAQueryFactory
@Test
fun contextLoads() {
}
@DisplayName("1개월 내로 등록된 Product 중 수량이 적은 5개 찾기")
@Test
fun querydslTest() {
// given
this.setUpTestData()
// when
val products = jpaQueryFactory
.selectFrom(product)
.where(product.registeredAt.after(LocalDateTime.now().minusMonths(1)))
.orderBy(product.quantity.asc())
.limit(5)
.fetch()
products.forEach { println(it) }
// 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")
}
private fun setUpTestData() {
val products = listOf(
Product("Apple", 1, LocalDateTime.now()),
Product("Banana", 3, LocalDateTime.now().minusDays(1)),
Product("Cap", 4, LocalDateTime.now().minusDays(15)),
Product("Dragon Fruit", 5, LocalDateTime.now().minusDays(20)),
Product("Egg", 7, LocalDateTime.now().minusDays(24)),
Product("Fish and Chips", 8, LocalDateTime.now().minusDays(26)),
Product("Ginger", 3, LocalDateTime.now().minusDays(28)),
Product("Ham", 5, LocalDateTime.now().minusDays(33)),
Product("Ice", 6, LocalDateTime.now().minusDays(46)),
)
productRepository.saveAll(products)
}
}
ㅇ 테스트 코드도 작성하였다.
ㅇ 이곳에서 소스 참조
ㅁ 함께 보면 좋은 사이트
ㅇ https://github.com/ivvve/code-examples/tree/master/spring-jpa-query-dsl
ㄴ 다양한 예제 소스가 많은 곳
ㅇ Spring JPA Querydsl 세팅 (with Kotlin)