Communardo Software GmbH, Kleiststraße 10 a, D-01129 Dresden
0800 1 255 255

Grails: LazyInitializationException - Lösungsmöglichkeiten

Die Kombination von Hibernate und kom­ple­xen Datenstrukturen bringt einige Herausforderungen mit­sich und daher ist dem ein oder ande­ren die Exception aus dem Titel sicher geläu­fig. In eini­gen Fällen hat man an sol­chen Punkten eine fast fer­tige Anwendung und es geht an die Implementierung von Jobs oder neuen Anforderungen. Nun las­sen sich diese meist nicht, oder sehr schwer, mit den vor­han­de­nen Methoden abbil­den und man fängt an, durch die Hintertür einige Abfragen zusam­men­zu­bauen um an die ent­spre­chen­den Daten zu kom­men. Gerade auch bei Jobs wer­den mit­un­ter Daten gela­den, wel­che quer­feld­ein durch die bestehen­den Methoden der Anwendung geschickt wer­den. Leider bleibt hier desöf­te­ren ein wich­ti­ger Teil auf der Strecke – die Verbindung zur Hibernate Session.

Lazy-Loading mit Proxy-Objekten

Grundsätzlich sind alle Ladevorgänge aus der Datenbank "Lazy-Loading". Dies soll ver­hin­dern, dass man die halbe Datenbank in den Speicher lädt falls die Beziehungen zwi­schen den Tabellen eng ver­wo­ben sind. Praktisch bedeu­tet das, Hibernate lädt bei Abfrage von Objekten zuge­hö­rige Referenzen (Collections/ Unterobjekte) als Proxies.

Ein Proxy-Objekt ent­hält dabei nur die Id des Objektes in der Datenbank und beim ers­ten Zugriff wird es auto­ma­tisch nach­ge­la­den. Der Nachteil an der Sache ist aber nun, wenn die Hibernate-Session geschlos­sen wird, befin­det sich das gerade gela­de­nen Objekt im Zustand detached.
Das bedeu­tet, ein Objekt ist nicht mehr in Synchronisation mit der Datenbank und dem­zu­folge kön­nen auch für die Proxy-Objekte keine Daten mehr nach­ge­la­den wer­den – die Anwendung wirft eine LazyInitializationException.

Manuelles Attach

Nun gibt es meh­rere Möglichkeiten etwas dage­gen zu tun, schauen wir uns zunächst mal die Einfachste an. In der Methode in der auf Parameter eines Objektes zuge­grif­fen wird, prüft man ein­fach ob die Verbindung zur Session noch da ist und falls nicht, dann wird das Objekt eben wie­der dran­ge­han­gen (attached).

def someAction(Author author) {
....
if(!author.isAttached()) {
author.attach()
}
}

FetchMode via Domain-Definition

Oft hat man jedoch nicht die Möglichkeit in den Methoden das Objekt wie­der an die Session zu hän­gen. Entweder ist die benutzte API bereits fer­tig oder man muss sehr viele Methoden anpas­sen.

Nun gibt es dafür aber eine eben­falls ein­fa­che Lösung: In Grails kann man den FetchMode, also die Strategie zum Nachladen der refe­ren­zier­ten Objekte, direkt in der jewei­li­gen Domain-Klasse defi­nie­ren. Der fol­gende Code demons­triert das anschau­lich:

class Author {
String firstname
String surname
static hasMany = [ books : Book ]
static fetchMode = [ books : 'eager' ]
}

class Book {
String name
static belongsTo = Author
static hasMany = [categories : Category]
static fetchMode = [categories : 'eager']
}

class Category {
String name
static belongsTo = Book
}

Der Vorteil die­ser Lösung ist, dass man sich wahr­schein­lich an kei­ner Stelle der Anwendung mehr Gedanken um das Nachladen der Bücher des Authors samt zuge­hö­ri­ger Kategorien machen muss. Hat man viele Bücher bzw. Kategorien zieht man sich jeoch, wie bereits oben erwähnt, ziem­lich viele Daten in den Speicher – auch in Situationen in denen man bei­spiels­weise nur alle Authoren auf­lis­ten möchte.

FetchMode via Queries

Wesentlich ele­gan­ter als die obere Strategie, ist es die benö­tig­ten Objekte beim Laden der Daten mit anzu­zei­gen. Über den Hibernate CriteriaBuilder lässt sich der FetchMode bequem für jedes Property ein­zeln defi­nie­ren. "Nested Properties", also ver­schach­telte Objekte wer­den durch den Punkt-Operator in der Query refe­ren­ziert. Die nach­fol­gende Abfrage holt alle Authoren, samt Bücher und Kategorien:

import org.hibernate.FetchMode as FM
....
def criteria = Author.createCriteria()
def result = criteria.list {
fetchMode("books", FM.EAGER)
fetchMode("books.categories", FM.EAGER)
}

In eini­gen Fällen hat man hier das Problem, dass die Datensätze mehr­fach in der Liste erschei­nen. Hibernate führt beim "Eager-Fetch" einen Outer Join durch und des­halb dupli­zie­ren sich die Einträge für gefun­dene Unterobjekte . Eine Lösung dafür ist criteria.listDistinct{} anstatt criteria.list{} zu ver­wen­den.

Ein Nachteil die­ser Lösung soll auch nicht ver­schwie­gen wer­den: Zwar bekommt man die Referenzen in den Speicher gela­den wel­che man möchte, nur muss man vor­her genau wis­sen was man an ande­rer Stelle benö­tigt. Wird eine Methode mit die­ser Query auf­ge­ru­fen und es wer­den gar nicht alle Bücher/Kategorien gebraucht, ver­schenkt man auch hier wie­der Performance.

Anmerkung: Dasselbe kann man natür­lich auch mit HQL bzw. DynamicFinder machen, hier über­gibt man den FetchMode dann als Parameter der Funktion.

Related Posts

Pin It on Pinterest