JPA ile Kotlin: varsayılan yapıcı cehennemi


132

JPA'nın gerektirdiği gibi, @Entitysınıflar, nesneleri veritabanından alırken örneklemek için varsayılan (arg olmayan) bir yapıcıya sahip olmalıdır.

Kotlin'de, aşağıdaki örnekte olduğu gibi, özellikler birincil kurucu içinde bildirilmek için çok uygundur:

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

Ancak arg olmayan yapıcı ikincil olarak bildirildiğinde, birincil kurucunun değerlerinin aktarılmasını gerektirir, bu nedenle bunlar için bazı geçerli değerler gereklidir, örneğin burada:

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

Özelliklerin yalnızca daha karmaşık bir türe sahip olması Stringve Intboş değer atanamaz olmaları durumunda, özellikle birincil oluşturucu ve initbloklarda çok fazla kod olduğunda ve parametreler aktif olarak kullanıldığında , bunlar için değerleri sağlamak tamamen kötü görünür - - yansıtma yoluyla yeniden atandıklarında, kodun çoğu tekrar çalıştırılacaktır.

Dahası, val-özellikler, kurucu çalıştırıldıktan sonra yeniden atanamaz, dolayısıyla değişmezlik de kaybolur.

Öyleyse soru şu: Kotlin kodu, kod kopyası olmadan, "sihirli" başlangıç ​​değerleri seçilerek ve değişmezlik kaybı olmadan JPA ile çalışmak üzere nasıl uyarlanabilir?

Not: JPA dışında Hibernate'in varsayılan kurucu olmadan nesneler oluşturabileceği doğru mu?


1
INFO -- org.hibernate.tuple.PojoInstantiator: HHH000182: No default (no-argument) constructor for class: Test (class must be instantiated by Interceptor)- evet, Hibernate varsayılan kurucu olmadan çalışabilir.
Michael Piefel

Bunu yapmanın yolu ayarlayıcılarla - aka: Değişkenlik. Varsayılan kurucuyu başlatır ve ardından ayarlayıcıları arar. Değişmez nesneler istiyorum. Yapılabilecek tek yol, hazırda bekletmelerin kurucuya bakmaya başlamasıdır. Bu hibernate.atlassian.net/browse/HHH-9440
Christian Bongiorno

Yanıtlar:


145

Kotlin 1.0.6'dan itibaren , kotlin-noargderleyici eklentisi, seçili notlarla açıklanmış sınıflar için sentetik varsayılan yorumlayıcılar üretir.

Gradle kullanıyorsanız, kotlin-jpaeklentiyi uygulamak, şu notlarla not verilen sınıflar için varsayılan oluşturucular oluşturmak için yeterlidir @Entity:

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
    }
}

apply plugin: "kotlin-jpa"

Maven için:

<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>

4
Bunun kotlin kodunuzda nasıl kullanılacağına dair biraz bilgi verebilir misiniz, bu bir " data class foo(bar: String)değişmezsiniz" durumu olsa bile . Bunun yerine nasıl oturduğuna dair daha eksiksiz bir örnek görmek güzel olurdu. Teşekkürler
thecoshman

5
Bu, tanıtılan kotlin-noargve kotlin-jpaamaçlarını detaylandıran bağlantıların bulunduğu blog gönderisidir blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here
Dalibor Filus

1
Peki ya bir varlık olmayan ancak varsayılan bir kurucuya ihtiyaç duyan CustomerEntityPK gibi bir birincil anahtar sınıf?
jannnik

3
Benim için çalışmıyor. Yalnızca yapıcı alanlarını isteğe bağlı yaparsam çalışır. Bu, eklentinin çalışmadığı anlamına gelir.
Ixx

3
@jannnik @EmbeddableAksi takdirde ihtiyacınız olmasa bile birincil anahtar sınıfını öznitelikle işaretleyebilirsiniz . Bu şekilde, tarafından alınacaktır kotlin-jpa.
svick

33

sadece tüm argümanlar için varsayılan değerler sağlayın, Kotlin sizin için varsayılan kurucu yapacaktır.

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

bkz NOTEaşağıdaki bölümde aşağıdaki kutuyu:

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


18
Açıkça onun sorusunu okumadınız, aksi takdirde, özellikle daha karmaşık nesneler için, varsayılan argümanların kötü göründüğünü söylediği kısmı görürdünüz. Bahsetmiyorum bile, bir şey için varsayılan değerler eklemek diğer sorunları gizler.
2016

1
Varsayılan değerleri sağlamak neden kötü bir fikirdir? Java'nın bağımsız değişken oluşturucu kullanmadığında bile, alanlara varsayılan değerler atanır (örneğin, başvuru türlerine null).
Umesh Rajbhandari

