2008. augusztus 7., csütörtök

Lazy fetching a'tka

Aki hasznalt/hasznal ORM framework-ot, biztos vagyok benne, hogy talalkozott mar a LAZY/EAGER fetching modokkal. Mind a ket loading modnak megvan az elonye/hatranya. Jelen esetben LAZY mod egyik igen bosszanto es gyakran elofordulo hatranyaval fogok foglalkozni, delikvens ORM pedig a Hibernate3.

Tegyuk fel, hogy three tier-es kornyezetben vagyunk es presentation tier-hez szeretnenk eljuttatni egy entity-t a business tier-bol. Az entity-nk neve legyen A.

class A {
private int id;
private List<B> b;
}

class B {
int id;
private List<C> c;
}

class C {
int id;
}

Fenti kodreszlet lenyegeben az A entitynk strukturajat mutatja. Fontos, hogy B es C is entity, tehat adatbazis szinten harom tablank van. A-ban talalhato B illetve a B-ben levo C lista LAZY modos fetching-t hasznal, JOIN kapcsolat van koztuk. Tehat A.getB() illetve B.getC() egy proxy-zott listat fog visszadni, csak akkor lesz populalva, ha valaki hasznalni akarja (pl. amikor vegig akarunk iteralni rajta).

Perfomance szempontjabol teljesen korrekt megoldas, minek betolteni azt, amit nem is biztos, hogy hasznalunk. Azonban van egy exception amivel szerintem minden Hibernate "barat" talalkozott mar: org.hibernate.LazyInitializationException. Ezt az exception akkor kapjuk, amikor egy proxyzott listan akarunk vegigiteralni, viszont a Hibernate session-unk mar nem letezik, igy a lista populalasa meghiusult.

Tegyuk fel, hogy presentation tier es a business tier kozott WebService-en keresztul mozognak az egyes entity-k. Marshalling soran (Object -> XML) valoszinuleg kapni fogunk egy exception a lazy loading miatt. A LazyInitializationException-t ugy tudjuk elkerulni, hogy az entitynket detach-eljuk, tehat a lazy loados listakat betoltjuk meg mielott a Hibernate session megszunne.

Kinai farmeres modszerrel megtehetjuk azt, hogy vegigiteralunk az oszes lista elemen, viszont nem a legegeszsegesebb. Szerencsere Hibernate biztosit szamunkra egy built-in utility methodot, org.hibernate.Hibernate#initialize(Object proxy). A proxy-zott listakat atjuk a initialize() methodusnak igy initializalva lesz a listank.

A fentiek alapjan, azt gondolnank, hogy a kovetkezo dologgal meg is oldottuk a marshalling problemat:

A a = ...;
Hibernate.initialize(a.getB());

Ha megfigyeljuk akkor B entity szinten tartalmaz egy proxyzott listat (C). Ezen lista nem kerul initializalasra, igy B entity C listajan is meg kellene hivnunk a initialize() methodust. Egy nagyon complex model eseten tobb szaz soros kodreszlet lesz, ami a detach-et vegzi, ami nem a legszebb latvany, illetve konnyu hibat veteni.

Tegyuk fel, hogy a detach-et vegzo kodot megirtuk, viszont RMI-re valtunk WebService helyett. Az RMI legjobb tulajdonsaga az, hogy egy az egyben kiszerializalja az objecteket, nem kell DDL, WebService esetben viszont igen. Ezen jo tulajdonsaga lesz esetunkben a hatranya, ugyanis List objectek konkret tipusa Hibernate fuggo, sajat tipusok. Ha a presentation tier dependency listaja nem tartalmazza a Hibernate libeket, akkor NoClassFoundException fogunk kapni.

Most erkeztunk el a lenyeghez amiert ezt a post-ot megirtam. Letezik egy megoldas, ami a teljes detach-es kodiras terhet leveszi a vallunkrol, illetve a RMI-s problemat is eliminalja. A utility class neve, ami nem resze a Hibernate CORE-nak (egyenlore), HibernateCleaner. Ahoz, hogy detach-eljuk illetve a megszabaduljunk a hibernate dependent class tipusoktol, meg kell hivnunk a clean() metodust.

3 megjegyzés:

Akos Kiss írta...

Na, így legalább megmarad ez az info. Részletes, jó leírás.

kack írta...

Thx :)

Károly írta...

Köszi, hasznos info.