JPA가있는 Kotlin : 기본 생성자 지옥


131

JPA에서 요구하는대로 @Entity클래스는 데이터베이스에서 객체를 검색 할 때 객체를 인스턴스화 할 기본 (비 인수) 생성자를 가져야합니다.

Kotlin에서는 다음 예제와 같이 기본 생성자 내에서 속성을 선언하는 것이 매우 편리합니다.

class Person(val name: String, val age: Int) { /* ... */ }

그러나 인수가 아닌 생성자를 보조 생성자로 선언하면 기본 생성자에 대한 값이 전달되어야하므로 다음과 같이 유효한 값이 필요합니다.

@Entity
class Person(val name: String, val age: Int) {
    private constructor(): this("", 0)
}

경우 속성은보다 좀 더 복잡한 유형이있을 때 StringInt특히 주 생성자에서 많은 코드있을 때, 그들에 대한 값을 제공하는 완전히 나쁜 보이는, 그들은 Null이있어 init블록 매개 변수가 적극적으로 사용된다 - -리플렉션을 통해 재 할당 될 때 대부분의 코드가 다시 실행됩니다.

또한 val생성자가 실행 된 후 -properties를 재 지정할 수 없으므로 불변성도 손실됩니다.

문제는 어떻게 코드 중복없이 Kotlin 코드를 JPA와 함께 사용하여 "마법의"초기 값과 불변성의 손실을 선택할 수 있습니까?

추신 JPA를 제외하고 Hibernate는 기본 생성자없이 객체를 생성 할 수 있다는 것이 사실입니까?


1
INFO -- org.hibernate.tuple.PojoInstantiator: HHH000182: No default (no-argument) constructor for class: Test (class must be instantiated by Interceptor)– 예, Hibernate는 기본 생성자없이 작동 할 수 있습니다.
Michael Piefel

그것이하는 방법은 세터 (일명 : 돌연변이)입니다. 기본 생성자를 인스턴스화 한 다음 setter를 찾습니다. 불변의 객체를 원합니다. 수행 할 수있는 유일한 방법은 최대 절전 모드에서 생성자를보고 시작하는 것입니다. 이에 오픈 티켓이 hibernate.atlassian.net/browse/HHH-9440는
기독교 Bongiorno

답변:


145

Kotlin 1.0.6 부터 kotlin-noarg컴파일러 플러그인은 선택된 주석으로 주석이 달린 클래스에 대한 합성 기본 구성자를 생성합니다.

gradle을 사용하는 경우 kotlin-jpa플러그인을 적용하면 @Entity다음으로 주석이 달린 클래스의 기본 생성자를 생성 할 수 있습니다 .

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
    }
}

apply plugin: "kotlin-jpa"

Maven의 경우 :

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>

    <configuration>
        <compilerPlugins>
            <plugin>jpa</plugin>
        </compilerPlugins>
    </configuration>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-noarg</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>

4
" data class foo(bar: String)변경되지 않는 "경우에도 kotlin 코드 내에서 이것이 어떻게 사용 될지에 대해 조금 확장 해 주시겠습니까 ? 이것이 어떻게 적용되는지에 대한 더 완전한 예를 보는 것이 좋을 것입니다. 감사합니다
thecoshman

5
이 소개하는 블로그 게시물 kotlin-noargkotlin-jpa자신의 목적을 자세히 링크 blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here
달리 보르 Filus을

1
그리고 엔티티는 아니지만 기본 생성자가 필요한 CustomerEntityPK와 같은 기본 키 클래스는 어떻습니까?
jannnik

3
나를 위해 작동하지 않습니다. 생성자 필드를 선택 사항으로 만드는 경우에만 작동합니다. 플러그인이 작동하지 않음을 의미합니다.
Ixx

3
@jannnik 기본 키 클래스는 @Embeddable속성이 필요하지 않더라도 속성으로 표시 할 수 있습니다 . 그렇게하면에 의해 픽업됩니다 kotlin-jpa.
svick 2009 년

33

모든 인수에 기본값을 제공하면 Kotlin이 기본 생성자를 만듭니다.

