Amikor valamilyen
ORM keretrendszert használunk az alkalmazásunkban, akkor örökösen felmerülő probléma, hogy mely kapcsolatokat töltsük be azonnal, amikor a domain objektumot felszedjük az adatbázisból, és melyeket csak akkor, amikor szükség van rájuk. Grails keretrendszerben a
GORM dübörög (vagy nem), ami pedig a népszerű, vagy mondhatnám sokak által használt, Hibernatere épül. Vegyünk egy egyszerű példát:
1 2 3 4 5 6 7 8 9 | class Bar {
static hasMany = [foos: Foo]
static mapping = {
foos cascade: 'all-delete-orphan'
}
}
class Foo {
static belongsTo = [bar: Bar]
}
|
1 2 3 4 5 6 | Bar bar = Bar.createCriteria().get {
isNotNull 'id'
maxResults 1
}
bar.discard()
println bar.foos
|
A művelet eredménye egy
org.hibernate.LazyInitializationException, hiszen nem töltöttük be a
fookat a domain osztállyal együtt, a discard metódus hatására pedig elveszett a kapcsolat a
Hibernate Session és a domain osztályban lévő proxy között. Javísuk a problémát.
1 2 3 4 5 | Bar bar = Bar.createCriteria().get {
isNotNull 'id'
fetchMode 'foos' , org.hibernate.FetchMode.JOIN
maxResults 1
}
|
Ez a bejegyzés nem jött volna létre, ha csak ennyivel megúsznánk a dolgot. Bár a Hibernate
dokumentációja szerint a FetchMode.JOIN "Fetch using an outer join", mégis ismét egy LazyInitializationException hibára hivatkozva száll el a kérés mint a győzelmi zászló. A problémát a cascade okozza!? Ugyanis ha eltérünk az alap értelmezett viselkedéstől (
OTM kapcsolatoknál az alap értelmezett a save-update, tehát a domain osztályt törölve az adatbázisban maradnak a hozzá kapcsolodó entitások), akkor a JOIN hatására nem a domain osztályok kerülnek a domainbe, hanem csak proxyk, tehát akkor szedi fel őket ténylegesen a rendszer, amikor hivatkozunk rájuk. A cascade
dokumentációja semmit nem említ erről az igen kellemetlen melléhatásról. Tudom Grailsbe "nem szokás" discard()olni, én mégis azt javaslom, hogy mielőtt átadjuk a vezérlést a nézetnek, csak a próba kedvéért válasszuk le a domaint a sessionről, és győződjünk meg róla, hogy nem fog n+1 lekérdezést indítani a háttérben, miközben mi meg vagyunk róla győződve, hogy minden rendben.
Ahogy említettem FetchMode.JOINnal és nélküle is proxyk jelennek meg a domainbe, egy apró különbség azonban van a két eljárás között. FetchMode.JOIN nélkül az alábbi kódsor hibát eredményez (LazyInitializationException).
1 2 3 4 5 6 | Bar bar = Bar.createCriteria().get {
isNotNull 'id'
maxResults 1
}
bar.discard()
println bar.foos.size()
|
FetchMode.JOIN megadásával pedig nem, amiből arra lehet következtetni, hogy van eltérés a két viselkedés között. Előbbi esetben az egész kapcsolatot egy proxy helyettesíti, utóbbinál pedig a domaineket helyettesíti egy-egy proxy.
Lefuttattam a tesztet a Grails legfrissebb verziójával (2.4.4), és minden jel arra utal, hogy már megoldották ezt a problémát. Nem tudom ténylegesen melyik verzióval lett javítva, ezért javaslom mindenkinek, hogy ellenőrizze megfelelően működik-e az alkalmazása, vagy frissítsen a legfrissebb verzióra.