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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

См. Также: https://stackoverflow.com/a/34624907/3679676 для исследования аналогичных вариантов.

Невозможно сохранить неизменность подобным образом. 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() } 
 @Entity data class Person(/*@Id @GeneratedValue var id: Long? = null,*/ var name: String? = null, var age: Int? = null) 

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

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

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

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

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

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

Я давно работаю с 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, я могу легко использовать заглушку, которую я только что создал. Например:

 @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 сделают наш код еще более простым и приятным.

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

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

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

Эти линии сборки 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 не работает.