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

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는 기본 생성자없이 객체를 생성 할 수 있다는 것이 사실입니까?



답변

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>


답변

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

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

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

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


답변

@ 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참조하십시오 .


답변

@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)

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


답변

이와 같이 불변성을 유지할 수있는 방법은 없습니다. 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()
}


답변

나는 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의 향후 릴리스에서 코드가 더 간단하고 멋지게 만들어지기를 바랍니다.


답변

위에서 언급했듯이 no를 사용해야합니다. no-arg Jetbrains에서 제공 plugin .

Eclispe사용하는 경우 Kotlin 컴파일러 설정을 편집해야 할 수 있습니다.

창> 환경 설정> Kotlin> 컴파일러

no-arg컴파일러 플러그인 섹션에서 플러그인을 활성화 하십시오.

참조 : https://discuss.kotlinlang.org/t/kotlin-allopen-plugin-doesnt-work-with-sts/13277/10