Zgodnie z wymaganiami JPA @Entity
klasy powinny mieć domyślny konstruktor (bez argumentów) do tworzenia instancji obiektów podczas pobierania ich z bazy danych.
W Kotlinie właściwości są bardzo wygodne do zadeklarowania w głównym konstruktorze, jak w poniższym przykładzie:
class Person(val name: String, val age: Int) { /* ... */ }
Ale kiedy konstruktor nie-arg jest zadeklarowany jako pomocniczy, wymaga przekazania wartości dla konstruktora głównego, więc potrzebne są dla nich pewne prawidłowe wartości, jak tutaj:
@Entity
class Person(val name: String, val age: Int) {
private constructor(): this("", 0)
}
W przypadku, gdy właściwości mają bardziej złożony typ niż tylko String
i Int
nie dopuszczają wartości null, podanie wartości dla nich wygląda zupełnie źle, zwłaszcza gdy jest dużo kodu w głównym konstruktorze i init
blokach oraz gdy parametry są aktywnie używane - - kiedy mają zostać ponownie przypisane poprzez odbicie, większość kodu zostanie ponownie wykonana.
Ponadto, val
-właściwości nie mogą być ponownie przypisane po wykonaniu konstruktora, więc niezmienność również zostaje utracona.
Pytanie brzmi więc: w jaki sposób można dostosować kod Kotlin do pracy z JPA bez duplikowania kodu, wybierając „magiczne” wartości początkowe i utratę niezmienności?
PS Czy to prawda, że Hibernate poza JPA może konstruować obiekty bez domyślnego konstruktora?
źródło
INFO -- org.hibernate.tuple.PojoInstantiator: HHH000182: No default (no-argument) constructor for class: Test (class must be instantiated by Interceptor)
- więc tak, Hibernate może działać bez domyślnego konstruktora.Odpowiedzi:
Począwszy od Kotlin 1.0.6 ,
kotlin-noarg
wtyczka kompilatora generuje syntetyczne domyślne konstruktory dla klas, które zostały opatrzone adnotacjami wybranymi adnotacjami.Jeśli używasz gradle, zastosowanie
kotlin-jpa
wtyczki wystarczy do wygenerowania domyślnych konstruktorów dla klas z adnotacjami@Entity
:Dla Mavena:
źródło
data class foo(bar: String)
się nie zmienia". Byłoby miło zobaczyć pełniejszy przykład tego, jak to pasuje. Dziękikotlin-noarg
ikotlin-jpa
zawiera linki opisujące ich cel blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here@Embeddable
atrybutem, nawet jeśli w innym przypadku nie jest to potrzebne. W ten sposób zostanie odebrany przezkotlin-jpa
.po prostu podaj domyślne wartości dla wszystkich argumentów, Kotlin zrobi dla ciebie domyślny konstruktor.
zobacz
NOTE
ramkę pod następującą sekcją:https://kotlinlang.org/docs/reference/classes.html#secondary-constructors
źródło
@ D3xter ma dobrą odpowiedź dla jednego modelu, drugi to nowsza funkcja w Kotlinie o nazwie
lateinit
:Użyjesz tego, gdy jesteś pewien, że coś wypełni wartości w czasie tworzenia lub wkrótce po (i przed pierwszym użyciem instancji).
Zauważysz, że zmieniłem się
age
na,birthdate
ponieważ nie możesz używać prymitywnych wartości zlateinit
i na razie muszą byćvar
(ograniczenie może zostać zwolnione w przyszłości).Więc nie jest to doskonała odpowiedź na niezmienność, ten sam problem, co inna odpowiedź w tym względzie. Rozwiązaniem są wtyczki do bibliotek, które mogą obsługiwać rozumienie konstruktora Kotlin i mapowanie właściwości na parametry konstruktora, zamiast wymagać domyślnego konstruktora. Moduł Kotlin dla Jackson robi to, więc jest wyraźnie możliwe.
Zobacz też: https://stackoverflow.com/a/34624907/3679676, aby poznać podobne opcje.
źródło
lateinit
gdy masz dobrze zdefiniowany cykl życia gwarantujący inicjalizację wkrótce po zbudowaniu, jest przeznaczony do takich przypadków. Natomiast delegat jest bardziej przeznaczony do „jakiegoś czasu przed pierwszym użyciem”. Chociaż technicznie mają podobne zachowanie i ochronę, nie są identyczne.false
dla Ints i Booleans. Nie jestem pewien, jak to wpłynie na kod frameworkaWartości początkowe są wymagane, jeśli chcesz ponownie użyć konstruktora dla różnych pól, kotlin nie dopuszcza wartości null. Jeśli więc planujesz pominąć pole, użyj w konstruktorze tego formularza:
var field: Type? = defaultValue
jpa nie wymagał konstruktora argumentów:
nie ma powielania kodu. Jeśli potrzebujesz encji konstrukcji i tylko ustawisz wiek, użyj tego formularza:
nie ma magii (wystarczy przeczytać dokumentację)
źródło
Nie ma sposobu, aby zachować taką niezmienność. Vals MUSI zostać zainicjowany podczas konstruowania instancji.
Jednym ze sposobów na zrobienie tego bez niezmienności jest:
źródło
Z Kotlin + JPA pracuję już od dłuższego czasu i stworzyłem własny pomysł na pisanie klas Entity.
Tylko nieznacznie przedłużam twój początkowy pomysł. Jak powiedziałeś, możemy stworzyć prywatny konstruktor bez argumentów i podać domyślne wartości dla prymitywów , ale kiedy próbujemy użyć innych klas, robi się trochę bałagan. Moim pomysłem jest utworzenie statycznego obiektu STUB dla klasy encji, którą obecnie zapisujesz, np .:
a kiedy mam klasę encji, która jest powiązana z TestEntity , mogę z łatwością użyć kodu źródłowego , który właśnie utworzyłem. Na przykład:
Oczywiście to rozwiązanie nie jest idealne. Nadal musisz utworzyć standardowy kod, który nie powinien być wymagany. Jest też jeden przypadek, którego nie można ładnie rozwiązać za pomocą stubbing - relacji rodzic-dziecko w ramach jednej klasy encji - na przykład:
Ten kod wygeneruje wyjątek NullPointerException z powodu problemu z jajkiem kurzego - do utworzenia STUB potrzebujemy STUB. Niestety, aby kod działał, musimy uczynić to pole dopuszczającym wartość null (lub podobnym rozwiązaniem).
Moim zdaniem również posiadanie Id jako ostatniego pola (i wartości null) jest całkiem optymalne. Nie powinniśmy przypisywać tego ręcznie i pozwolić, aby zrobiła to za nas baza danych.
Nie mówię, że jest to idealne rozwiązanie, ale myślę, że wykorzystuje czytelność kodu encji i funkcje Kotlina (np. Zerowe bezpieczeństwo). Mam tylko nadzieję, że przyszłe wydania JPA i / lub Kotlin uczynią nasz kod jeszcze prostszym i ładniejszym.
źródło
Jak wspomniano powyżej, musisz użyć
no-arg
wtyczki no dostarczonej przez Jetbrains.Jeśli używasz Eclispe , może być konieczna edycja ustawień kompilatora Kotlin.
Okno> Preferencje> Kotlin> Kompilator
Aktywuj
no-arg
wtyczkę w sekcji Wtyczki kompilatora.Zobacz: https://discuss.kotlinlang.org/t/kotlin-allopen-plugin-doesnt-work-with-sts/13277/10
źródło
Sam jestem nubem, ale wydaje się, że musisz jawnie inicjalizować i powrócić do wartości null w ten sposób
źródło
Podobnie jak w przypadku @pawelbial, użyłem obiektu towarzyszącego, aby utworzyć domyślną instancję, jednak zamiast definiować konstruktor pomocniczy, wystarczy użyć domyślnych argumentów konstruktora, takich jak @iolo. Dzięki temu nie musisz definiować wielu konstruktorów i kod jest prostszy (chociaż jest to oczywiste, definiowanie obiektów towarzyszących „STUB” nie jest do końca proste)
A potem na zajęcia, które dotyczą
TestEntity
Jak wspomniał @pawelbial, nie zadziała to tam, gdzie
TestEntity
klasa „ma”TestEntity
klasę, ponieważ STUB nie zostanie zainicjowany po uruchomieniu konstruktora.źródło
Pomogły mi te linie budowania Gradle:
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa/1.1.50 .
Przynajmniej jest zbudowany w IntelliJ. W tej chwili nie działa w linii poleceń.
I mam
i
var path: LtreeType nie działa.
źródło
Jeśli dodałeś wtyczkę gradle https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa ale nie zadziałała, prawdopodobnie wersja jest przestarzała. Byłem na 1.3.30 i mi to nie wyszło. Po uaktualnieniu do 1.3.41 (najpóźniej w momencie pisania), zadziałało.
Uwaga: wersja kotlin powinna być taka sama jak ta wtyczka, np .: tak dodałem oba:
źródło