1
Mantıklı varsayılanlar sağlayamayacağınız zamanlar vardır. Verilen bir kişi örneğini ele alalım, onu gerçekten doğum tarihiyle modellemelisiniz, çünkü bu değişmez (elbette, bir şekilde istisnalar geçerlidir), ancak buna verilecek makul bir varsayılan yoktur. Bu nedenle, saf bir kod bakış açısı oluşturduğunuzda, kişi kurucusuna bir DoB geçirmelisiniz, böylece hiçbir zaman geçerli bir yaşı olmayan bir kişiye sahip olamayacağınızdan emin olmalısınız. Sorun şu ki, JPA'nın çalışmayı sevme şekli, bağımsız bir kurucu ile bir nesne yapmayı ve sonra her şeyi ayarlamayı sever.
thecoshman

1
Bunu yapmanın doğru yolu olduğunu düşünüyorum, bu cevap JPA kullanmadığınız veya hazırda bekletmediğiniz diğer durumlarda işe yarar. Ayrıca cevapta belirtildiği gibi belgelere göre önerilen yoldur.
Mohammad Rafigh

1
Ayrıca, JPA ile veri sınıfını kullanmamalısınız: "JPA değişmez sınıflarla veya veri sınıfları tarafından otomatik olarak oluşturulan yöntemlerle çalışmak üzere tasarlanmadığı için val özelliklerine sahip veri sınıflarını kullanmayın." spring.io/guides/tutorials/spring-boot-kotlin/…
Tafsen

11

@ D3xter'in bir model için iyi bir cevabı var, diğeri Kotlin'deki daha yeni bir özellik lateinit:

class Entity() {
    constructor(name: String, age: Date): this() {
        this.name = name
        this.birthdate = age
    }

    lateinit var name: String
    lateinit var birthdate: Date
}

Bunu, yapım zamanında veya çok kısa bir süre sonra (ve örneğin ilk kullanımından önce) bir şeyin değerleri dolduracağından emin olduğunuzda kullanırsınız.

Olarak değiştiğimi not edeceksiniz age, birthdateçünkü ilkel değerleri kullanamazsınız lateinitve şu an için de olmalıdır var(gelecekte kısıtlama kaldırılabilir).

Yani değişmezlik için mükemmel bir cevap değil, bu konuda diğer cevapla aynı problem. Bunun çözümü, varsayılan bir kurucu gerektirmek yerine Kotlin yapıcısını anlamayı ve özellikleri yapıcı parametrelerine eşlemeyi işleyebilen kitaplık eklentileridir. Jackson için Kotlin modülü yapar, bu yüzden açıkça mümkündür.

Ayrıca bakınız: Benzer seçeneklerin keşfi için https://stackoverflow.com/a/34624907/3679676 .


Lateinit ve Delegates.notNull () 'un aynı olduğunu unutmayın.
fasth

4
benzer ama aynı değil. Delege kullanılırsa, Java tarafından gerçek alanın serileştirilmesi için görülenleri değiştirir (delege sınıfını görür). Ayrıca, lateinitinşaattan hemen sonra başlatmayı garanti eden iyi tanımlanmış bir yaşam döngüsüne sahipseniz kullanmak daha iyidir , bu durumlar için tasarlanmıştır. Oysa delege daha çok "ilk kullanımdan bir süre önce" içindir. Teknik olarak benzer davranış ve korumaya sahip olsalar da, aynı değiller.
Jayson Minard

İlkel değerleri kullanmanız gerekiyorsa, aklıma gelen tek şey, bir nesneyi başlatırken "varsayılan değerler" kullanmaktır ve bununla falsesırasıyla 0 ve İns ve Boole'lar için kullanmayı kastediyorum . Bunun çerçeve kodunu nasıl etkileyeceğinden emin değilim
OzzyTheGiant

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

Yapıcıyı farklı alanlar için yeniden kullanmak istiyorsanız başlangıç ​​değerleri gereklidir, kotlin boş değerlere izin vermez. Dolayısıyla, alanı atlamayı planladığınızda, yapıcıda bu formu kullanın:var field: Type? = defaultValue

jpa bağımsız değişken oluşturucu gerektirmedi:

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

kod kopyası yoktur. Varlık oluşturmaya ve yalnızca yaş kurulumuna ihtiyacınız varsa, bu formu kullanın:

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

sihir yok (sadece belgeleri okuyun)


1
Bu kod parçacığı soruyu çözebilirken, bir açıklama eklemek, yayınınızın kalitesini iyileştirmeye gerçekten yardımcı olur. Gelecekte okuyucular için soruyu yanıtladığınızı ve bu kişilerin kod önerinizin nedenlerini bilmeyebileceklerini unutmayın.
DimaSan

... @DimaSan, haklısın, ama bu iş parçacığı zaten bazı görevlerde açıklamalar var
Maksim Kostromin

Ancak pasajınız farklı ve farklı bir açıklaması olsa da, şimdi çok daha net.
DimaSan

4

Değişmezliği bu şekilde korumanın bir yolu yok. Örneği oluştururken Vals başlatılmalıdır ZORUNLU.

Bunu değişmezlik olmadan yapmanın bir yolu şudur:

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()
}

