Bei unserem Workshop “Java EE 7 – Enterprise-Anwendungen ohne Ballast” auf der JAX 2016 hatte ich das Problem, dass eine Liste von Daten, die als Lazy-Collection in einem Entity enthalten waren, trotz eines Extended EntityManager nicht geladen wurden, d. h. eine Lazyload-Exception auslösten. Im Workshop haben wir das Problem damit gelöst, dass die Daten mittels Entity Graph explizit geladen wurden. Die eigentlich offensichtliche Ursache, warum uns der Extended EntityManager in dem Fall gar nicht helfen konnte, versteckte sich sprichwörtlich in dem Wald, den man schon mal vor lauter Bäumen nicht sieht. Nach dem Workshop war es nach kurzer Zeit ruhigen Nachdenkens sofort klar, waran es lag:
Im Workshop wurde eine kleine Java-EE-Webanwendung zur Verwaltung von Konferenzbeiträgen entwickelt. Sie können Sie sich hier anschauen: https://github.com/dirkweil/javaee-workshop/tree/jax16. Betrachten Sie für das hier diskutierte Problem folgende Klassen und Facelets:
- de.gedoplan.workshop.domain.Talk
Dies ist die erwähnte Entity. Sie enthält die Lazy-Collection speakers - de.gedoplan.workshop.presentation.TalkPresenter
Diese JSF-Präsentationsklasse sorgt für das Lesen der Talks in postConstruct - src/main/webapp/talk/talk.xhtml sowie …/talkEdit.xhtml
Diese Facelets arbeiten auf Basis von TalkPresenter und bilden ein Master/Detail-View-Paar: talk.xhtml zeigt alle Talks an und verzweigt zur Bearbeitung eines einzelnen Talks nach talkEdit.xhtml.
Beim Bearbeiten eines Talks, d. h. beim Übergang von talk.xhtml zu talkEdit.xhtml kam es zu besagten Lazyload-Exception, die bei ruhiger Betrachtung auch ganz erklärlich ist:
- Die Talks werden für talk.xhtml gelesen und in einem TalkPresenter-Objekt aufbewahrt. Nach dem Request sind natürlich die ggf. genutzte Transaktion und damit auch der zum Lesen genutzte EntityManager geschlossen, die gelesenen Talks also detached.
- Beim Übergang zu talkEdit.xhtml wird einer der gelesen Talks direkt benutzt, um die Detaildaten anzuzeigen. Und da die Talks detached sind, funktioniert das Nachladen der Lazy-Daten nicht mehr – Peng!
Im Workshop haben wir dann die Variante genutzt, schon beim initialen Lesen der Talks auch die Speaker zu lesen, was aber nur dann wiklich sinnvoll ist, wenn die Speaker tatsächlich immer gebraucht werden, was für die Master-View talk.xhtml ja nicht zutrifft.
Besser wäre es gewesen, die Daten des im Detail anzuzeigenden Talks beim Übergang von talk.xhtml zu talkEdit.xhtml einfach neu einzulesen. So ist das jetzt in der aktuell auf GitHub befindlichen Version auch gelöst, zu sehen in der Methode TalkPresenter.editTalk.
Sollte Ihnen also eine zunächst unerklärliche Lazyload-Exception entgegen springen, überlegen Sie genau, wo die betroffenen Daten herkommen und ob sie im Moment des Nachladens nicht detached sind.
Eine alternative Lösung können Sie in dem Branch des Projektes betrachten, das im Zuge des Workshops auf der W-JAX 2015 in München entwickelt wurde (https://github.com/dirkweil/javaee-workshop/tree/wjax15): Hier wurde talk.xhtml nicht in den Flow integriert, sondern erst beim Übergang von talk.xhtml zu talkEdit.xhtml ein Flow namens “editTalk” gestartet. Dadurch wurden die Daten vor dem Betreten der Detail-Seite aus der DB aktualisiert und waren beim Nachladen der Lazy-Daten nicht detached.
Man sieht: Scopes, Ladezeitpunkt und Lazyness von Entitydaten müssen aufeinander abgestimmt sein. Es braucht also ein wenig Planung vorweg, was im normalen Projektalltag auch problemlos gelingt. Bei einem teilweise recht agilen Workshop kann’s schonmal schief gehen, was ja auch Erkenntnisse bringt …