@Entity
data class Person(val name: String="", val age: Int=0)

NOTE다음 섹션 아래 의 상자를 참조하십시오 .

https://kotlinlang.org/docs/reference/classes.html#secondary-constructors


18
당신은 분명히 그의 질문을 읽지 않았습니다. 말할 것도없이, 무언가에 대한 기본값을 추가하면 다른 문제가 숨겨집니다.
스노 위

1
기본값을 제공하는 것이 좋지 않은 이유는 무엇입니까? Java의 args no consturctor를 사용하는 경우에도 기본값이 필드에 지정됩니다 (예를 들어 null은 참조 유형).
Umesh Rajbhandari

1
합리적인 기본값을 제공 할 수없는 경우가 있습니다. 주어진 사람의 예를 들어 보면, 생년월일로 모델을 변경해야합니다 (물론 예외는 어딘가에 적용됩니다). 그러나 그에 대한 합리적인 근거는 없습니다. 따라서 순수한 코드 관점을 구성하려면 개인 생성자에 DoB를 전달해야 유효한 연령이 아닌 사람을 가질 수 없습니다. 문제는 JPA가 좋아하는 방식, 인수없는 생성자를 사용하여 객체를 만들고 모든 것을 설정하는 것입니다.
thecoshman

1
나는 이것이 올바른 방법이라고 생각합니다.이 답변은 JPA를 사용하지 않거나 최대 절전 모드를 사용하지 않는 다른 경우에 효과적입니다. 또한 답변에 언급 된 문서에 따라 제안 된 방법입니다.
모하마드 라피

1
또한 JPA와 함께 데이터 클래스를 사용하지 않아야합니다. "JPA는 변경 불가능한 클래스 또는 데이터 클래스에 의해 자동으로 생성 된 메소드와 함께 작동하도록 설계되지 않았기 때문에 val 특성과 함께 데이터 클래스를 사용하지 마십시오." spring.io/guides/tutorials/spring-boot-kotlin/…
Tafsen

11

@ D3xter는 한 모델에 대한 좋은 대답을 가지고 있으며 다른 모델은 Kotlin의 새로운 기능입니다 lateinit.

class Entity() {
    constructor(name: String, age: Date): this() {
        this.name = name
        this.birthdate = age
    }

    lateinit var name: String
    lateinit var birthdate: Date
}

구성 시간 또는 그 직후 (인스턴스를 처음 사용하기 전에) 값이 채워질 것으로 확신 할 때이 옵션을 사용합니다.

내가 바뀐 것을 알 수 있습니다 agebirthdate당신이 원시 값을 사용할 수 없기 때문에 lateinit그들은 또한 순간해야합니다 위해 var(제한이 미래에 출시 될 수 있음).

따라서 불변성에 대한 완벽한 대답은 아니며 다른 점에서와 같은 문제입니다. 이에 대한 해결책은 기본 생성자가 아닌 Kotlin 생성자를 이해하고 생성자 매개 변수에 속성을 매핑하는 것을 처리 할 수있는 라이브러리에 대한 플러그인입니다. 잭슨 코 틀린 모듈은 이 작업을 수행, 그래서 그것은 분명히 가능하다.

유사한 옵션에 대한 탐색은 https://stackoverflow.com/a/34624907/3679676참조하십시오 .


lateinit와 Delegates.notNull ()은 동일합니다.
fasth February

4
비슷하지만 동일하지는 않습니다. Delegate를 사용하면 Java에 의해 실제 필드의 직렬화에 표시되는 내용이 변경됩니다 (대리자 클래스 참조). 또한 lateinit시공 직후 초기화를 보장하는 잘 정의 된 수명주기가있는 경우 사용하는 것이 좋습니다 . 이러한 경우를위한 것입니다. 델리게이트는 "처음 사용하기 전에"더 의도 된 것입니다. 기술적으로는 유사한 동작과 보호 기능이 있지만 동일하지는 않습니다.
Jayson Minard