Öyleyse, Hibernate'e sütunları yapıcı argümanlarıyla eşlemesini söylemenin bir yolu yok mu? Belki de arg olmayan yapıcı gerektirmeyen bir ORM çerçevesi / kitaplığı var mı? :)
kısayol

Bundan emin değilim, Hibernate ile uzun süredir çalışmadım. Ancak bir şekilde adlandırılmış parametrelerle uygulanması mümkün olmalıdır.
D3xter

Bence hazırda bekletmenin bunu biraz (fazla değil) işle yapabileceğini düşünüyorum. Java 8'de, aslında yapıcıda adlandırılmış parametreleriniz olabilir ve bunlar şimdi alanlara olduğu gibi eşlenebilir.
Christian Bongiorno

3

Bir süredir Kotlin + JPA ile çalışıyorum ve Entity sınıflarını nasıl yazacağım konusunda kendi fikrimi yarattım.

İlk fikrinizi biraz genişletiyorum. Dediğiniz gibi özel argümansız kurucu oluşturabilir ve ilkel değerler için varsayılan değerler sağlayabiliriz , ancak başka sınıflar kullanmaya ihtiyaç duyduğumuzda biraz karmaşıklaşır. Benim fikrim, şu anda yazdığınız varlık sınıfı için statik STUB nesnesi oluşturmaktır, örneğin:

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

    companion object {
        val STUB = TestEntity()
    }
}

ve TestEntity ile ilgili bir varlık sınıfım olduğunda, yeni oluşturduğum saplamayı kolayca kullanabilirim. Örneğin:

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

    companion object {
        val STUB = RelatedEntity()
    }
}

Elbette bu çözüm mükemmel değil. Yine de gerekli olmaması gereken bazı standart kodlar oluşturmanız gerekir. Ayrıca, saplama ile güzelce çözülemeyen bir durum vardır - bir varlık sınıfı içindeki ebeveyn-çocuk ilişkisi - şöyle:

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

    companion object {
        val STUB = TestEntity()
    }
}

Bu kod NullPointerException oluşturacak tavuk yumurtası sorunu nedeniyle oluşturacaktır - STUB oluşturmak için STUB'a ihtiyacımız var. Maalesef kodun çalışması için bu alanı null yapılabilir (veya benzer bir çözüm) yapmamız gerekiyor.

Ayrıca bence Id'nin son alan (ve null yapılabilir) olması oldukça optimaldir. El ile atamamalı ve veritabanının bizim için yapmasına izin vermemeliyiz.

Bunun mükemmel bir çözüm olduğunu söylemiyorum, ancak varlık kodunun okunabilirliğini ve Kotlin özelliklerini (örneğin, boş güvenlik) kullandığını düşünüyorum. Umarım JPA ve / veya Kotlin'in gelecekteki sürümleri kodumuzu daha da basit ve güzel hale getirir.



2

Ben de bir nub'um ama görünüşe göre başlatıcıyı ve bunun gibi boş değere geri dönüşü belirtmeniz gerekiyor

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

1

@Pawelbial'e benzer şekilde, varsayılan bir örnek oluşturmak için eşlik eden nesne kullandım, ancak ikincil bir kurucu tanımlamak yerine @iolo gibi varsayılan yapıcı değiştirgelerini kullanın. Bu, sizi birden fazla kurucu tanımlama zorunluluğundan kurtarır ve kodu daha basit tutar (verilmiş olmasına rağmen, "STUB" eşlik nesnelerini tanımlamak tam olarak basit tutmaz)

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

    companion object {
        val STUB = TestEntity()
    }
}

Ve sonra ilgili sınıflar için TestEntity

@Entity
data class RelatedEntity(
    val testEntity: TestEntity = TestEntity:STUB,
    @Id @GeneratedValue val id: Int? = null
)

@Pawelbial'ın da belirttiği gibi, bu TestEntitysınıfın "bir" TestEntitysınıfa sahip olduğu yerde çalışmayacaktır, çünkü yapıcı çalıştırıldığında STUB başlatılmayacaktır.


1

Bu Gradle derleme satırları bana yardımcı oldu:
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa/1.1.50 .
En azından IntelliJ'de oluşturulur. Şu anda komut satırında başarısız oluyor.

Ve bir

class LtreeType : UserType

ve

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

var yol: LtreeType çalışmadı.


1

Gradle eklentisini eklediyseniz https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa çalışmadıysa, sürümün eski olma ihtimali vardır. 1.3.30'daydım ve benim için işe yaramadı. 1.3.41'e (en son yazma sırasında) yükselttikten sonra işe yaradı.

Not: kotlin sürümü bu eklenti ile aynı olmalıdır, örneğin: ikisini de böyle ekledim:

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
    }
}

Micronaut ile çalışıyorum ve 1.3.41 sürümüyle çalışmasını sağladım. Gradle, Kotlin sürümümün 1.3.21 olduğunu ve herhangi bir sorun görmedim, diğer tüm eklentilerin ('kapt / jvm / allopen') 1.3.21'de olduğunu söyledi.Ayrıca eklenti DSL biçimini kullanıyorum
Gavin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.