2015-08-16 6 views
60

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

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

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

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

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

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

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

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

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

+1

'INFO - org.hibernate.tuple.PojoInstantiator: HHH000182: конструктор по умолчанию (без аргументов) для класса: Test (класс должен быть создан Interceptor)' - так, да, Hibernate может работать без конструктора по умолчанию , –

+0

То, как это делается, с сеттерами - aka: Mutability. Он создает экземпляр конструктора по умолчанию, а затем ищет сеттеры. Я хочу неизменные объекты. Единственный способ, который можно сделать, - это спящий режим, начинающийся с поиска конструктора. Существует открытый билет на этом https://hibernate.atlassian.net/browse/HHH-9440 –

ответ

64

В Котлин 1.0.6, плагин kotlin-noarg компилятор генерирует синтетические construtors по умолчанию для классов, которые были аннотированных с выбранными аннотациями.

Если вы используете 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, даже если это случай вашего «класса данных foo (bar: String)« не изменяется ». Было бы неплохо увидеть более полный пример того, как это укладывается на место. Спасибо – thecoshman

+0

Вам не нужно ничего делать в коде. Классы, аннотированные с помощью '@ Entity', автоматически получат конструктор по умолчанию с указанной выше конфигурацией. –

+2

Это сообщение в блоге, в котором введены 'kotlin-noarg' и' kotlin-jpa' ссылки со ссылками на их цель https://blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is- here/ –

4

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

Так что даже нет возможности сообщить Hibernate сопоставить столбцы с конструкторами args? Ну, может быть, есть структура/библиотека ORM, которая не требует конструктора без аргументов? :) – hotkey

+0

Не уверен в этом, давно не работал с Hibernate. Но нужно как-то реализовать с именованными параметрами. – D3xter

+0

Я думаю, что спящий режим может сделать это немного (не так много) работы. В java 8 вы можете иметь параметры, указанные в конструкторе, и они могут быть сопоставлены так же, как и теперь в полях. –

8

@ 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 и сопоставлять свойства с параметрами конструктора, вместо того, чтобы требовать конструктор по умолчанию. Kotlin module for Jackson делает это, так что это очевидно.

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

+0

Стоит отметить, что lateinit и Delegates.notNull() совпадают. – user2138356

+4

похоже, но не то же самое. Если используется делегат, он изменяет то, что видно для сериализации фактического поля Java (он видит класс делегата). Кроме того, лучше использовать 'lateinit', когда у вас есть четко определенный жизненный цикл, гарантирующий инициализацию вскоре после строительства, он предназначен для этих случаев. В то время как делегат больше предназначен для «когда-то до первого использования». Хотя технически они имеют аналогичное поведение и защиту, они не идентичны. –

22

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

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

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

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

+6

вы, очевидно, не читали его вопроса, иначе вы бы увидели ту часть, где он утверждает, что аргументы по умолчанию плохо выглядят, особенно для более сложных объектов. Не говоря уже, добавление значений по умолчанию для чего-то скрывает другие проблемы. – snowe

+0

Почему плохая идея предоставить значения по умолчанию? Даже при использовании конструктора без аргументов args значения по умолчанию назначаются полям (например, null для ссылочных типов). –

+1

Есть моменты, когда вы не можете обеспечить разумные значения по умолчанию. Возьмите приведенный пример человека, вы действительно должны моделировать его с датой рождения, поскольку это не меняется (конечно, исключения применяются где-то как-то), но нет разумного дефолта, чтобы дать этому. Следовательно, формируя чистую кодовую точку зрения, вы должны передать DoB в конструктор person, тем самым гарантируя, что у вас никогда не будет человека, у которого нет действительного возраста. Проблема в том, как JPA любит работать, ему нравится создавать объект с конструктором no-args, а затем устанавливать все. – thecoshman

2

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

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

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

@Entity 
data class TestEntity(
     val testEntity: TestEntity, 
     @Id @GeneratedValue val id: Long? = null 
) { 
    private constructor() : this(STUB) 

    companion object { 
     val STUB = TestEntity() 
    } 
} 

Этот код будет производить NullPointerException из-за выпуск куриного яйца - нам нужна заглушка создайте STUB. К сожалению, нам нужно сделать это поле нулевым (или некоторым аналогичным решением), чтобы заставить код работать.

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

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

1

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

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) 

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

+1

Хотя этот фрагмент кода может решить вопрос, [включая объяснение] (http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин вашего предложения кода. – DimaSan

+0

@DimaSan, вы правы, но эта нить уже имеет объяснения в некоторых сообщениях ... –

+0

Но ваш фрагмент отличается и может отличаться от описания, так или иначе, теперь это намного яснее. – DimaSan

1

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

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

Эти Gradle строить линии помогли мне:
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa/1.1.50.
По крайней мере, он построен в IntelliJ. На данный момент он не работает в командной строке.

И я

@Column(name = "path", nullable = false, columnDefinition = "ltree") 
    @Type(type = "com.tgt.unitplanning.data.LtreeType") 
    var path: String 

уаг путь к

class LtreeType : UserType 

и

: LtreeType не работает.

Смежные вопросы