프리미티브 값을 사용해야하는 경우 객체를 인스턴스화 할 때 "기본 값"을 사용하는 것만 생각할 수 있습니다. 즉, 0과 falseInts 및 Booleans를 각각 사용하는 것을 의미 합니다. 확실하지 그 프레임 워크 코드 생각에 어떻게 영향을 미치는지
OzzyTheGiant

6
@Entity data class Person(/*@Id @GeneratedValue var id: Long? = null,*/
                          var name: String? = null,
                          var age: Int? = null)

다른 필드에 대해 재사용 생성자를 원하면 kotlin은 null을 허용하지 않습니다. 따라서 필드를 생략 할 때마다 생성자에서이 양식을 사용하십시오.var field: Type? = defaultValue

jpa에는 인수 생성자가 필요하지 않습니다.

val entity = Person() // Person(name=null, age=null)

코드 복제가 없습니다. 구성 엔티티가 필요하고 설정 연령 만 필요한 경우 다음 양식을 사용하십시오.

val entity = Person(age = 33) // Person(name=null, age=33)

마술이 없습니다 (문서를 읽으십시오)


1
이 코드 스 니펫은 문제를 해결할 수 있지만 설명을 포함하면 게시물의 품질을 향상시키는 데 실제로 도움이됩니다. 앞으로 독자에게 질문에 대한 답변을 제공하고 있으며 해당 사람들이 귀하의 코드 제안 이유를 모를 수도 있습니다.
DimaSan

@DimaSan, 당신 말이 맞지만, 그 스레드는 이미 일부 게시물에 설명이 있습니다 ...
Maksim Kostromin

그러나 스 니펫은 다르며 설명이 다를 수 있지만 어쨌든 훨씬 명확합니다.
DimaSan

4

이와 같이 불변성을 유지할 수있는 방법은 없습니다. Val은 인스턴스를 구성 할 때 초기화해야합니다.

불변성없이 그것을 수행하는 한 가지 방법은 다음과 같습니다.

class Entity() {
    public constructor(name: String, age: Int): this() {        
        this.name = name
        this.age = age
    }

    public var name: String by Delegates.notNull()

    public var age: Int by Delegates.notNull()
}

따라서 열을 생성자 인수에 매핑하도록 Hibernate에 지시 할 방법조차 없습니까? 글쎄, 인수가 아닌 생성자를 필요로하지 않는 ORM 프레임 워크 / 라이브러리가 있습니까? :)
핫키

확실하지 않습니다, 오랫동안 Hibernate와 함께 일하지 않았습니다. 그러나 명명 된 매개 변수로 구현하는 것이 가능해야합니다.
D3xter

최대 절전 모드로 약간의 작업 으로이 작업을 수행 할 수 있다고 생각합니다. Java 8에서는 실제로 생성자에서 명명 된 매개 변수를 가질 수 있으며 매개 변수는 현재 필드에있는 것처럼 매핑 될 수 있습니다.
Christian Bongiorno 2016 년

3

나는 Kotlin + JPA와 꽤 오랫동안 협력 해 왔으며 Entity 클래스를 작성하는 방법을 내 아이디어로 만들었습니다.

초기 아이디어를 약간 확장했습니다. 당신이 말했듯이 우리는 private argumentless 생성자를 생성하고 primitives에 대한 기본값을 제공 할 수 있지만 다른 클래스를 사용해야 할 때 약간 지저분 해집니다. 내 생각은 현재 쓰는 엔티티 클래스에 대한 정적 STUB 객체를 만드는 것입니다.

@Entity
data class TestEntity(
    val name: String,
    @Id @GeneratedValue val id: Int? = null
) {
    private constructor() : this("")

    companion object {
        val STUB = TestEntity()
    }
}

TestEntity 와 관련된 엔티티 클래스가 있으면 방금 만든 스텁을 쉽게 사용할 수 있습니다. 예를 들면 다음과 같습니다.

@Entity
data class RelatedEntity(
        val testEntity: TestEntity,
        @Id @GeneratedValue val id: Long? = null
) {
    private constructor() : this(TestEntity.STUB)

    companion object {
        val STUB = RelatedEntity()
    }
}

