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

时间:2015-08-16 17:54:02

标签: hibernate jpa default-constructor kotlin

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

如果属性的类型比StringInt更复杂且它们不可为空,那么为它们提供值看起来很糟糕,特别是当代码很多时在主构造函数和init块中以及当参数被主动使用时 - 当它们通过反射重新分配时,大多数代码将再次执行。

此外,val - 属性在构造函数执行后无法重新赋值,因此不变性也会丢失。

所以问题是:Kotlin代码如何在没有代码重复的情况下适应JPA,选择“神奇”的初始值和失去不变性?

P.S。除了JPA之外,Hibernate是否可以构造没有默认构造函数的对象?

13 个答案:

答案 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 }