Hibernate vs EclipseLink insertable = false, updatable = false

Fórumok

Sziasztok!

Nem sikerül működésre bírnom az EntityGraph-ot EclipseLink alatt (https://hup.hu/node/156399), gondoltam megpróbálom a Hibernate-et hátha ott nagyobb sikerrel járok.
Én kis naiv azt hittem hogy a persistence.xml-ben a provider átírásával megúszom a dolgot, de nem.
Az alkalmazásszerver GlassFish.


@Entity
@Table(name = "KEDVEZMENYEK_RAKTAR_EGYEDI")
@IdClass(KedvezmenyekRaktarEgyediPK.class)
public class KedvezmenyekRaktarEgyedi {
   private int idArTipus;
   private ArTipus arTipus;

    @Id
    @Column(name = "ID_AR_TIPUS", insertable = false, updatable = false)
    public int getIdArTipus() {
        return idArTipus;
    }

    public void setIdArTipus(int idArTipus) {
        this.idArTipus = idArTipus;
    }

    @OneToOne
    @JoinColumn(name = "ID_AR_TIPUS", referencedColumnName = "ID")
    public ArTipus getArTipus() {
        return arTipus;
    }

    public void setArTipus(ArTipus arTipus) {
        this.arTipus = arTipus;
    }
    
    ...
 
}

EclipseLink alatt tökéletesen működik a Hibernate-el viszont a következő hibaüzenetet kapom:

org.hibernate.MappingException: Repeated column in mapping for entity: KedvezmenyekRaktarEgyedi column: ID_AR_TIPUS (should be mapped with insert="false" update="false")

Specifikáció szerint ha az egyikben működik a dolog akkor a másikban is kellene ha jól gondolom.
Ha az

idArTipus

mezűnél beállítottam az

insertable = false, updatable = false

paramétereket akkor azt a Hibernate miért nem veszi figyelembe?

Hozzászólások

Szerintem az a baja, hogy a

@Column(name = "ID_AR_TIPUS", insertable = false, updatable = false)
public int getIdArTipus() {

és a

@JoinColumn(name = "ID_AR_TIPUS", referencedColumnName = "ID")
public ArTipus getArTipus() {

ugyanazt az adatbázis mezőt mappelik (ID_AR_TIPUS) és a kettő nem konzisztens (az insertable, updatable értékek miatt).

Igen, ugyanazt az adatbázis mezőt mappeli. Viszont EclipseLink alatt ez gond nélkül működik, most hogy lecserélném alatta a povidert Hibernate-re, a Hibernate valamiért a fenti kivételt dobja rá.

Az EclipseLink csak annyit kér, hogy az egyikre tegyek egy

insertable = false, updatable = false

megszorítást, ellenben a Hibernate csak a akkor nem dob kivételt ha a relációra

@OneToOne

teszem ezt a megszorítást.

Több ilyen entitás van használatban, így mindegyiket nem szeretném átírni egy teszt kedvéért ha nem muszáj.
Valami mód nincs arra, hogy ugyan úgy menjen ez a kód Hibernatet használva mint EclipseLink alatt?

Tudom, hogy nem ezt akarod hallani, de most még csak karcolod a JPA gondok felszínét. Kettőbe már belefutottál: a weaving vagy müxik vagy nem (nálad épp nem), illetve a JPA provider vendor lock-in.

A legnagyobb gond, hogy a JPA koncepciója hibás, mivel azzal hitegeti az egyszeri fejlesztőt, hogy egy relációs adatbázis mappelhető objektumgráfként, amit aztán úgy lehet kezelni, mintha az tényleg egy in-memory objektumgráf lenne. Newsflash: nem lehet, csak a JPA fejlesztői ezt nem ismerik el, pedig már maga a JPA is az egyik bizonyíték erre. :)

Ha nincs valami külső körülmény, ami miatt kötelező JPA-t használni, akkor használj inkább bármi mást, pl. a JDBI egy nagyon kellemes könyvtár: http://jdbi.org

Ha a modelled immutable objektumokként építed fel, a módosítást egy külön Builder / Editoron keresztül csinálod, akkor triviálisan előáll az a changeset, amiből lehet generálni az update queryket (meg audit logot is pl.), és szerintem kényelmesebb lesz használni mint egy JPAs modellt.

Azt még megemésztem, hogy a JPA vendor lock-in.
De, hogy a weaving vagy működik, vagy nem, azt már nehezen fogadom el.
Elég nagy felhasználói bázisa van szerintem a JPA-nak, köztük biztos sok nagy kritikus rendszer (pl. banki rendszerek), ahol biztos szükség lehet az Entity Graph-ra.
Elég régóta része a specifikációnak nem igaz, hogy nem tudtak egy jól működő implementációt összehozni az EclipseLink fejlesztői.

Lehet csak én nem használok valamit jól.

A weaving-et én úgy értelmeztem az EclipseLinknél, hogy alapból tudnia kellene a GlassFish-nek. Vagy nem jól értelmezem? Valamit mégis be kellene állítanom?
Próbáltam a GF admin konzolban a -javaagent:eclipselink.jar JVM opcióval beállítani a dynamic weaving-et, de úgy el sem indul a GF és a logban sem ír semmit.

Vagy ha ez nem működik mégsem (bár továbbra sem akarom elhinni :) ) akkor milyen technika van arra (az EtityGraph-on kívül), hogy ha nagy számú mezőt és relációt tartalmazó entitást úgy mappeljek fel, hogy ha nem csak csak a mezők egy részét töltse be.


class Entity{
   mezo1;
   mezo2;
   ...
   mezo50;
   List<> ontToMany1;
   List<> ontToMany2;
   ...
   List<> ontToMany18;
}

Mert az kicsit luxus lenne hogy ha kell egy olyan entitás mely csak a

mezo1, mezo4, oneToMay2, oneToMany7

-et kell tartalmaznia azt egy külön entitás osztályba megint elkészítsem. Elég nehezen karban tartható kódot eredményezne.

Valami gond mégis van a weavingel, mert nem megy az entity graph. A következő hibaüzenetet kapom

You must define a fetch group manager at descriptor (...) in order to set a fetch group on the query (...)

és mind az EclipseLink fórumon, mind a stackoveflown azt írják, hogy a weavinget kell hozzá használni.
Meg lehet valahogy nézni hogy tényleg megy e a weaving az alkalmazás szerverben és ezért kapom ezt a hibaüzenetet, vagy megy a weaving és teljesen más miatt nem megy az entity graph.

"köztük biztos sok nagy kritikus rendszer (pl. banki rendszerek), ahol biztos szükség lehet az Entity Graph-ra."

Pontosan lerögzített verziószámú JPA-implementációkra reszelik a kódot. Legtöbb helyen még főverzióváltás sincs, nemhogy másik providerre átállás, csak úgy csettintésre. Kibaszottul érteni kell a JPA implementációkhoz, hogy független kódot tudj írni.

Abban a pillanatban, hogy a hibernate/eclipselink/etc saját doksijából olvasol ki caveats-jellegű beállításokat (ahogy te is tetted most), a vendor lock-in meg is történt.
azért nem megy hibernate alatt ,emrt eclipselink-only -vá vált a kódod.

Egyetértek az előttem szólókkal:
Amíg nem kurvára muszáj JPA-znod, írj saját query-ket.

Csak ugye az fejlesztő azért használ keretrendszert, hogy ne neki keljen mindent megírnia, mert egy kész keretrendszerrel sokkal gyorsabban halad a fejlesztés. És persze ha sokan használnak egy keretrendszert, akkor annak hamarabb kijönnek a hibái és remélhetőleg hamar javítják.
Ennyi erővel azt az utat is lehetne választani hogy írjuk meg a komplett alkalmazást assemblyben. :)
Ahogy látom elég régóta része a JPA specifikációnak az etitygraph. A fentebb említett magyar nyelvű cikk 2015 nyarán készült, tehát bő 2 éve. Benne azt írják, hogy a JPA 2.1 specifikáció része. Megnézve a JSR-t 2013 májusábaj jött ki a final release belőle. 5 éve. Nem igaz, hogy 5 év alatt nem lehet összehozni egy - nem azt mondom hibáktól mentes implementációt, de hogy az első minta alkalmazás így elhasaljon ...
Egy ilyen széles körben támogatott valaminek mint a Java EE, azért jobban kellene muzsikálnia.

