Котлин с JPA: конструктор по умолчанию ад



как требует JPA,@Entity классы должны иметь конструктор по умолчанию (не arg) для создания экземпляров объектов при их извлечении из базы данных.



в Kotlin свойства очень удобно объявлять в основном конструкторе, как в следующем примере:



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


но когда конструктор non-arg объявлен как вторичный, он требует, чтобы значения для основного конструктора передавались, поэтому для них необходимы некоторые допустимые значения, например здесь:



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


в случае, когда свойства имеют несколько более сложный тип, чем просто String и Int и они не обнуляются, это выглядит совершенно плохо, чтобы предоставить значения для них, особенно когда есть много кода в основном конструкторе и init блоки и когда параметры активно используются -- когда они должны быть переназначены через отражение большая часть кода будет выполнена снова.



кроме того, val-свойства не могут быть переназначены после конструктор выполняется, поэтому неизменность также теряется.



Итак, вопрос: как код Kotlin может быть адаптирован для работы с JPA без дублирования кода, выбора "волшебных" начальных значений и потери неизменности?



P. S. А это правда, что спящий в стороне от СПД может построить объекты без конструктора по умолчанию?

719   9  

9 ответов:

по состоянию на 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>

просто укажите значения по умолчанию для всех аргументов, Котлин сделает конструктор по умолчанию для вас.

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

посмотреть NOTE поле под следующим разделом:

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

@D3xter имеет хороший ответ для одной модели, другая-более новая функция в Котлине под названием 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 и сопоставление свойств с параметрами конструктора, вместо того, чтобы требовать конструктор по умолчанию. Элемент модуль Котлина для Джексона это, очевидно, возможно.

Читайте также: https://stackoverflow.com/a/34624907/3679676 для исследования подобных вариантов.

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

нет никакой магии (просто прочитайте документацию)

нет никакого способа сохранить неизменность, как это. При создании экземпляра необходимо инициализировать 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()
}

Я работаю с Kotlin + JPA довольно долго, и я создал свою собственную идею, как писать классы сущностей.

Я просто немного расширяю вашу первоначальную идею. Как вы сказали, мы можем создать частный конструктор без аргументов и предоставить значения по умолчанию для примитивы, но когда мы пытаемся использовать другие классы, это становится немного грязным. Моя идея заключается в создании статического объекта заглушки для класса сущностей, который вы в настоящее время записываете, например:

@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 из-за куриного яйца проблема - нам нужен заглушка для создания заглушки. К сожалению, нам нужно сделать это поле nullable (или какое-то подобное решение), чтобы код работал.

также на мой взгляд имея Id как последнее поле (и nullable) является вполне оптимальным. Мы не должны назначать его вручную, и пусть база данных сделает это за нас.

Я не говорю, что это идеальное решение, но я думаю, что оно использует читаемость кода сущности и функции Kotlin (например, нулевую безопасность). Я просто надеюсь, что будущие релизы JPA и/или Котлин сделает наш код еще проще и приятнее.

подобно @pawelbial я использовал companion object для создания экземпляра по умолчанию, однако вместо определения вторичного конструктора просто используйте конструкторы по умолчанию, такие как @iolo. Это избавляет вас от необходимости определять несколько конструкторов и упрощает код (хотя, конечно, определение сопутствующих объектов "заглушки" не совсем просто)

@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 класс, так как заглушка не будет инициализирована при запуске конструктора.

Я сам нуб, но, похоже, вам нужно явный инициализатор и резервный вариант для нулевого значения, как это

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

эти линии сборки Gradle помогли me:
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 не работает.

Comments

    Ничего не найдено.