与JPA Kotlin:默认构造函数地狱

按照JPA的要求,@ @Entity类应该有一个默认的(非arg)构造函数来从数据库中检索对象时实例化对象。

在Kotlin中,属性在主构造函数中声明非常方便,如下例所示:

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

但是,当非参数构造函数被声明为次要构造函数时,它需要传递主构造函数的值,所以需要一些有效的值,如下所示:

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

如果这些属性比StringInt有更复杂的types,并且它们是不可空的,那么为它们提供值看起来完全不好,特别是当主构造函数和init块中有很多代码,并且参数是积极使用 – 当它们被重新分配时,大部分代码将被重新执行。

而且, val -properties不能在构造函数执行后重新分配,所以不可变性也会丢失。

所以问题是:Kotlin代码如何适应与JPA无代码重复,选择“魔术”的初始值和不可变性的损失?

PS除了JPA,Hibernate是否可以构造没有默认构造函数的对象呢?

从Kotlin 1.0.6开始kotlin-noarg编译器插件会为已经使用选定注释注释的类生成合成默认kotlin-noarg器。

如果使用gradle,应用kotlin-jpa插件就足以生成用@Entity注解的类的默认构造函数:

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

对于Maven:

  kotlin-maven-plugin org.jetbrains.kotlin ${kotlin.version}   jpa     org.jetbrains.kotlin kotlin-maven-noarg ${kotlin.version}    

只是提供所有参数的默认值,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 } 

如果您确信在施工时或者在第一次使用实例之前(或在第一次使用实例之前)很快就会填入值,您就可以使用它。

你会注意到我改变了lateinitbirthdate因为你不能使用带有lateinit原始值,而且他们也一定是var (限制可能会在将来被释放)。

所以对于不可变性来说,这不是一个完美的答案,在这方面与其他答案相同的问题。 解决方案是插件到库,可以理解Kotlin构造函数和映射属性到构造函数参数,而不是需要一个默认的构造函数。 jackson的Kotlin模块做到了这一点,所以显然是可能的。

另请参阅: https //stackoverflow.com/a/34624907/3679676 ,了解类似的选项。

没有办法像这样保持不变性。 在构建实例时,必须初始化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() } 
 @Entity data class Person(/*@Id @GeneratedValue var id: Long? = null,*/ var name: String? = null, var age: Int? = null) 

初始值是需要的,如果你想重用构造函数的不同领域,kotlin不允许空值。 所以,当你计划省略字段,在构造函数中使用这个表单: var field: Type? = defaultValue var field: Type? = defaultValue

jpa不需要参数构造函数:

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

没有代码重复。 如果您需要构建实体并且只需要设置年龄,请使用以下表单:

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

没有魔法(只是阅读文档)

我一直在与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() } } 

当然这个解决方案并不完美。 您仍然需要创建一些不需要的样板代码。 还有一种情况,在一个实体类中不能很好地与stubbing – parent – child关系解决 – 就像这样:

 @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特性(例如,无效安全性)。 我只希望JPA和/或Kotlin的未来版本将使我们的代码更加简单和更好。

和@pawelbial类似,我使用了companion对象来创建一个默认的实例,但不是定义一个辅助构造函数,而是使用像@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所提到的,当TestEntity类“具有” TestEntity类,因为STUB在构造函数运行时不会被初始化的时候,这将不起作用。

我是一个结节本人,但似乎你必须明确的初始化和回退到这样的空值

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

这些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不起作用。