Persze, egy ideig tök jó, gyorsan halad a fejlesztés.

Majd amikor elkezded használni is az adatbázisod, és nem csak dashboardokat baszkodsz ki meg formokkal frissítesz,
a) vagy kibebaszottul lassú lesz minden, mert a JPA réteg nem képes rendesen leképezni, mégis mi a tarkafaszt akarsz (ilyenkor jön pl. a DTO-zás, meg az ugyanarra több különböző Entity készítése view-k segítségével, és a többi),
b) vagy (ez a kedvencem) simán csak nem lehet megcsinálni, mert olyan a feladat. Jobb esetben átlendülsz ezen, 16-féle hibernate (vagy 9 eclipselink) specifikus annotációval meg kézzel írt querykkel -- éljen a NamedQuery.

Viszont ha már pl. NamedQuery-t használsz, fél lábbal kívül kerültél a JPA-ból. Oda, ahonnan menekültél, mert "framework nélkül szar/nemlehet/lassú SQL-ezni". (nem a te szavaid, a hype generálók mondják ezt)

A Java EE nem széles körben támogatott. Mindenki Springezik, ami egy EE-szerű framework, hogy weblogic/glassfish/etc nélkül is JEE-zhess. Mert pfuj nagy appszerverek, inkább írunk hatalmas frameworköt a vékony kis servlet containernek.

