首页 文章

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

提问于
浏览
88

正如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 -properties,因此也不会失去不变性 .

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

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

9 回答

  • 9

    As of Kotlin 1.0.6kotlin-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

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

    @Entity
    data class Person(val name: String="", val age: Int=0)
    

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

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

  • 6

    @ 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这样做,所以显然是可能的 .

    See also: https://stackoverflow.com/a/34624907/3679676用于探索类似的选项 .

  • 1
    @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)
    

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

  • 3

    没有办法像这样保持不变性 . 构造实例时必须初始化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()
    }
    
  • 29

    我已经和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将使我们的代码更简单,更好 .

  • 1

    与@pawelbial类似,我使用了伴随对象来创建默认实例,但是不是定义辅助构造函数,而是使用默认构造函数args,如@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起_1158688_类"has a" TestEntity 类时,这将不起作用 .

  • 4

    我自己是一个小组,但似乎你必须显式初始化并回退到这样的null值

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

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

相关问题