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

hot*_*key 113 hibernate jpa default-constructor kotlin

正如JPA所要求的那样,@Entity类应该有一个默认(非arg)构造函数,用于在从数据库中检索对象时实例化对象.

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

class Person(val name: String, val age: Int) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)

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

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

在情况下,当性能有一些更复杂的类型不只是StringInt他们是不可为空的,它看起来完全坏为他们提供价值,尤其是当有在主构造和太多的代码init块,当这些参数都在积极使用- - 当他们通过反射重新分配时,大部分代码将再次执行.

此外,val在构造函数执行后,-properties不能重新分配,因此不可变性也会丢失.

所以问题是:Kotlin代码如何适应JPA而不需要代码重复,选择"神奇"的初始值和失去不变性?

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

Ing*_*gel 123

从Kotlin 1.0.6开始,kotlin-noarg编译器插件为已使用选定注释注释的类生成合成默认构造函数.

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

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

apply plugin: "kotlin-jpa"
Run Code Online (Sandbox Code Playgroud)

对于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>
Run Code Online (Sandbox Code Playgroud)

  • 你是否可以扩展一下如何在你的kotlin代码中使用它,即使它是"你的`数据类foo(bar:String)`没有改变"的情况.很高兴看到一个更完整的例子说明这是如何适应的.谢谢 (4认同)
  • 这是一篇博客文章,介绍了`kotlin-noarg`和`kotlin-jpa`以及详细说明其目的的链接https://blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here / (4认同)
  • 不为我工作。它仅在我将构造函数字段设为可选时才有效。这意味着该插件不起作用。 (4认同)
  • @jannnik您可以使用“@Embeddable”属性标记主键类,即使您不需要它。这样,它就会被“kotlin-jpa”拾取。 (3认同)

小智 30

只需为所有参数提供默认值,Kotlin将为您创建默认构造函数.

@Entity
data class Person(val name: String="", val age: Int=0)
Run Code Online (Sandbox Code Playgroud)

请参阅NOTE以下部分下方的框:

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

  • 你显然没有读过他的问题,否则你会看到他声明默认参数不好看的部分,特别是对于更复杂的对象.更不用说,为某些东西添加默认值会隐藏其他问题. (14认同)
  • 有时您无法提供合理的默认值。以一个人的给定示例为例,您确实应该使用出生日期对其进行建模,因为它不会改变(当然,例外以某种方式适用于某处)但是没有合理的默认值。因此,从纯代码的角度来看,您必须将 DoB 传递给 person 构造函数,从而确保您永远不会拥有一个没有有效年龄的人。问题是,JPA 喜欢工作的方式,它喜欢使用无参数构造函数创建一个对象,然后设置所有内容。 (3认同)
  • 另外,您不应该将数据类与 JPA 一起使用:“不要将数据类与 val 属性一起使用,因为 JPA 不适用于不可变类或由数据类自动生成的方法。” https://spring.io/guides/tutorials/spring-boot-kotlin/#_persistence_with_jpa (2认同)

Jay*_*ard 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
}
Run Code Online (Sandbox Code Playgroud)

当您确定某些内容将在构造时或很快之后(以及在第一次使用实例之前)填充值时,您将使用此方法.

你会注意到我改为age,birthdate因为你不能使用原始值,lateinit它们目前也必须var(限制可能在将来发布).

因此,对于不变性而言,这不是一个完美的答案,在这方面与其他答案一样.解决方案是插件到库,可以处理理解Kotlin构造函数和将属性映射到构造函数参数,而不需要默认构造函数.杰克逊Kotlin模块就是这样做的,所以显然是可能的.

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

  • 相似但不一样.如果使用Delegate,它会改变Java对实际字段的序列化所看到的内容(它看到委托类).此外,最好使用`lateinit`当你有一个明确定义的生命周期低保初始化即将建成后,它是为那些案件.代表更倾向于"在第一次使用之前的某个时间".虽然从技术上讲它们具有相似的行为和保护,但它们并不相同. (4认同)

edu*_*nti 9

在 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"
}
Run Code Online (Sandbox Code Playgroud)


Mak*_*min 6

@Entity data class Person(/*@Id @GeneratedValue var id: Long? = null,*/
                          var name: String? = null,
                          var age: Int? = null)
Run Code Online (Sandbox Code Playgroud)

如果要为不同的字段重用构造函数,则需要初始值,kotlin不允许空值.因此,无论何时计划省略字段,请在构造函数中使用此表单:var field: Type? = defaultValue

jpa不需要参数构造函数:

val entity = Person() // Person(name=null, age=null)
Run Code Online (Sandbox Code Playgroud)

没有代码重复.如果您需要构造实体且仅​​设置年龄,请使用以下形式:

val entity = Person(age = 33) // Person(name=null, age=33)
Run Code Online (Sandbox Code Playgroud)

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

  • 虽然此代码片段可能会解决问题,但 [包括解释](http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) 确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而那些人可能不知道您提出代码建议的原因。 (2认同)

小智 5

我自己就是一个小块头,但似乎您必须像这样显式初始化并回退到空值

@Entity
class Person(val name: String? = null, val age: Int? = null)
Run Code Online (Sandbox Code Playgroud)