물론이 솔루션은 완벽하지 않습니다. 필요하지 않은 상용구 코드를 작성해야합니다. 또한 하나의 엔터티 클래스 내에서 부모-자식 관계-스터 빙으로 제대로 해결할 수없는 경우가 있습니다.

@Entity
data class TestEntity(
        val testEntity: TestEntity,
        @Id @GeneratedValue val id: Long? = null
) {
    private constructor() : this(STUB)

    companion object {
        val STUB = TestEntity()
    }
}

이 코드는 NullPointerException 을 생성합니다 닭 계란 문제로 인해 을 생성합니다. STUB을 만들려면 STUB이 필요합니다. 불행히도 우리는 코드를 작동시키기 위해이 필드를 널 입력 가능 (또는 유사한 솔루션)으로 만들어야합니다.

또한 내 의견으로는 Id 를 마지막 필드 (및 nullable)로 사용하는 것이 매우 최적입니다. 우리는 그것을 직접 할당해서는 안되며 데이터베이스가 우리를 위해 그것을하도록하십시오.

이것이 완벽한 솔루션이라고 말하는 것은 아니지만 엔터티 코드 가독성과 Kotlin 기능 (예 : null 안전)을 활용한다고 생각합니다. JPA 및 / 또는 Kotlin의 향후 릴리스에서 코드가 더 간단하고 멋지게 만들어지기를 바랍니다.



2

나는 멍청한 사람이지만 명시 적으로 초기화하고 null 값으로 대체 해야하는 것 같습니다.

@Entity
class Person(val name: String? = null, val age: Int? = null)

1

@pawelbial과 마찬가지로 도우미 객체를 사용하여 기본 인스턴스를 만들었지 만 보조 생성자를 정의하는 대신 @iolo와 같은 기본 생성자 인수를 사용하십시오. 이렇게하면 여러 생성자를 정의 할 필요가없고 코드를 더 단순하게 유지할 수 있습니다 (물론 "STUB"컴패니언 객체를 정의하는 것이 정확하게 유지되지는 않습니다)

@Entity
data class TestEntity(
    val name: String = "",
    @Id @GeneratedValue val id: Int? = null
) {

    companion object {
        val STUB = TestEntity()
    }
}

그리고 관련 수업 TestEntity

@Entity
data class RelatedEntity(
    val testEntity: TestEntity = TestEntity:STUB,
    @Id @GeneratedValue val id: Int? = null
)

@pawelbial에서 언급했듯이 생성자가 실행될 때 STUB가 초기화되지 않았기 때문에 TestEntity클래스에 "a" TestEntity클래스 가 있는 경우 작동 하지 않습니다.


1

이 Gradle 빌드 라인은 나를 도왔습니다 :
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa/1.1.50 .
적어도 IntelliJ에서 빌드됩니다. 현재 명령 행에서 실패하고 있습니다.

그리고 나는

class LtreeType : UserType

    @Column(name = "path", nullable = false, columnDefinition = "ltree")
    @Type(type = "com.tgt.unitplanning.data.LtreeType")
    var path: String

var path : LtreeType이 작동하지 않았습니다.


1

gradle 플러그인 https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa 를 추가 했지만 작동하지 않으면 버전이 오래 되었을 가능성이 있습니다. 나는 1.3.30에 있었고 그것은 나를 위해 작동하지 않았다. 1.3.41 (작성 시점에서 최신)로 업그레이드 한 후 작동했습니다.

참고 : kotlin 버전은이 플러그인과 동일해야합니다. 예 : 다음은 두 가지를 모두 추가 한 방법입니다.

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
    }
}

Micronaut와 함께 일하고 있으며 1.3.41 버전에서 작동하도록했습니다. Gradle을 내 코 틀린 버전은 1.3.21이고 나는이 문제, 다른 모든 플러그인을 참조 didnt는 말한다 ( 'kapt / JVM / allopen') 또한 내가 플러그인 DSL 형식을 사용하고 1.3.21에있다
개빈
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.