Котлин с 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. А это правда, что спящий в стороне от СПД может построить объекты без конструктора по умолчанию?
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? = defaultValuejpa не требуется конструктор аргументов:
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: Stringvar path: LtreeType не работает.
Comments