Szóval az van (szóval nem kezdünk mondatot! hehe :P), hogy próbára tettem a tárgyban jelölt két JPA implementációt, mit alkotnak, ha tárolt eljárásokkal és/vagy függvényekkel kerülnek szembe. Próbáltam védeni a becsületüket, de nem sikerült teljes mértékben.
(jelenlegi tudásom alapján ebben a pillanatban egyik implementáció sem képes maradéktalanul / mindkét dolgot lekezelni)**
**Ez a bejegyzés írása közben megváltozott, EclipseLink 2.2.1-es verziójával megoldható, a 2.3.1-essel viszont nem (jelen pillanatban)
Függvényhívások
Első ízben az EclipseLink-et izzasztottam meg, vagy inkább Ő engem.
Függvényhívással kezdtem volna a mókát. Találtam is egy annotációt, ami ígéretesnek tűnt, de úgy döntöttem a nativeQuery-t teszem előbb próbára.
Alapvetően 3 entitás vett részt a tesztelésben, 2 függvény és 1 tárolt eljárás.
A fenti kódban a createNativeQuery második paramétere egy ResultSetMapping -re utal, amit az entitásra húztam rá:
...és valójában teljesen felesleges, gyakorlatilag ugyanazt mondja, mintha a második paraméter a UserStatusType.class lenne. Az SqlResultSetMapping összetett eredménytípus esetén hasznos, mikor több entitás formájában kapjuk meg az eredményt.
Tehát a select lefut hiba nélkül. Boldogan elkezdesz rajta iterálni, majd mégis szomorú ténnyel kell szembesülnöd. A listában szereplő első entitás még teljesen okés, a második viszont már nincs feltöltve, null értékekkel van inicializálva minden property kivéve az elsődleges kulcsot (amit @Id-vel annotáltunk). Ez pusztán azért érthetetlen, mert a fenti utasítás gyakorlatilag annyit tesz, mintha egy táblából lekérdeznénk minden rekordot.
Abban az esetben, ha nem adod meg a visszatérési típust második paraméterként, az eredményként érkező listában Object tömbök lesznek, minden tömb-elem az adattábla egy-egy mezőjének az értéke.
Ebben az esetben persze el lehet érni az értékeket ha a fentit úgy módosítjuk pl.: ${s[0]}
Érdekes, hogy így minden listaelem a megfelelő értékeket tartalmazza. Ugyanez miért nem sikerül ORM módjára?
Ugyanez a történet Hibernate esetén úgy működik, ahogy az a nagydoksiban meg van írva.
Mint fentebb írtam, találtam egy ígéretesnek tűnő annotációt. Most jöhetne az a rész, hogy "igen, ez az, megvan a megoldás", de nem. Nincs. Ugyanis az említett annotáció nem képes table valued stored function lekezelésére.
Mivel ez gyakorlatilag egy névvel ellátot lekérdezés, a fenti nativeQuery-n egy apróbb módosítás hajtunk végre:
Majd felannotáljuk a UserStatusType entitásunkat:
Két dolgot vehetünk észre:
1.) a createNamedQuery továbbra is megkapja második paraméterként a visszatérési típust, annak ellenére, hogy az annotációban is egyértelműen meghatároztuk azt. Ennek a korábban említett Object tömb probléma az oka.
2.) Ebben az esetben a resultSetMapping már fontos, ugyanis kötelező adat.
Eredményként a már előrevetített hibát kapjuk:
Kis google nyaggatás után arra kellett rájönnöm, hogy függvények hívására a NamedPLSQLStoredFunctionQuery annotációt szokták használni (Oracle-höz mindenképp). Elkezdtem foglalkozni a témával, minek során a következők derültek ki.:
Továbbra is szükségünk van a resultSetMapping-re. Ezen felül definiálnunk kell egy adatstruktúrát, amit adatbázis szinten használ majd a rendszer, gyakorlatilag ennek segítségével állítja össze az eredményben szereplő rekordokat, és ezeknek a listájával tér vissza. A probléma csak az, hogy ezt az objektumot létre kéne hozni adatbázis szinten is, amit a google-ban történő keresés eredményeképpen kidobott első 3 oldal megtekintése után nem állt szándékomban tovább erőltetni MsSQL esetén. Az sem derült ki számomra, hogy lehet-e benne. Oracle-ben no para, nade ez a mikrópuha ez nem az én világom :)
Továbbá ha az ember vet egy pillantást a fenti definicióra, nem nehéz belátni, hogy ez egy totálisan függvényekkel operáló alkalmazás esetén tarthatatlan. Nem akarunk mi 10-20 sort gépelni csak azért, hogy egy nyamvadt UDF-et meghívjunk, és tán még eredményt is kapjunk belőle.
Mikor idáig jutottam kezdtem el gondolkodni rajta, hogy mi a fene ez, hogy nativeQuery esetén nem tölti fel az entitásunkat EclipseLink. Kipróbáltam, természetesen szimpla select * from tbl lekérdezés esetén is ezzel az anomáliával kerültem szembe.
Fogtam magam, és a korábban feleslegesnek titulált resultSetMapping-et egy kicsit megokosítottam:
Ennek hatására a nativeQuery helyes eredményt adott vissza, ami elgondolkodtató. Nem kéne ezt megoldania a JPA-nak, ahogy az Hibernate esetén meg is történik? EclipseLink bug vagy hiányosság? Érdekes, hogy nem találtam erre a problémára utaló bejegyzést sehol a neten, illetőleg elég régóta használok EclipseLinket, és még nem volt ilyen problémám.
Ehhez a projekthez a 2.3.2-es verziójú EclipseLink-et használtam. Letöltöttem a 2.2.1-et, lecseréltem a libeket, és láss csodát... Amivel szivattam magam 2 napig, az tökéletesen működik. Nem kell resultSetMapping sem, sima nativeQuery megoldja amit meg kell.
Micsoda fintor, hogy ehhez a bejegyzéshez egy Eclipse bugreport is fűződik :)
Az eddig leírt litánia tehát a függvényhívásokról szól, amit gyakorlatilag annyival is elintézhettem volna, hogy Hibernate esetén jól működik, EclipseLink esetén pedig használjunk 2.2.1-es verziót, mert a 2.3.2-ben bugos a nativeQuery.
Tárolt eljárások
A tárolt eljárásoknak is entitásokkal ugrottam neki EclipseLink esetén, és szerencsére ezzel nem is volt semmilyen szívás. Az alábbit az entitásunkra kell ráhúznunk:
Ebben maximum annyi kivetnivaló lehet, hogy minden eljáráshoz meg kell írnunk a fenti kódot, majd mikor meghívjuk azt, tudnunk kell, hogy hogy neveztük el a paramétereket és melyik-melyik. Természetesen egységes névkonvenciók alkalmazása mellett egyszerűsíthetjük az életet. Ezen felül ha a lekérdezés megírásánál figyeled a felannotált entitást is, akkor könnyebb a helyzet. Ennél jobban amúgy sem tudom, hogy lehet-e könnyíteni ezen a dolgon bármilyen technológiát is használjunk.
Ez után az eljárás hívása már egyszerű:
Hibernate API doksijában sajnos meg vagyon írva, hogy nativeQuery-vel ugyan futtathatsz tárolt eljárásokat, de OUT paramétereket nem tud kezelni a rendszer. Kipróbáltam, úgy tűnik tényleg így van.
Összességében tehát ha tárolt eljárásokat és függvényeket egyaránt szeretnénk kezelni teljes szolgáltatási skálájukat kihasználva, jobb választás az EclipseLink.
Amennyiben csak függvényekkel kell operálnunk, megteszi a Hibernate is.
(jelenlegi tudásom alapján ebben a pillanatban egyik implementáció sem képes maradéktalanul / mindkét dolgot lekezelni)**
**Ez a bejegyzés írása közben megváltozott, EclipseLink 2.2.1-es verziójával megoldható, a 2.3.1-essel viszont nem (jelen pillanatban)
Függvényhívások
Első ízben az EclipseLink-et izzasztottam meg, vagy inkább Ő engem.
Függvényhívással kezdtem volna a mókát. Találtam is egy annotációt, ami ígéretesnek tűnt, de úgy döntöttem a nativeQuery-t teszem előbb próbára.
Alapvetően 3 entitás vett részt a tesztelésben, 2 függvény és 1 tárolt eljárás.
ZipCode(ZipCode, PlaceName, Shire)
OfferType(ID, OfferTypName...)
UserStatusType(ID, StatusName)
AddUpdateOfferType // SP OUT: @NewId
GetStatusTypes // UDF returns table
GetOfferTypeList // UDF returns table
A GetStatusTypes függvény natív hívása félig-meddig sikeresnek mondható, de mégsem:A fenti kódban a createNativeQuery második paramétere egy ResultSetMapping -re utal, amit az entitásra húztam rá:
@SqlResultSetMapping(name="statusResult", entities=@EntityResult(entityClass=UserStatusType.class))
...és valójában teljesen felesleges, gyakorlatilag ugyanazt mondja, mintha a második paraméter a UserStatusType.class lenne. Az SqlResultSetMapping összetett eredménytípus esetén hasznos, mikor több entitás formájában kapjuk meg az eredményt.
Tehát a select lefut hiba nélkül. Boldogan elkezdesz rajta iterálni, majd mégis szomorú ténnyel kell szembesülnöd. A listában szereplő első entitás még teljesen okés, a második viszont már nincs feltöltve, null értékekkel van inicializálva minden property kivéve az elsődleges kulcsot (amit @Id-vel annotáltunk). Ez pusztán azért érthetetlen, mert a fenti utasítás gyakorlatilag annyit tesz, mintha egy táblából lekérdeznénk minden rekordot.
Abban az esetben, ha nem adod meg a visszatérési típust második paraméterként, az eredményként érkező listában Object tömbök lesznek, minden tömb-elem az adattábla egy-egy mezőjének az értéke.
org.apache.jasper.JasperException: An exception occurred processing JSP page /WEB-INF/jsp/index.jsp at line 6
3: status name by id: ${ust.statusName}<br /><br />
4: status names by UDF:
5: <c:forEach items="${ustl}" var="s">
6: ${s.id}:${s.statusName},
7: </c:forEach>
8: <br /><br />
9: offer type id: ${ot.id}<br />
[...]
java.lang.NumberFormatException: For input string: "id"
java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
Ebben az esetben persze el lehet érni az értékeket ha a fentit úgy módosítjuk pl.: ${s[0]}
Érdekes, hogy így minden listaelem a megfelelő értékeket tartalmazza. Ugyanez miért nem sikerül ORM módjára?
Ugyanez a történet Hibernate esetén úgy működik, ahogy az a nagydoksiban meg van írva.
Mint fentebb írtam, találtam egy ígéretesnek tűnő annotációt. Most jöhetne az a rész, hogy "igen, ez az, megvan a megoldás", de nem. Nincs. Ugyanis az említett annotáció nem képes table valued stored function lekezelésére.
Mivel ez gyakorlatilag egy névvel ellátot lekérdezés, a fenti nativeQuery-n egy apróbb módosítás hajtunk végre:
@SuppressWarnings("unchecked") public List<UserStatusType> getEntities() { return em.createNamedQuery("getStatusTypes", UserStatusType.class).getResultList(); }
Majd felannotáljuk a UserStatusType entitásunkat:
@NamedStoredFunctionQuery( name="getStatusTypes", functionName="GetStatusTypes", resultSetMapping="statusResult", returnParameter=@StoredProcedureParameter(direction=Direction.OUT, queryParameter="p1") )
Két dolgot vehetünk észre:
1.) a createNamedQuery továbbra is megkapja második paraméterként a visszatérési típust, annak ellenére, hogy az annotációban is egyértelműen meghatároztuk azt. Ennek a korábban említett Object tömb probléma az oka.
2.) Ebben az esetben a resultSetMapping már fontos, ugyanis kötelező adat.
Eredményként a már előrevetített hibát kapjuk:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: java.sql.SQLException: The request for procedure 'GetStatusTypes' failed because 'GetStatusTypes' is a table valued function object.
Kis google nyaggatás után arra kellett rájönnöm, hogy függvények hívására a NamedPLSQLStoredFunctionQuery annotációt szokták használni (Oracle-höz mindenképp). Elkezdtem foglalkozni a témával, minek során a következők derültek ki.:
Továbbra is szükségünk van a resultSetMapping-re. Ezen felül definiálnunk kell egy adatstruktúrát, amit adatbázis szinten használ majd a rendszer, gyakorlatilag ennek segítségével állítja össze az eredményben szereplő rekordokat, és ezeknek a listájával tér vissza. A probléma csak az, hogy ezt az objektumot létre kéne hozni adatbázis szinten is, amit a google-ban történő keresés eredményeképpen kidobott első 3 oldal megtekintése után nem állt szándékomban tovább erőltetni MsSQL esetén. Az sem derült ki számomra, hogy lehet-e benne. Oracle-ben no para, nade ez a mikrópuha ez nem az én világom :)
@NamedPLSQLStoredFunctionQuery( name="getStatusTypes", functionName="GetStatusTypes", resultSetMapping="statusResult", returnParameter=@PLSQLParameter(name="res", databaseType="UST_REC", direction=Direction.OUT) ) @PLSQLRecord(name="UST_REC", compatibleType="UST_TYPE", javaType=UserStatusType.class, fields = { @PLSQLParameter(name="ID"), @PLSQLParameter(name="StatusName") } ) @Struct(name="UST_TYPE", fields={"ID", "StatusName"}) @SqlResultSetMapping(name="statusResult", entities=@EntityResult(entityClass=UserStatusType.class))
Továbbá ha az ember vet egy pillantást a fenti definicióra, nem nehéz belátni, hogy ez egy totálisan függvényekkel operáló alkalmazás esetén tarthatatlan. Nem akarunk mi 10-20 sort gépelni csak azért, hogy egy nyamvadt UDF-et meghívjunk, és tán még eredményt is kapjunk belőle.
Mikor idáig jutottam kezdtem el gondolkodni rajta, hogy mi a fene ez, hogy nativeQuery esetén nem tölti fel az entitásunkat EclipseLink. Kipróbáltam, természetesen szimpla select * from tbl lekérdezés esetén is ezzel az anomáliával kerültem szembe.
Fogtam magam, és a korábban feleslegesnek titulált resultSetMapping-et egy kicsit megokosítottam:
@SqlResultSetMapping( name="statusResult", entities=@EntityResult( entityClass=UserStatusType.class, fields={ @FieldResult(name="id", column="ID"), @FieldResult(name="statusName", column="StatusName") } ) )
Ennek hatására a nativeQuery helyes eredményt adott vissza, ami elgondolkodtató. Nem kéne ezt megoldania a JPA-nak, ahogy az Hibernate esetén meg is történik? EclipseLink bug vagy hiányosság? Érdekes, hogy nem találtam erre a problémára utaló bejegyzést sehol a neten, illetőleg elég régóta használok EclipseLinket, és még nem volt ilyen problémám.
Ehhez a projekthez a 2.3.2-es verziójú EclipseLink-et használtam. Letöltöttem a 2.2.1-et, lecseréltem a libeket, és láss csodát... Amivel szivattam magam 2 napig, az tökéletesen működik. Nem kell resultSetMapping sem, sima nativeQuery megoldja amit meg kell.
Micsoda fintor, hogy ehhez a bejegyzéshez egy Eclipse bugreport is fűződik :)
Az eddig leírt litánia tehát a függvényhívásokról szól, amit gyakorlatilag annyival is elintézhettem volna, hogy Hibernate esetén jól működik, EclipseLink esetén pedig használjunk 2.2.1-es verziót, mert a 2.3.2-ben bugos a nativeQuery.
Tárolt eljárások
A tárolt eljárásoknak is entitásokkal ugrottam neki EclipseLink esetén, és szerencsére ezzel nem is volt semmilyen szívás. Az alábbit az entitásunkra kell ráhúznunk:
@NamedStoredProcedureQueries({ @NamedStoredProcedureQuery( name="addUpdateOfferType", procedureName="AddUpdateOfferType", returnsResultSet=false, parameters={ @StoredProcedureParameter(queryParameter="p1", name="ID", direction=Direction.IN, type=Integer.class), @StoredProcedureParameter(queryParameter="p2", name="OfferTypeName", direction=Direction.IN, type=String.class), @StoredProcedureParameter(queryParameter="p3", name="DailyWorkHours", direction=Direction.IN, type=Integer.class), @StoredProcedureParameter(queryParameter="p4", name="ProfessionalPractice", direction=Direction.IN, type=Integer.class), @StoredProcedureParameter(queryParameter="p5", name="IsDeleted", direction=Direction.IN, type=Boolean.class), @StoredProcedureParameter(queryParameter="p6", name="UserId", direction=Direction.IN, type=Integer.class), @StoredProcedureParameter(queryParameter="p0", name="NewId", direction=Direction.OUT, type=Integer.class) } ) })
Ebben maximum annyi kivetnivaló lehet, hogy minden eljáráshoz meg kell írnunk a fenti kódot, majd mikor meghívjuk azt, tudnunk kell, hogy hogy neveztük el a paramétereket és melyik-melyik. Természetesen egységes névkonvenciók alkalmazása mellett egyszerűsíthetjük az életet. Ezen felül ha a lekérdezés megírásánál figyeled a felannotált entitást is, akkor könnyebb a helyzet. Ennél jobban amúgy sem tudom, hogy lehet-e könnyíteni ezen a dolgon bármilyen technológiát is használjunk.
Ez után az eljárás hívása már egyszerű:
@Transactional public OfferType createUpdateOfferType(OfferType offerType) { Integer newId = null; OfferType newOfferType = null; Query q = em.createNamedQuery("addUpdateOfferType"); newId = (Integer)q.setParameter("p1", offerType.getId() == null ? 0 : offerType.getId()) .setParameter("p2", offerType.getOfferTypeName()) .setParameter("p3", offerType.getDailyWorkHours()) .setParameter("p4", offerType.getProfessionalPractice()) .setParameter("p5", offerType.getIsDeleted()) .setParameter("p6", offerType.getCreatedBy()) .getSingleResult(); if (newId != null) { newOfferType = find(OfferType.class, newId); } else { throw new IllegalStateException(); } return newOfferType; }
Hibernate API doksijában sajnos meg vagyon írva, hogy nativeQuery-vel ugyan futtathatsz tárolt eljárásokat, de OUT paramétereket nem tud kezelni a rendszer. Kipróbáltam, úgy tűnik tényleg így van.
Összességében tehát ha tárolt eljárásokat és függvényeket egyaránt szeretnénk kezelni teljes szolgáltatási skálájukat kihasználva, jobb választás az EclipseLink.
Amennyiben csak függvényekkel kell operálnunk, megteszi a Hibernate is.
Nincsenek megjegyzések:
Megjegyzés küldése