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 может создавать объекты без конструктора по умолчанию?
Ответы
Ответ 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