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

プロパティが単なるより複雑なタイプでStringありInt、null可能ではない場合、特にプライマリコンストラクターとinitブロックに多くのコードがあり、パラメーターがアクティブに使用されている場合、それらの値を提供することは完全に悪いように見えます- -それらがリフレクションによって再割り当てされる場合、ほとんどのコードが再度実行されます。

さらに、valコンストラクターの実行後に-propertiesを再割り当てできないため、不変性も失われます。

したがって、問題は、コードを重複させずに「マジック」初期値と不変性の損失を選択して、JPAで動作するようにKotlinコードをどのように適合させることができるかということです。

PS 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 2017

それを行う方法はセッター-別名:可変性です。デフォルトのコンストラクタをインスタンス化し、セッターを探します。不変オブジェクトが必要です。実行できる唯一の方法は、休止状態がコンストラクタを調べ始める場合です。このhibernate.atlassian.net/browse/HHH-9440に
クリスチャンボンジョルノ

回答:


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 2017

5
これは紹介されたブログ投稿でkotlin-noargありkotlin-jpa、その目的を詳しく説明するリンクが付いています。blog.jetbrains.com/ kotlin / 2016/12 / kotlin
Dalibor Filus

1
そして、エンティティではないがデフォルトのコンストラクタを必要とするCustomerEntityPKのような主キークラスについてはどうでしょうか。
jannnik 2017

3
私のために働いていません。コンストラクタフィールドをオプションにした場合にのみ機能します。つまり、プラグインが機能していません。
Ixx

3
@jannnik @Embeddable他の方法で必要でない場合でも、属性で主キークラスをマークできます。そうすれば、それはによってピックアップされkotlin-jpaます。
8:46にスビック

33

すべての引数にデフォルト値を指定するだけで、Kotlinがデフォルトのコンストラクターを作成します。

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

NOTE次のセクションの下のボックスを参照してください。

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


18
あなたは明らかに彼の質問を読んでいなかったでしょう、そうでなければ、特により複雑なオブジェクトの場合、デフォルトの引数は見栄えが悪いと彼が言っている部分を見たことでしょう。言うまでもなく、何かにデフォルト値を追加すると、他の問題が隠されます。
16

1
なぜデフォルト値を提供するのが悪いのですか?Javaの引数なしのコンストラクターを使用する場合でも、デフォルト値がフィールドに割り当てられます(たとえば、参照型にはnull)。
Umesh Rajbhandari

1
適切なデフォルトを提供できない場合があります。与えられた人の例をとると、実際には生年月日でモデル化する必要があります。なぜなら、それは変わらないからです(もちろん、例外はどこかで何らかの形で適用されます)が、それに与える適切なデフォルトはありません。したがって、純粋なコードの観点を形成するには、DoBをpersonコンストラクターに渡す必要があります。これにより、有効な年齢を持たないpersonが存在しないようにします。問題は、JPAが機能する方法、引数のないコンストラクターでオブジェクトを作成し、すべてを設定する方法です。
thecoshman 2017

1
これは正しい方法だと思います。この回答は、JPAや休止状態を使用しない他のケースでも機能します。また、回答に記載されているドキュメントによると、これは推奨される方法です。
Mohammad Rafigh 2017

1
また、JPAでデータクラスを使用しないでください。「JPAは不変クラスまたはデータクラスによって自動的に生成されたメソッドで動作するように設計されていないため、valプロパティを持つデータクラスを使用しないでください。」spring.io/guides/tutorials/spring-boot-kotlin/...
Tafsen

11

@ D3xterは1つのモデルに対して適切な答えを示し、もう1つは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コンストラクターの理解とコンストラクターパラメーターへのプロパティのマッピングを処理できるライブラリへのプラグインです。ジャクソンKotlinモジュールがこれを行うので、それは明らかに可能です。

同様のオプションの探索については、https //stackoverflow.com/a/34624907/3679676 も参照してください


lateinitとDelegates.notNull()は同じであることに注意してください。
fasth

4
似ているが同じではない。Delegateを使用すると、Javaによる実際のフィールドのシリアル化で表示される内容が変更されます(デリゲートクラスが表示されます)。また、lateinit構築後すぐに初期化を保証する明確に定義されたライフサイクルがある場合に使用することをお勧めします。一方、デリゲートは「最初の使用前のいつか」を対象としています。技術的には同様の動作と保護がありますが、同一ではありません。
Jayson Minard、2016

プリミティブ値を使用する必要がある場合、オブジェクトをインスタンス化するときに「デフォルト値」を使用することしか考えfalseられません。つまり、それぞれ0を使用し、Intとブール値を使用します。それがフレームワークのコードにどのように影響するかはわかりません
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 2017

4

このように不変性を維持する方法はありません。インスタンスを構築するとき、Valsを初期化する必要があります。

不変性なしでそれを行う1つの方法は次のとおりです。

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 2015

hibernateは少しの作業でこれを行うことができると思います。Java 8では、実際にはコンストラクターで名前を付けたパラメーターを使用でき、それらは現在のフィールドと同じようにマップできます。
クリスチャンボンジョルノ2017年

3

私はKotlin + JPAをかなり長い間使用しており、Entityクラスを作成する方法を自分で考え出しました。

私はあなたの最初の考えを少しだけ拡張します。あなたが言ったように、引数なしのプライベートコンストラクターを作成し、プリミティブのデフォルト値を提供できますが、別のクラスを使用する必要がある場合、少し面倒になります。私の考えは、あなたが現在書いているエンティティクラスのための静的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()
    }
}

もちろん、このソリューションは完璧ではありません。それでも、不要な定型コードを作成する必要があります。また、次のように、スタブではうまく解決できないケースが1つあります。1つのエンティティークラス内の親子関係です。

@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が必要です。残念ながら、コードを機能させるには、このフィールドをnullにできるようにする(またはいくつかの同様のソリューション)必要があります。

また、私の意見ではIdを最後のフィールド(およびnull可能)にすることは非常に最適です。手動で割り当てて、データベースに任せるべきではありません。

これが完璧なソリューションであると言っているわけではありませんが、エンティティコードの読みやすさと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クラスが「ある」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によると、Kotlinのバージョンは1.3.21で、他のプラグイン( 'kapt / jvm / allopen')はすべて1.3.21です。また、プラグインDSL形式を使用しています
Gavin
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.