正如JPA所要求的那样,@Entity
类应该有一个默认(非arg)构造函数,以便在从数据库中检索对象时实例化对象。
在Kotlin中,在主构造函数中声明属性非常方便,如下例所示:
class Person(val name: String, val age: Int) { /* ... */ }
但是当非arg构造函数被声明为次要构造函数时,它需要传递主构造函数的值,因此需要一些有效的值,如下所示:
@Entity
class Person(val name: String, val age: Int) {
private constructor(): this("", 0)
}
如果属性的类型比String
和Int
更复杂且它们不可为空,那么为它们提供值看起来很糟糕,特别是当代码很多时在主构造函数和init
块中以及当参数被主动使用时 - 当它们通过反射重新分配时,大多数代码将再次执行。
此外,val
- 属性在构造函数执行后无法重新赋值,因此不变性也会丢失。
所以问题是:Kotlin代码如何在没有代码重复的情况下适应JPA,选择“神奇”的初始值和失去不变性?
P.S。除了JPA之外,Hibernate是否可以构造没有默认构造函数的对象?
答案 0 :(得分:108)
从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>
答案 1 :(得分:29)
只为所有参数提供默认值,Kotlin将为您创建默认构造函数。
@Entity
data class Person(val name: String="", val age: Int=0)
请参阅以下部分下方的NOTE
框:
https://kotlinlang.org/docs/reference/classes.html#secondary-constructors
答案 2 :(得分:9)
@ 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
}
当您确定某些内容将在构造时或很快之后(以及在第一次使用实例之前)填入值时,您将使用此方法。
您会注意到我已将age
更改为birthdate
,因为您无法将原始值与lateinit
一起使用,而且目前它们也必须为var
(限制可能会在未来)。
因此,对于不变性而言,这不是一个完美的答案,与此相关的其他答案也是如此。解决方案是插件到库,可以处理理解Kotlin构造函数和将属性映射到构造函数参数,而不需要默认构造函数。 Kotlin module for Jackson执行此操作,因此显然可行。
另请参阅: https://stackoverflow.com/a/34624907/3679676,了解类似选项。
答案 3 :(得分:6)
@Entity data class Person(/*@Id @GeneratedValue var id: Long? = null,*/
var name: String? = null,
var age: Int? = null)
如果要为不同的字段使用重用构造函数,则需要初始值,kotlin不允许空值。因此,无论何时计划省略字段,请在构造函数中使用此表单:var field: Type? = defaultValue
jpa不需要参数构造函数:
val entity = Person() // Person(name=null, age=null)
没有代码重复。如果您需要构造实体且仅设置年龄,请使用以下形式:
val entity = Person(age = 33) // Person(name=null, age=33)
没有魔法(只是阅读文档)
答案 4 :(得分:4)
没有办法像这样保持不变性。 在构造实例时必须初始化Vals。
没有不变性的一种方法是:
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()
}
答案 5 :(得分:3)
我已经和Kotlin + JPA合作了很长一段时间,我已经创建了自己的想法如何编写实体类。
我只是略微扩展你的初步想法。正如您所说,我们可以创建私有无参数构造函数并为原语提供默认值,但是当我们尝试使用其他类时,它会变得有点混乱。我的想法是为您当前编写的实体类创建静态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 作为最后一个字段(并且可以为空)是非常理想的。我们不应该手动分配它,让数据库为我们做。
我并不是说这是完美的解决方案,但我认为它利用了实体代码可读性和Kotlin功能(例如null安全性)。我希望将来发布的JPA和/或Kotlin将使我们的代码更简单,更好。
答案 6 :(得分:2)
在gradle中添加JPA插件对我有用:
plugins {
id("org.springframework.boot") version "2.3.4.RELEASE"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
kotlin("jvm") version "1.3.72"
kotlin("plugin.spring") version "1.3.72"
kotlin("plugin.jpa") version "1.3.72"
}
答案 7 :(得分:1)
与@pawelbial类似,我使用了伴随对象来创建默认实例,但是不是定义辅助构造函数,而是使用默认构造函数args,如@iolo。这样可以节省您必须定义多个构造函数并使代码更简单(尽管已授予,定义&#34; STUB&#34;伴随对象并不完全保持简单)
@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所提到的那样,TestEntity
类&#34;有一个&#34; TestEntity
类因为STUB在构造函数运行时没有被初始化。
答案 8 :(得分:1)
我自己也是一个小伙伴,但似乎你必须明确初始化并回退到像这样的空值
@Entity
class Person(val name: String? = null, val age: Int? = null)
答案 9 :(得分: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不起作用。
答案 10 :(得分: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"
}
}
答案 11 :(得分:1)
如上所述,您必须使用Jetbrains提供的no-arg
插件。
如果您使用的是Eclispe ,则可能需要编辑Kotlin编译器设置。
窗口>首选项> Kotlin>编译器
在“编译器插件”部分中激活no-arg
插件。
请参阅:https://discuss.kotlinlang.org/t/kotlin-allopen-plugin-doesnt-work-with-sts/13277/10
答案 12 :(得分:0)
在以下线程中参考@ tin-ng提到的Interface方法:
hibernate jpa projection with @Query
将class Person(val name: String, val age: Int)
转换为Interface Person{ val name: String val age: Int }