1. August 2014 | 2 min lesezeit

Eager-Synchronized EntityManager mit @Transactional

Getreu dem Motto „Nicht alles muss ein EJB sein“ ermöglicht Java EE 7 den Schritt weg von EJBs hin zu reinen CDI-basierten Anwendungen. Eine der großen Neuerungen ist dabei die @Transactional-Annotation, die deklaratives Transaktionsmanagement gänzlich ohne EJBs für alle Java EE Komponenten ermöglicht. Mit einer passenden Konfiguration können so jetzt auch ohne Stateful Session-Beans Extended-EntityManager Instanzen erzeugt werden, die einen lesenden Zugriff außerhalb einer Transaktion und punktuell den Schreibzugriff in einer Transaktion ermöglichen.

Im intensiven Einsatz zeigt sich jedoch schnell ein Manko: Wird der EntityManager bereits vor einer Transaktion initialisiert, tritt er nicht mehr automatisch der Transaktion bei. In diesem Blogbeitrag zeigen wir daher neben einer passenden Konfiguration auch eine einfache Lösung für dieses Problem auf.

Teil 1: Eine JTA-Datenbankkonfiguration für einen Applikationsserver wie Wildfly oder Glassfish

Ist die Datenbank im Applikationsserver konfiguriert, muss die persistence.xml für die Datenquelle eingestellt werden. Hier ein Beispiel für die Wildfly-Standardverbindung zu einer H2-Datenbank:

Der nächste Schritt ist die Bereitstellung des EntityManager innerhalb der Anwendung. Wir verwenden dabei eine Version der Application Managed Persistence und stellen jedem Request einen neuen EntityManager beiseite. Dieses Setup entspricht im Wesentlichen dem bekannten OpenSessionInViewFilter-Ansatz des Spring Frameworks..

Mit diesen beiden Einstellungen können wir über ein „@Inject EntityManager“ auf ihn zugreifen. Per @Transactional kann auch schreibend auf die Datenbank zugegriffen werden. Doch plötzlich…

Teil 2: Hilfe, mein @Transactional funktioniert nicht mehr!

Wie bereits eingangs angekündigt kann es dazu kommen, dass plötzlich die @Transactional-Annotation nicht mehr funktioniert. Ein manuelles flush() auf dem EntityManager innerhalb der Transaktion sorgt auf dem Wildfly für ein „no transaction is in progress“ und der Glassfish ist der Meinung „Transaction marked as rollbackOnly“. Was ist passiert?

Wenn eine Transaktion gestartet ist und der EntityManager initialisiert wird, fügt er sich automatisch der laufenden Transaktion zu. Passiert beides umgekehrt wird zwar eine Transaktion gestartet, jedoch bekommt der EntityManager davon nichts mit. Dieses Verhalten ist – wenn man aufmerksam die JPA Spec liest – in der JPA Spezifikation und seit 2.1 und der Unterscheidung in SYNCHRONIZED und UNSYNCHRONIZED EntityManager-Instanzen eindeutig festgelegt.

Man kann es leicht nachstellen, indem auf einer JSF-Seite über ein „rendered“-Attribut eines @RequestScoped Beans der EntityManager initialisiert wird. Die eigentliche Action mit @Transactional erfolgt erst in einer späteren Phase. Die Transaktion scheitert mit dem oben beschriebenen Fehlerbild, da sie erst nach Auswertung von „rendered“ gestartet wird.

Teil 3: Die Lösung!

Ein EntityManager kann auch nachträglich per joinTransaction() einer Transaktion hinzugefügt werden. Mit einem einfachen CDI 1.1 Interceptor wird man die Probleme nie mehr erleben:

Schon fügt sich der EntityManager bei jedem @Transactional automatisch der Transaktion hinzu. Sollte mehr als ein EntityManager existieren, so muss das joinTransaction() für jede EntityManager-Instanz durchgeführt werden. Ein iterieren über „@Inject @Any Instance<EntityManager>“ erleichtert die Arbeit in diesem Fall.


Keine Kommentare

Kontakt

OPEN KNOWLEDGE GmbH

Standort Oldenburg:
Poststraße 1, 26122 Oldenburg

Standort Essen:
II. Hagen 7, 45127 Essen