Подтвердить что ты не робот

Kotlin с 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 -properties не может быть переназначен после выполнения конструктора, поэтому неизменность также теряется.

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

P.S. Верно ли, что Hibernate в стороне от JPA может создавать объекты без конструктора по умолчанию?

4b9b3361

Ответ 1

Начиная с 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>

Ответ 2

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

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

см. раздел NOTE ниже в следующем разделе:

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

Ответ 3

@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 для Jackson делает это, поэтому это возможно.

См. также: fooobar.com/questions/106919/... для поиска похожих параметров.

Ответ 4

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

Начальные значения требуются, если вы хотите использовать конструктор повторного использования для разных полей, kotlin не допускает null. Поэтому всякий раз, когда вы планируете опустить поле, используйте эту форму в конструкторе: var field: Type? = defaultValue

jpa не требует конструктора аргументов:

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

нет дублирования кода. Если вам нужно создать объект и установить возраст устаревания, используйте эту форму:

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

нет волшебства (просто прочитайте документацию)

Ответ 5

Невозможно сохранить неизменность таким образом. 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()
}

Ответ 6

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

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

@Entity
data class TestEntity(
    val name: String,
    @Id @GeneratedValue val id: Int? = null
) {
    private constructor() : this("")

    companion object {
        val STUB = TestEntity()
    }
}

и когда у меня есть класс сущностей, связанный с TestEntity, я могу легко использовать stub, который я только что создал. Например:

@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 как последнего поля (и значение NULL) является весьма оптимальным. Мы не должны назначать его вручную и позволить базе данных делать это для нас.

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

Ответ 7

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

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

Ответ 8

Подобно @pawelbial, я использовал объект-компаньон для создания экземпляра по умолчанию, однако вместо определения вторичного конструктора просто используйте аргументы конструктора по умолчанию, такие как @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 не будет инициализирован при запуске конструктора.

Ответ 9

Эти строки построения 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: не был указан лит. тип.

Ответ 10

Если вы добавили подключаемый модуль 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

Как указано выше, вы должны использовать плагин no no-arg, предоставляемый Jetbrains.

Если вы используете Eclispe, возможно, вам придется изменить настройки компилятора Kotlin.

Окно> Настройки> Kotlin> Компилятор

Активируйте плагин no-arg в разделе плагинов компилятора.

Видеть: https://discuss.kotlinlang.org/t/kotlin-allopen-plugin-doesnt-work-with-sts/13277/10