Hm...

Van a JPA 2.1 specifikáció, melynek része az entity graph. Az EclipseLink, a fejlesztők állítása szerint megfelel ennek a specifikációnak. A Hibernate is, mégis ha lecseréled a providert az alkalmazás alatt hibák lehetnek.
Jogos elvárás szerintem, hogy ez működjön is ha már ezt állítják róla (lehet működik is csak én csinálok valamit rosszul, ez sem kizárt).
Gondolom mindenki úgy van vele, ha valamiről valamit állítanak, akkor az úgy is működjön, bármiről is legyen szó. Senki nem örülne neki ha befeküdne egy tervezett gerincsérv műtétre (specifikáció) és levágnák a lábát (ja bocs a sebészünk csak ezt tudta).

Az már más tészta, hogy teljesítményben mint nyújt vagy nem képes bizonyos dolgokra.
De ha azt állítja magáról, hogy funkcionalitásban tud valamit akkor azt ténylegesen és lehetőleg hiba nélkül tudja.

Nem a Hibernate feladata, hogy az entitás-t értelmezze?

Ha egy entitáson belül próbálom mappelni ugyan azt az adatbázis mezőt,


 @Id
    @Column(name = "ID_AR_TIPUS", insertable = false, updatable = false)
    public int getIdArTipus() {
        return idArTipus;
    }

 @OneToOne
    @JoinColumn(name = "ID_AR_TIPUS", referencedColumnName = "ID")
    public ArTipus getArTipus() {
        return arTipus;
    }

és Hibernate azt szeretné, hogy a

@OneToOne

reációt lássam el

insertable = false, updatable = false

megszorítással, akkor gyanítom WildFly-nál is ugyan ez lenne a helyzet.
Bár itt ha jól sejtem arra gondolsz, hogy ha egy WildFly alatt működő rendszer alá tennék Glassfisht, akkor ott még több hibát kapnék.

Lehet, nem vitatom, még sosem próbáltam, csak ugye ha megfelelnek a JEE X követelményinek akkor működjön már úgy, ahogy a specifikációban le van írva.

De ha a JEE-nél ilyen érdekességek vannak akkor pl. egy Spring mennyivel fájdalommentesebb?

