본문 바로가기
language, framework, library/kotlin

[kotlin + Spring] 코틀린, Spring Boot 환경에서 JPA 사용하기, plugin과 함께

by 사바라다 2021. 7. 30.

안녕하세요. 오늘은 코틀린 그리고 Spring Boot 환경에서 JPA를 사용하는 방법에 대해서 알아보도록 하겠습니다.

환경

  • JDK
    • 버전 : 1.8.0.292
  • kotlin
    • 버전 : 1.4.32
  • spring boot
    • 버전 : 2.4.5
  • gradle
    • 버전 : 6.8.3
  • intellij
    • 버전 : 2021.1.1

프로젝트 시작하기

처음에 프로젝트를 시작하는 방법과 기본적인 gradle kotlin dsl의 설정에 대해서는 이전 포스팅에서 자세히 다루었습니다. 참고해주시기 바랍니다.

[kotlin + Spring] 코틀린 환경에서 Spring Boot 사용하기

gradle kotlin dsl 설정

먼저 gradle kotlin dsl 설정을 하는 법부터 알아보도록 하겠습니다. gradle kotlin dsl 설정은 이전시간과 크게 다르지 않습니다. 아래의 코드를 보도록 하겠습니다. JPA를 사용하기위해서 추가로 넣은 plugin과 dependencies에는 주석으로 그 용도를 적어두었습니다. plugin의 자세한 설명은 아래에서 하도록 하겠습니다.

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

plugins {
    id("org.springframework.boot") version "2.4.5"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.4.32"
    kotlin("plugin.spring") version "1.4.32"
    kotlin("plugin.jpa") version "1.4.32" // JPA를 사용하기 위한 플러그인
}

group = "personal.project"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa") // JPA를 사용하기 위한 의존성
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

allOpen { // 추가적으로 열어줄 allOpen
    annotation("javax.persistence.Entity")
    annotation("javax.persistence.MappedSuperclass")
    annotation("javax.persistence.Embeddable")
}

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

tasks.withType<Test> {
    useJUnitPlatform()
}

Plugin

plugin.spring(AllArg)

JPA를 사용하기 위해서는 이전 시간에 배웠던 allOpen과 Entity 생성에 필요한 no-arg plugin이 필요합니다. 이전 시간에 allOpen을 포함하고 있는 plugin.spring에 대해서 배웠지만 이를 그냥 사용하면 기본적인 JPA에서는 정상적으로 작동합니다. 하지만 dreatedAt 등 공통된 부분에 Auditing을 사용할 때 라던지 MappedSuperclass, Embeddable 등을 활용할 때는 문제가 있습니다. 왜 그런지 조금 자세히 들여다보도록 하겠습니다.

kotlin을 기본적으로 open으로 만들어주는 플러그인이 plugin.spring, plugin.allopen입니다. 하지만 이는 모든 클래스를 open으로 하여 상속가능하게 만들어주지 않습니다. kotlin docs 홈페이지에 보면 아래와 같은 리스트에 대해서 만들어준다고 명시되어 있습니다.

  • @Component
    • @Component를 상속받는 @Configuration, @Controller, @RestController, @Service, @Repository
  • @Async
  • @Transactional
  • @Cacheable
  • SpringBootTest

여기에 보면 JPA에서 공통 컬럼들을 묶어줄때 사용되는 @MappedSuperclass는 포함되고 있지 않는것을 알 수 있습니다. 따라서 이부분은 별도로 명시를 해줘야합니다. 추가적으로 해주면 좋은 어노테이션을 포함했습니다.

allOpen {
    annotation("javax.persistence.Entity")
    annotation("javax.persistence.MappedSuperclass")
    annotation("javax.persistence.Embeddable")
}

plugin.jpa(noarg)

두번째로 알아볼 플러그인은 plugin.jpa 입니다. 이 플러그인은 java의 기본 생성자를 기본적으로 만들어주는 플러그인입니다. 이것 또한 모든 클래스에 기본 생성자를 만들어주지는 않습니다. 공식 문서에 따르면 아래의 경우에 기본 생성자를 만들어준다고 합니다. 따라서 추가적으로 기본 생성자가 필요할 경우에는 아래와 같이 noArg를 이용하여 추가할 수 있습니다.

  • @Entity
  • @Embeddable
  • @MappedSuperclass
noArg {
  annotation("com.my.Annotation")
}

참고로 코틀린은 primary 생성자에 있는 모든 값에 기본 값이 있으면 컴파일 시 자동적으로 기본 생성자를 만들어줍니다.

의존성

JPA를 사용하기 위해서 사용하는 의존성은 자바와 동일합니다.

implementation("org.springframework.boot:spring-boot-starter-data-jpa")

코틀린 코드 생성

아래는 위 코틀린 설정을 기본으로하여 JPA를 사용해본 코드입니다. 여러분의 코드에 참고하시면 좋을것 같습니다.

Entity

@MappedSuperclass
@EntityListeners(value = [AuditingEntityListener::class])
class BaseEntityModel {

    @CreatedDate
    @Column(name = "created_datetime", insertable = false, updatable = false)
    lateinit var createdDateTime: LocalDateTime

    @LastModifiedDate
    @Column(name = "updated_datetime", insertable = false, updatable = false)
    lateinit var updatedDateTime: LocalDateTime
}

@Entity
data class Mapping(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long,
    val senderType: UserType,
    val senderId: Long,
    val receiverType: UserType,
    val receiverId: Long,
    val approval: Boolean
) : BaseEntityModel()

Repository

interface MappingRepository : CrudRepository<Mapping, Long>

Test 코드 작성

@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class MappingRepositoryTest(
    val mappingRepository: MappingRepository
) {

    @Test
    fun `mapping 테스트`() {
        // when
        val count = mappingRepository.count()

        // then
        Assertions.assertThat(count).isEqualTo(0)
    }

    @Test
    fun `mapping insert 테스트`() {
        // given
        val mapping = Mapping(senderId = 1L, receiverId = 2L, approval = false)

        // when
        mappingRepository.save(mapping)
    }

}

마무리

오늘은 이렇게 코틀린, Spring Boot 환경에서 JPA를 사용하는 방법에 대해서 알아보았습니다.

감사합니다.

참조

kotlinlang

댓글