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は、コンストラクターの実行後に再割り当てできないため、不変性も失われます。
問題は、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は1つのモデルに対して適切な答えを持ち、もう1つはlateinit
と呼ばれるKotlinの新しい機能です。
class Entity() {
constructor(name: String, age: Date): this() {
this.name = name
this.birthdate = age
}
lateinit var name: String
lateinit var birthdate: Date
}
これは、構築時に、またはインスタンスの最初の使用の直前に、何かが値を埋めると確信している場合に使用します。
age
でプリミティブ値を使用できないため、birthdate
をlateinit
に変更したことに注意してください。また、現時点ではvar
でなければなりません。
そのため、不変性に対する完璧な答えではなく、その点で他の答えと同じ問題です。そのためのソリューションは、デフォルトのコンストラクターを必要とせずに、Kotlinコンストラクターの理解とコンストラクターパラメーターへのプロパティのマッピングを処理できるライブラリーへのプラグインです。 ジャクソンの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)
魔法はありません(ドキュメントを読むだけです)
このような不変性を維持する方法はありません。インスタンスを作成するときは、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()
}
私はかなり長い間Kotlin + JPAを使用しており、Entityクラスの書き方について独自のアイデアを作成しました。
最初のアイデアを少しだけ拡張します。あなたが言ったように、プライベート引数なしコンストラクタを作成し、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()
}
}
もちろん、このソリューションは完璧ではありません。不要なボイラープレートコードを作成する必要があります。また、スタブではうまく解決できないケースが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が必要です。残念ながら、コードを機能させるには、このフィールドをヌル可能(または同様のソリューション)にする必要があります。
また、私の意見では、Idを最後のフィールドとして(およびヌル可能)にすることは非常に最適です。手動で割り当てて、データベースに任せるべきではありません。
これが完璧なソリューションであると言っているわけではありませんが、エンティティコードの可読性とKotlin機能(例:null安全性)を活用していると思います。 JPAおよび/またはKotlinの将来のリリースによって、コードがさらにシンプルでより良いものになることを願っています。
@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
クラスでは機能しません。
これらのGradleビルドラインは私を助けました:
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa/1.1.5 。
少なくとも、IntelliJに組み込まれています。現時点ではコマンドラインで失敗しています。
そして私は
class LtreeType : UserType
そして
@Column(name = "path", nullable = false, columnDefinition = "ltree")
@Type(type = "com.tgt.unitplanning.data.LtreeType")
var path: String
var path:LtreeTypeは機能しませんでした。
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"
}
}
私は自分自身がナブですが、明示的なイニシャライザとこのようなnull値へのフォールバックが必要なようです
@Entity
class Person(val name: String? = null, val age: Int? = null)