Programming/Spring

[Spring] Kotlin으로 JPA Querydsl 세팅

기록하는 백앤드개발자 2024. 8. 9. 10:10
반응형

ㅁ 들어가며

ㅇ 새로운 프로젝트에서 코틀린을 처음 쓰게 되었다.

ㅇ 또한 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)
    }
}

ㅇ 테스트 코드도 작성하였다.

ㅇ 이곳에서 소스 참조

 

ㅁ 함께 보면 좋은 사이트

queryDSL 공식 홈

QueryDSL GIT - 샘플 소스

https://github.com/ivvve/code-examples/tree/master/spring-jpa-query-dsl

  ㄴ 다양한 예제 소스가 많은 곳

스프링부트 3.x에서 Querydsl 설정하기

Spring JPA Querydsl 세팅 (with Kotlin)

 

반응형