> Ha egy entitáson belül próbálom mappelni ugyan azt az adatbázis mezőt,
Na, itt most jó volna tudni, hogy mi is a specifikáció. Messze nem ismerem a JPA-t, de gyanús, hogy kapásból olyat csináltál, ami Eclipse specifikus -> vendor lock-in

> csak ugye ha megfelelnek a JEE X követelményinek akkor működjön már úgy, ahogy a specifikációban le van írva.
Ebben biztos lehetsz, hogy úgy fog - mind a Java EE, mind a Java SE erősen körbe van TCK-zva. Ami annak nem felel meg, nehezen hívhatod Java EE konténernek.
Sokkal nagyobb valószínűsége annak, hogy te nem felelsz meg neki. Ezt ne vedd sértésnek, én se tudok.

> De ha a JEE-nél ilyen érdekességek vannak akkor pl. egy Spring mennyivel fájdalommentesebb?
Kicsit almát-körtével dolog, mert a Java EE egy szabvány-halmaz (volt), míg a Spring egy konkrét specifikáció.

Én az utóbbi időben nem használtam JPA-t (mobil fókusz), szerver oldalon is kikerültük, ahol lehetett.

Korábban viszont elég sokat használtuk, Hibernate és EclipseLink (sőt még az Oracle brandelt elődje is) is megvoltak.

Ez az EntityGraph a hibás koncepció még mélyebbre ásása.

Nézd meg pl, hogy hogyan definiálsz egy asszociácót a te kódodban is:

List<> izébigyó.

Namost ezzel több baj is van:
* Egyrészt egy ilyen asszociáció (akár onetomany, akár manytomany) nem List<> szemantikával működik. A List<> ugyanis rendezett, egy relációk közötti kapcsolat meg nem. Áshatjuk mélyebbre a gödröt azzal, hogy definiálunk @OrderBy-t rá, de az csak még távolabbra kerülünk egy valódi listától.
* Ok, akkor legyen Collection<>, ami ergyrészt előrelépés, hiszen stimmel(get) a szemantika, add, remove, iterator és jónapot.
* Viszont a Collection<> interfész is egy in-memory adatstruktúrához van tervezve, nem tudja azt lemodellezni, hogy
(a) a Collectionben több elem van, mint amit valaha be tudsz tölteni a memóriába
(b) nincs minden elem betöltve a Collection-ből a memóriába, tehát különböző elemek elérése nagyságrendekkel különbözik
* Sebaj van ásónk, mondták a JPA tervezői, és lett LAZY annotációnk, meg weaving, proxy generálás futásidőben, és még ki tudja mi

De tovább is van, mondjam még? :)

Az összes fenti dolog eltörpül amögött, hogy a JPA megpróbálja (!) fenntartani a referencia integritást. Vagyis, ha van egy entityd, amit egyszer már betöltöttél, és egy tök más query miatt újra betöltődik, akkor megpróbálja az eredetileg betöltött objektumot felhasználni. Hogy ez miért baj? Mert ennek semmi értelme: csak egy adott tranzakción
belül tudja értelmesen biztosítani (utána már mindenféle DETACHED állapotokba kerülnek az objektumok), ráadásul valószínűleg több energia karbantartani az objektum-újrafelhasználást lehetővé tevő infrastruktúrát, mint simán létrehozni az objektumokat újra.

A megoldást valahol abban látom, hogy ketté kell választani az entitások közötti kapcsolatokat valahol az UML modellezésből ismert kompozíció (tele rombusz) / asszociáció (üres rombusz) mentén:
- a kompozíció jellegű kapcsolatnál egyben töltődnek be az elemek, így lehet a megszokott kollekciókat, közvetlen kapcsolatokat használni
- a távolabbi kapcsolatnál kell egy új interfészt bevezetni (Association), ami:
1. Támogat add, remove, query( typesafe fluent interfésszel, query language nélkül)
2. A műveletek aszinkron / reactive interfészt kapnak, nincs blokkoló API
3. Működik akár kliens oldalon is pl. TeaVM-mel, simán az Association példányosításakor olyan ojjektum jön létre, ahol ha valamit be kell tölteni, akkor megy az XMLHttpRequest kérés a szerver felé.