Spring – JPA und Transaktionen

Montag, 21. November 2011 von  
unter Fachartikel Architektur

Spring JPA und Transaktionen – der dritte Teil

Die Motivation:

Zusätzlich zu den Teilen I + II möchte ich mit diesem dritten Blog-Eintrag zum Thema JPA die Themen ansprechen, welche in den vorigen zwei Blog-Beiträgen nicht oder nicht vollständig thematisiert wurden, d.h. im Folgenden geht es um spezielle Entity Abbildungsverfahren, Vererbungsstrategien und -hierarchien, den Einsatz des Criteria APIs und die Verwendung von JPLQ, sowie JPA und Transaktionen als auch um Locking Prinzipien. Dabei soll auch der Einsatz von JPA mit Spring sowie Transaktionen mittels Spring Unterstützung möglichst nicht zu kurz kommen, wie gewohnt, mit einer Vielzahl von Beispielen.

Spezielle Entity Abbildungsverfahren:

Hierbei werden die Themen Sekundärtabellen, eingebettete Objekte, Vererbung, sowie die Strategien “Single Table“, “Table per Class“ u. “JOINED“ besprochen. Also los geht’s. Entity Attribute können auf Primär- und Sekundärtabellen verteilt werden, in zwei Annotationen: @SecondaryTable(name, catalog, schema, pkJoinColumns, uniqueConstraints) oder @SecondaryTables(@SecondaryTable(…), @SecondaryTable(…)). Hier ein Beispiel dazu.

Tabellendaten können auch auf mehrere Entities verteilt werden, einerseits unter Verwendung der @Embedded() und @Embeddable() Annotationen, andererseits mittels @AttributOverride(name, column) bzw. @AttributOverrides(@AttributOverride(…), @AttributOverride(…)). Hier ein Beispiel. Seit JPA 2.0 dürfen Embeddables beliebig verschachtelt werden und wiederum To-One-Relationen besitzen, wie in diesem Beispiel hier. Dabei ist zu beachten, dass Embeddables immer unidirektional aus einer Entity adressiert werden, weshalb der Key eines Relationspartners des Embeddables immer in der Owner Entity dieses Embeddables gespeichert wird. Im Fall bidirektionaler Unterelemente zeigt der Relationspartner immer auf diese Owner Entity, weshalb es oft sinnvoller ist, das Embeddable als Entity zu mappen, anstatt es als Embeddable einzusetzen.

Zum Thema Vererbungen lässt sich feststellen, dass die Abbildung von Enity Vererbungshierarchien gut umsetzbar ist, unter Verwendung der Annotationen @Inheritance(strategy), @DiscriminatorColumn(name, discriminatorType, columnDefinition, length) und @DiscriminatorValue(value). Ein Vorteil bei der Abbildung von Vererbungen ist die Ermöglichung polymorpher Such-Queries. Hier ein Beispiel. Zu beachten bei der Verwendung von Vererbungsmechanismen ist die Tatsache, dass Vererbungen bestimmte tabellarische Relationen erfordern, wobei alle Instanzen einen eigenen Primary Key erfordern. Dabei sind manchmal besondere Primary Key Generierungen erforderlich, bei MySQL z.B. über eine extra Primary Key Tabelle (strategy=GenerationType.TABLE), oder bei Verwendung von MySQL-Sequences (strategy = GenerationType.SEQUENCE) eine extra Sequence-Tabelle.

Die Vererbungsstrategie “Single-Table“ wird durch den InheritanceType.SINGLE_TABLE (Default Wert) als Wert des strategy Parameters der @Inheritance Annotation abgebildet, wobei alle Instanzen in einer gemeinsamen Table gespeichert werden. Zur Spaltenanzahl kann bemerkt werden, dass alle Eigenschaften aller Klassen der Hierarchie in derselben Table enthalten sein müssen, wodurch diese Table in jeder Hinsicht (viele Spalten, viele Daten) schnell sehr groß werden kann. Eine als Basisklassen-Entity (absolute Oberklasse) verwendete Entity erhält dann die Annotationen @Inheritance(strategy=InheritanceType.SINGLE_TABLE) dann die @DiscriminatorColumn((name=…, discriminatorTye=…) und dann @DiscriminatorValue(“…“) über der Class Definition der obersten Basisklasse, wobei eine davon abgeleitete Entity hingegen nur den Discriminator Value mittels Annotation @DiscriminatorValue(“…“) über der abgeleiteten Klassendefinition redefiniert. (Dies kann bei optionalen Attributen einer Entity (z.B. nullable, etc.) allerdings etwas problematisch werden.) Hierzu ein Beispiel.

Die Vererbungsstrategie “Joined-Table“ wird durch den InheritanceType.JOINED_TABLE als Wert des strategy Parameters der @Inheritance Annotation über der absoluten Basisklasse abgebildet, wobei pro Klasse jeweils eine neue Tabelle entsteht. Hierbei ist die Spaltenanzahl gegenüber der zuvor genannten Strategie natürlich wesentlich geringer, da sie nur aus den jeweils neu hinzugekommenen Attributen je abgeleiteter Subclass besteht. Hier ein Beispiel dazu.

Die Vererbungsstrategie “Table-Per-Class“ wird durch den InheritanceType.TABLE_PER_CLASS als Wert des strategy Parameters der @Inheritance Annotation abgebildet. Hierbei ensteht eine neue Tabelle je Klasse, mit der Anzahl der Spalten aller vererbten Attribute der Basisklasse und dazu den zusätzlichen Attributen der abgeleiteten Subclass. Dafür ist eine übergeordnete Primary Key Generierungs-Strategie erforderlich, also entweder per strategy=GenerationType.SEQUENCE oder wo dies nicht geht per strategy=GenerationType.TABLE. Bei Änderungen in der Klassenhierarchie können hierbei allerdings Probleme entstehen. Ein Beispiel zu dieser Strategie findet sich hier.

Was die Basisklassen innerhalb einer Entity Vererbungshierarchie betrifft, können Java Beans (also auch simple POJOs) zwar als Basisklassen verwendet werden, jedoch nicht als einzelne Entities in der DB persistiert werden, da sie ja keine Entity Beans sind. Allerdings können sie als designierte Basisklassen persistierbare Attribute an die hiervon abgeleiteten Entity Subclasses vererben. Die Java Bean Oberklasse wird hierbei mit der @MappedSuperclass Annotation annotiert, die Entity mit der @Inheritance(strategy=InheritanceType.JOINED) Annotation annotiert. Hier ein Beispiel dazu.

Einsatz des Criteria APIs und die Verwendung von JPQL:

JPQL:

Zunächst geht es um allgemeine Bemerkungen zu JPQL Queries, danach um die wichtigen Query Interfaces “TypedQuery“ und “Query“ und die Syntax von JPQL Statements, sowie die JPQL Neuerungen in JPA 2.0. Abschließend geht es um das Criteria API.

JPQL kann für SQL Abfragen in einer Query oder TypedQuery verwendet werden und beinhaltet Funktionen (wie z.B. z.B. Skalare MIN(), MAX()), Ausdrücke, Operationen und named Parameter, außerdem werden BULK Operationen unterstützt, wie UPDATE oder DELETE. Ganz konkret also SELECT id, message, log_date FROM message_log m WHERE m.log_date < :endDate als Query Syntax und als BULK DELETE Beispiel DELETE FROM message_log m WHERE m.log_date < :yesterday . Da Query Operationen über den EntityManager (APIs hier für JPA1.0 und JPA 2.0) ausgeführt werden, wird zuerst je Abfrage mittels Aufruf von Query query = em.createQuery(queryString) ein Query-Objekt erzeugt, wonach man über query.getResultList() anschließend die Ergebnisdatenliste erhält. Ab JPA 2.0 werden auch typsichere Queries unterstützt, wie z.B. so: TypedQuery<Message> query = em.createQuery(queryString, Message.class); List<Message> message = query.getResultList();. Folgendes ist noch bei BULK Operationen und dem erwünschten kaskadierenden Löschen von Relationen auf der DB zu beachten. In der Specification ist die Löschung von Relationen zwar nicht obligatorisch vorgesehen, was bedeutet, dass Key Constraint Violations akzeptiert werden, andererseits ist jedoch spezifiziert, dass ElementCollections den Lifecycle ihrer Entity übernehmen (was z.B. in Hibernate 3.5.x noch nicht unterstützt wurde). Eine mögliche Lösung des Problems besteht im Setzen von CASCADE DELETE constraints für die betroffenen Tabellen des Datenbank Schemas.

Ein Aufruf der Methode query.getResultList() liefert den jeweiligen Rückgabewert bzw. die ResultList einer Query, dabei kommen als Rückgabetypen simple Werte, wie bei einem COUNT(*), eine einzelne Entity (wie bei der Abfrage auf den Primary Key einer Entity in der WHERE Klausel), oder natürlich eine Liste von simplen Column Werten oder von Entities, sowie eine Liste von Tupeln simpler Werte oder eine Liste von Tupeln von Entities, z.B. bei Bildung des kartesischen Produkts mittels “Cross JOIN“ zweier Entities: SELECT cust, ord FROM Customer AS cust, Order AS ord.

Hier gibt es genauere Informationen zu den Methoden des Interfaces “TypedQuery“ und zum “Query“ Interface. Dabei sind die folgenden Methoden seit JPA 2.0 hinzugekommen: FlushModeType getFlushModeType(), Map getNamedParameters(), Map getHints(), Set<String> getSupportedHints(), int getFirstResult(), int getMaxResults(). Hier noch ein Beispiel zur Verwendung der Interfaces zwecks Berechnung der Pagination.

Die SQL-Fragmente eines JPQL Statements sollen noch etwas genauer hinsichtlich ihrer SQL Syntax betrachtet werden: jede Query hat zwecks Angabe der Table bzw. der Entity einen “FROM tablename bzw. EntityName (AS) alias“ Teil, z.B. also FROM Customer (AS) c. Bei einem INNER JOIN werden relationale Entities zu einem Object[] verknüpft, wie beim häufig verwendeten “SELECT cust, ord FROM Customer (AS) cust, Order (AS) ord WHERE (cust.customerNumber = ord.customerNumber AND ord.deliveryDate=SYSDATE“), also als INNER JOIN Beispiel: SELECT cust, ord FROM Customer (AS) cust (INNER) JOIN cust.orders (AS) ord WHERE ord.deliveryDate = SYSDATE. Die Gefahr der Bildung eines kartesischen Produkts in JPQL Queries ist relative gering, kann aber auftreten, wenn zwei Entities im SELECT Teil und im FROM Teil verlangt werden, ohne zusätzliche Verwendung einer einschränkenden WHERE Bedingung. Hier ein paar typische (INNER) JOIN Beispiele. Es soll noch erwähnt werden, dass aus der EJB QL, wie sie in den EJB 2.0 und EJB 2.1 Spezifikationen verwendet wurde, auch der IN Operator, welcher im FROM Teil einer Query Collections auf Assoziationen mappt, in JPQL verwendet werden kann. Der IN Operator evaluiert, ob die Variable p ein Aufzählungstyp (1..n Elemente) der Telefonnummern Collection ist. Der JOIN Operator ist allerdings effektiver, um Relationen abzubilden und auch der empfohlene Operator für Queries. Hier die angesprochene IN-Beispiel Query: SELECT DISTINCT p FROM Customer c, IN (e.phones) p. Ein weiteres Sprachelement der JPQL ist der Ausdruck “IS NOT EMPTY“. Hier ein Beispiel dazu. Weiterhin können mehrfache JOINs im FROM Teil verwendet werden. Dazu hier auch ein Beispiel. Die folgende Query holt den Namen und die Collection von Telefonnummern aller Kunden: SELECT c.name, p FROM Customer c JOIN c.phones p, was sich mit Hilfe eines sogenannten “Map JOINs“ unter Verwendung der Schlüsselwörter KEY, VALUE auch anders codieren lässt, hier die Beispiele dazu. Will man anstatt der einzelnen Elemente, per KEY oder VALUE, direkt ein Element einer Map erhalten, kann hierfür das Schlüsselwort ENTRY verwendet werden, wobei ENTRY ausschließlich im SELECT Teil verwendbar ist, wogegen KEY u. VALUE auch im WHERE Teil bzw. im HAVING Teil verwendet werden können. In jedem der Beispiele wurde eine Entity mit einem seiner Map Attribute geJOINt, um entweder einen Key, einen Value oder einen kompletten Map-Entry zurückzuerhalten. In JPA kann man aktuell nicht die Entity mit den Keys einer Entity Map JOINen. Die KEY, VALUE, und ENTRY Schlüsselwörter der angespochenen Map JOINs existieren seit JPA 2.0, da in JPA 1.0 Maps ausschließlich Entities enthalten konnten.

Bei einem LEFT(/RIGHT) OUTER JOIN werden relationale Entities zu einem Object[] verknüpft, wobei zusätzlich zu den matchenden Entities alle Entities der linken(/anderen) Entity ohne Bezug zur geJOINten Entity hinzugefügt werden. Hier ein Beispiel. Die FETCH Option beim JOIN lädt “Lazy Relationen“ direkt in die Entity Attribute. Hierzu ein Beispiel.

Der WHERE Teil einer JPQL Query reduziert die Menge der Ergebnisdaten und enthält entweder eine einfache Bedingung (z.B. WHERE cust.name=“Norris“) oder eine aus mehreren mittels AND/OR verknüpften Bedingungen zusammengesetzte WHERE Bedingung (z.B. (z.B. WHERE cust.name=“Norris“ AND cust.birthday=?2). In dem WHERE Teil kann mittels BETWEEN eine Range für eine Bereichsabfrage definiert werden (z.B. WHERE cust.age BETWEEN 18 AND 99). Weiterhin können im WHERE Teil mittels IN mehrere mit OR verknüpfte Bedingungen zusammengefasst werden (z.B. WHERE a.artnr IN (6351789, 6351790, 7371790, 9392852)). Mittels LIKE Operator kann in der WHERE Bedingung ein Pattern ähnlicher Strings zwecks Matching codiert werden (z.B. für 0..n Zeichen:  WHERE cust.name LIKE ‘%orri%‘, oder z.B. für ein einzelnes Zeichen: WHERE cust.name LIKE ‘Nor_is‘, oder mit escapten Wildcards: WHERE cust.name LIKE ‘\_orri%‘ ESCAPE ‘\‘). Die weiteren optionalen Schlüsselworte für die WHERE Bedingung sind die Worte IS [NOT] NULL, IS EMPTY, MEMBER [OF], EXISTS, ALL, ANY, SOME. Folgende vorhandene Funktionen können in der WHERE Bedingung verwendet werden. Als numerische Funktionen: ABS, LOCATE, LENGTH, MOD, SIZE, SQRT und als alphanumerische bzw. String Funktionen: CONCAT, LOWER, UPPER, SUBSTRING, TRIM … FROM (LEADING, TRAILING, BOTH). Hier ein Beispiel zu den Funktionen.

Das GROUP BY Element entweder anstatt oder nach der WHERE Bedingung gruppiert die Ergebnisdatenmenge nach den Attributwerten. Hier ein Beispiel dazu. Es darf darauf hingewiesen werden, dass es allerdings nicht das im PL/SQL verfügbare Sprach-Element des GROUP BY GROUPING SETS gibt.

Die ORDER BY Syntax kann anstelle der GROUP BY Syntax verwendet werden und sortiert die Ergbnisliste entweder vom kleinsten zum größten Wert (ASC), was der Default ist, oder vom größten zum kleinsten Wert (DESC). Bei der Verwendung von Objekten im ORDER BY als Ordnungskriterium wird automatisch der PRIMARY KEY als Sortierkriterium verwendet. Hier ein paar Beispiele hierzu.

Das HAVING Element nach der WHERE Bedingung oder dem GROUP BY schränkt die Auswahl zuvor selektierter Gruppen von Ergebnisdatenmengen weiter ein, hierzu ein Beispiel.

Die JPQL SELECT Syntax kann alle oder nur bestimmte Attribute aus der Ergebnisdatenmenge einer Entity oder mehrerer Entities selektieren, auch die Attribute einer ManyToOne oder OneToOne relationalen Entity können selektiert werden. Mittels Schlüsselwort DISTINCT werden doppelte Ergebnisdatensätze aus dem Ergebnis entfernt, was auch innerhalb numerischer Funktionen eingesetzt werden kann. Mit dem optionalen Schlüsselwort OBJECT kann explizit auf die Selektion eines Objekts hingewiesen werden, was aber zu einem üblichen “select c from Customer AS c“ völlig aquivalent ist. Das Schlüsselwort NEW kann unter Angabe des vollqualifizierten Package-Namens des zu allokierenden Rückgabe-Typs beliebige Rückgabe-Objekte erzeugen. Hier die Beispiele zur SELECT Syntax.

JPQL ermöglicht auch die Verwendung von BULK Operationen, wie UPDATE und DELETE. Diese Operationen ändern immer nur genau einen Entity Typ, wobei bei fehlender WHERE Bedingung alle Entities dieses einen Typ von den Änderungen betroffen sind. UPDATE ändert Datensätze, hier ein simples Beispiel zum UPDATE von Datensätzen.  DELETE löscht Datensätze, hier ein simples Beispiel zur Löschung von Datensätzen, wobei kaskadierendes Löschen der hiervon abhängigen Entities und Rows nur möglich ist, wenn auf der DB Tabelle der Constraint “ON DELETE CASCADE“ definiert wurde.

Hier noch ergänzend die mit JPA 2.0 verfügbaren JPQL Neuerungen zusammengefasst an Beispielen für die Verwendung in der WHERE Bedingung: Seit JPA 2.0 können für Timestamps auch Literale angegeben werden. Nicht-polymorphe Queries ähnlich wie ein instanceof können mittels CLASS-Syntax verwendet werden und Ausdrücke mittels IN können komplette Collections als Parameter verwenden. Weiterhin gibt es für Ergebnis-Listen ein Index Mapping mittels INDEX Schlüsselwort. Eine Möglichkeit für Fallunterscheidungen mittels CASE WHEN ELSE, … END ist ebenfalls in JPA 2.0 hinzugekommen. Hier die Beispiele zu den JPA 2.0 Neuerungen.

Criteria API:

Nun zum Criteria API. Dazu hier ein Beispiel, in welchem mittels den Criteria API Neuerungen (JPA 2.0) die folgende JPQL Abfrage codiert wird: “SELECT c from Customer c WHERE c.name=:customerName“. Ein weiteres Beispiel zur Verwendung des Criteria APIs gibt es hier. Eine Alternative zur Verwendung der Criteria API ist die Konkatenierung der JPQL Statement Fragmente, was ein SQL Statement zwar leichter nachvollziehbar und somit zwecks SQL Performance Tuning durchaus Sinn macht, jedoch nicht unbedingt zum Clean Code beiträgt.

JPA Transaktionen und Locking Prinzipien:

Es geht nun um die Themen Container Managed Transactions (CMT) und Bean Managed Transactions (BMT), sowie das Interface SessionSynchronization, um Optimistic und Pessimistic Locking und die mit Transaktionen thematisch verbundenen Exceptions, die unterschieden werden in System Exceptions und Application Exceptions.

CMT: Bei Container Managed Transactions (CMT) kann der Entwickler bzw. Assemblierer/Deployer deklarativ per Annotationen oder XML Dateien das transaktionale Verhalten einer EJB festlegen, was für die Applikation eine automatische Transaktions-Steuerung zur Folge hat. CMT wird von allen Bean Typen unterstützt, also von Session Beans (SLSB, SFSB), Entity Beans und Message Driven Beans (MDB). Somit nehmen Entity Beans ebenfalls nach Konfiguration des Transaktionsverhaltens automatisch an CMT Transaktionen teil. Hier ein Beispiel einer per @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) Annotation transaktional annotierten Beispiel Methode und dazu auch gleich ein per <assembly-descriptor> konfiguriertes CMT Konfigurations-Beispiel.

Was die Möglichkeit der Vergabe von Transaktionsattributen betrifft, so kann dies entweder auf Klassenebene über der Klassendefinition einer mittels @Stateless Annotation annotierten Stateless Session Bean (SLSB) erfolgen, oder auf Methodenebene z.B. über der Methode addItem(Item item) einer mittels @Stateful Annotation annotierten Stateful Session Bean (SFSB). Transaktionsattribute werden mittels @TransactionAttribute(TransactionAttributeType)  Annotation annotiert und legen über die Transaction Propagation des TransactionAttributeType-Enumeration Werts das transaktionale Verhalten (speziell den Beginn einer Transaktion, also ob transaktional und wenn ja, wie, oder eben nicht-transaktional) der Klasse oder Methode fest. Ein Beispiel dafür findet sich hier.

Beim Abbruch einer Transaktion, also dem Ende einer Transaktion, gibt es auch unterschiedliche Varianten. Entweder  entsteht während der (Stateful) Session Bean Methode eine spezielle Exception, für deren Auftreten z.B. Rollback definiert ist, oder im Session Bean Code wird für die laufende Transaktion Rollback angefordert, z.B. per sessionContext.setRollbackOnly(), oder per Attribut einer @Transactional Annotation (wenn z.B. die Spring Transaktions-Unterstützung bei Verwendung eines JTA TransactionManagers genutzt wird, bzw. bei Verwendung von Springs TransactionTemplate können die entsprechenden Attribute ebenfalls gesetzt werden, hier noch eine typische persistence.xml mit einer entsprechenden <persistence-unit> für CMT und JTA bzw. “global transactions“). Zu unterscheiden bei Verwendung entsprechender Transaction Propagation Attribute (Required, RequiresNew, NotSupported, Supports, Mandatory, Never) ist, ob die Transaktion vom Client bereits gestartet wird (sog. “ Transaction View“ Design Pattern), also mit Transaktionskontext läuft, oder erst auf dem Server gestartet wird (simples MVC Pattern), also ohne Transaktionskontext läuft. Ohne Transaktionskontext bekommt der Client im Fall MANDATORY eine TransactionRequiredException, bzw. TransactionRequiredLocalException, da keine Transaktion gestartet ist. Mit Transaktionskontext bekommt der Client im Fall der Propagation NEVER eine RemoteException (EJBException), da die gestartete Transaktion nicht bearbeitet werden kann. Diese Restriktionen gilt es also bei Verwendung bzw. Nicht-Verwendung des Transaction View Patterns zu beachten. Zu beachten bei CMT ist außerdem, dass CMT nur bei Aufrufen über das Remote oder Local Interface funktioniert nicht jedoch bei Aufrufen einer zweiten Methode von einer ersten Methode aus innerhalb derselben Stateful Session Bean, selbst dann nicht, wenn diese Methoden mit der Annotation @TransactionAttribute( TransactionAttributeType.REQUIRED ) bzw. @TransactionAttribute( TransactionAttributeType.REQUIRES_NEW ) annotiert sind. Hier das angesprochene Beispiel.

Als Standard- bzw. Default-Verhalten/-Konfiguration der meisten ApplicationServer wird eine Transaktion gestartet je Aufruf über das Remote oder Local Interface. Sowohl Session Beans, als auch Message Driven Beans können mittels @TransactionManagement(TransactionManagementType.BEAN) Annotation über der Klassen Definition oder mittels dieser Annotation, aber mit einem anderem TransactionManagementType, nämlich demjenigen für CMT, also mittels @TransactionManagement(TransactionManagementType.CONTAINER), wobei CONTAINER der Default Wert ist, zu einer transaktionalen EJB werden.

BMT: Bei Bean Management Transaction (BMT) läuft es etwas anders, denn die EJB ist für die Transaktionssteuerung selbst zuständig, wofür die Methoden der Transaktionsklammer (BEGIN, COMMIT, ROLLBACK im Fehlerfall) selbst codiert werden müssen. Der Beginn bzw, Erhalt einer Transaktion erfolgt entweder über sessionContext.getUserTransaction(), mittels Dependency Injection (DI) z.B. mittels Annotation @Resource UserTransaction uTx; (und ist danach unter dem Environment Naming Context Wert “java:comp/UserTransaction” auffindbar) oder per Context Lookup über JNDI: uTx = (UserTransaction) ctx.lookup( “/UserTransaction“ );. Hier ein typisches Beispiel für eine BMT Anwendung, wobei uTx.begin(), uTx.commit() und uTx.rollback() in einem try{] catch{} Handler verwendet werden. Beim Abbruch einer Transaktion, also dem Ende einer Transaktion, wird entsprechend der catch() Teil aufgerufen und die Transaktion zurückgerollt.

Die Methoden des UserTransaction Interface finden sich hier. Jede JTA Transaktion ist an einen ausführenden Thread gebunden, wodurch genau ein ausführender Thread je Prozessor Kern zu einem Zeitpunkt aktiv sein kann. Wenn also eine UserTransaction gerade läuft und aktiv ist, kann der User (der Client) zusätzlich keine weitere Transaktion in demselben Thread starten, bis die erste Transaktion commitet oder zurückgerollt wurde, oder per Timeout zurückgerollt und beendet wurde. In bestimmten CMT Situationen (REQUIRES_NEW mit Transaction View Pattern) würde der Container bei einer CMT die erste Transaktion suspenden, um eine neue zu starten und um nach erfolgtem Commit der neuen Transaktion, die erste wieder zu reaktivieren. Dies kann jedoch nur der Container realisieren, aber bei UserTransactions (BMT) gibt es diese Möglichkeiten nicht, weder im API des UserTransaction Interface, noch woanders. Zum Thema uTx.commit() ist noch zu bemerken, dass der Container beim Aufruf von Commit eine Exception auslöst, falls vorher die Methode uTx.setRollbackOnly() aufgerufen wurde, oder falls im Rahmen des Two-Phase Commit eine Rollback-bewirkende Exception ausgelöst wird.

Exceptions: Welche sind nun diese Exceptions, die zu den beschriebenen Rollback-Aktionen führen. Unterschieden werden diese in Exceptions der Kategorie “System Exception“ oder der Kategorie “Application Exception“. Die System Exception entsteht bei einem technischen EJB Fehler und ist stets eine Folge eines besonders gravierenden Fehler des Software Systems. Eine System Exception führt immer automatisch zu einem serverseitigen ROLLBACK und ist immer eine sog. “Unchecked Exception“ (z.B. NullPointerException, ArrayOutOfBoundsException, etc.). Sie werden von der serverseitigen App in eine RemoteException (sog. “Exception Wrapper“) gekapselt für eine bessere Behandlung durch den Client, z.B. als Subclass einer RemoteException in einer TransactionRolledBackException. Bei einer System Exception wird die beteiligte EJB immer aus dem Container entfernt.

Hingegen die Application Exception ist als anwendungsbezogener EJB Fehler zu werten und wird auch so verarbeitet, d.h. eine Application Exception stellt einen fachlichen Anwendungsfehler dar und wird nie gekapselt, sondern direkt an den Client weitergereicht. Dabei wird die beteiligte EJB nicht aus dem Container entfernt. Per @ApplicationException Annotation können abgeleitete Exception Subclasses als fachliche Fehler (um-)deklariert werden und per Attribut rollback=true für den Rollback markiert werden, mittels rollback=false (was der Default Wert ist) hingegen ohne Rollback, obwohl diese Exception auch eine Ausprägung von RuntimeException sein kann. Als Varianten für abgeleitete Exceptions kommen in Abhängigkeit von ihrer Basisklasse zwei Varianten vor. Entweder als “Checked Exception“, d.h. die Exception ist von der Basisklasse Exception abgeleitet, weshalb bekanntlich mittels throws Syntax diese Exception in der Signatur der EJB- und Remote Interfaces angegeben werden müssen, oder (seit EJB 3.0) als “Unchecked Exception“, d.h. die Exception ist von der Basisklasse RuntimeException abgeleitet, weshalb sie nicht in den Methodensignaturen der Interfaces angegeben werden muß, mit der Besonderheit, dass die betroffene bzw. auslösende SessionBean nicht aus dem Container entfernt wird.

Abschließend soll zum Thema BMT bemerkt werden, dass Message Driven Beans (MDBs) die Möglichkeit der Kontrolle von über JMS ausgeführten Transaktionen bieten und darüber hinaus eine Bestätigung bzw. Quittung über die erhaltene Message ermöglichen. JTA Transaktionen sind mit JMS Transaktionen verknüpfbar. Hier ein Beispiel dazu.

Ein Wort noch zur Verwendung des angesprochenen “Transaction View“ Design Patterns und der Vermeidung von  “Detachted Entities“. Entweder verwendet man empfehlenswerterweise keine per SessionBeans geladenen Entities in einer JSP View, oder verwendet einen Conversational PersistenceContext, der so lange verfügbar bleibt bis der Rendering Prozess der View abgeschlossen ist, sodass lazy-loading Relationen aufgelöst und im Bedarfsfall geladen werden können. Wenn man also keine Entities in der JSP View verwenden möchte, kann dies das Umkopieren von Entity Daten in andere Datenstrukturen mit gänzlich anderem lazy-loading Verhalten. Ein Design Pattern dieser Methodik ist das ”Transfer Object” Design Pattern, was hier aber reichlich redundant erscheint angesichts der POJO Struktur von Entities. Als sinnvolleres Konzept bietet sich hier die Verwendung von “Projection Queries“, um nur die Entity Attribute zu laden, welche auch in der JSP Page präsentiert werden sollen, anstatt immer die kompletten Entity Instanzen zu laden. Was würde es bedeuten einen Conversational PersistenceContext so lange, wie benötigt, offen zu halten. Es würde heißen, dass es entweder immer eine aktive Transaktion gibt für Entities, die aus Transaction-Scope PersistenceContexts gelesen werden, oder dass ein Application-managed oder ein Extended PersistenceContext verwendet wird. Dies ist offensichtlich nicht die beste Option, wenn Entities serialisiert in einen anderen Layer oder zu einem Remote Client übertragen werden müssen, genügt aber oft den Anforderungen einer simplen Web Applikation.

Optimistic und Pessimistic Locking: Zum Thema “Optimistic und Pessimistic Locking“ ist zu bemerken, dass dies noch einfacher gehalten wurde, als z.B. bei der JPA Implementierung Hibernate. Während in JPA 1.0 nur “Optimistic Locking“ unterstützt wurde, unterstützt JPA 2.0 nun auch “Pessimistic Locking“. Bei der Verwendung von Optimistic Locking ist zu beachten, dass Locking nicht-versionierter Objekte nicht unbedingt unterstützt wird und die JPA Implementierung in dem Fall deshalb eine “PersistenceException“ auslöst.

Optimistic Locking: Dies bedeutet Konfliktsteuerung ohne zusätzliche Locks bzw. Sperren von DB Rows. Eine Anwendung lädt, ändert und schreibt die geänderten Datensätze über eine long-running Transaction, JPA sorgt vor dem Commit dafür, dass alle geänderten Datensätze nicht konkurrierend von einer anderen Transaction geändert wurden, sondern nach ihrer beabsichtigten Änderung commitet werden können. Ein Problem dieses Konzept ist die zeitlich zu spät ausgelöste Benachrichtigung per “OptimisticLockingException“ über eine konkurrierend durchgeführte Änderung. Außerdem muß bei BULK Updates das Versionsattribut selbst verwaltet werden. Prinzipiell läuft ein Optimistic Locking immer quasi automatisch im Hintergrund, jede Entity mit 1..n mittels @Version public int getVersion() {…} annotierten Attributen wird optimistisch gelockt verwaltet. Jede Änderung der solcherart versionierten Objekte führt beim COMMIT zur Inkrementierung der Version, Konflikte durch konkurrierende Änderungen erzeugen hingegen immer eine “OptimisticLockingException“. Eine explizite Optimistic Locking Strategie ist möglich, ein Versionscheck eines bestimmten Objekts kann vor jedem COMMIT angefordert werden per LockModeType.OPTIMISTIC (JPA 2.0) bzw. per LockModeType.READ (JPA 1.0). Ein Versionscheck eines bestimmten Objekts kann vor jedem COMMIT angefordert werden und die Version kann beim COMMIT inkrementiert werden, auch wenn nichts geändert wurde, per LockModeType.OPTIMISTIC_FORCE_INCREMENT (JPA 2.0) bzw. per LockModeType.WRITE (JPA 1.0).

Pessimistic Locking: Dies bedeutet Konfliktsteuerung mit zusätzliche Locks bzw. Sperren von DB Rows. Eine Anwendung lädt, ändert und schreibt die geänderten Datensätze über eine long-running Transaction, JPA sorgt dafür, dass in der DB explizite Read/Write Locks für die betroffenen DB Rows gesetzt werden. Ein Problem dieses Konzept ist, dass die DB Row Locks die Mehrprozessfähigkeit bzw. Parallelität der Applikation verringern und dadurch die Laufzeit-Performance verschlechtern. Auch können dadurch sog. “Dead Locks“ entstehen. Prinzipiell läuft ein Pessimistic Locking entweder über einen bestimmten Isolations Level der DB, ist somit nicht in der JPASpezifikation festgelegt, oder durch explizites Locking evtl. versionierter Beans mittels JPA Lock Mode. Explizite Pessimistic Locks können folgendermaßen gesetzt werden: entweder ein Read Lock beim Lesen eines Satzes mittels LockModeType.PESSIMISTIC_READ setzen, oder eine Schreibsperre beim Lesen eines Satzes mittels LockModeType.PESSIMISTIC_WRITE setzen. Eine Schreibsperre für ein bestimmtes Objekt kann beim Lesen eines Datensatzes (DB Row) gesetzt werden und die Version kann beim COMMIT inkrementiert werden, auch wenn nichts geändert wurde, per LockModeType.PESSIMISTIC_FORCE_INCREMENT (in JPA 2.0) bzw. per LockModeType.WRITE (in JPA 1.0). Hier ein Beispiel zu Pessimistic Write Locking mit und ohne Refresh.

SessionSynchronization Interface: Dieses Interface enthält Callback Methoden für den Transaktions-Verlauf und darf nur in SessionBeans verwendet werden. Es funktioniert nur für Transaktionen der Propagation REQUIRED, REQUIRED_NEW oder MANDATORY. Die Einsatzbereiche der durch das Interface abstrahierten Callback-Methoden bestehen in der Wiederherstellung von SessionBean Attributen nach fehlgeschlagenen Transaktionen, in der Kontrolle des Transaktionsverlaufs und im Logging transaktionaler Vorgänge. Die void Methoden des SessionSynchronization Interfaces lauten afterBegin() zur Markierung des Starts einer Transaktion, beforeCompletion() zur Meldung eines angeforderten Commits mit der Möglichkeit noch setRollbackOnly() aufzurufen und afterCompletion(boolean committed), welche den erfolgreichen Abschluß der Transaktion markiert.

Die Verwendung des Spring Application Context und der @Transactional Annotation auf JPA- und Hibernate-DAO Ebene erklärt der bereits vorhandene Artikel „Spring – Mapper Templates und Transaktions Manager nutzen“ in diesem Blog.

Auch praktische Erklärungen zu Service-Architekturen mit Spring, Hibernate als JPA Implementierung und WebServices finden sich hier in diesem Blog.

Alle verwendeten Beispiele dieses Blog-Eintrags finden sich übrigens hier.

Quelle dieses Blog Eintrags sind die bei SpringSource frei verfügbare Spring Referenz, verschiedene Bücher aus dem Apress-Verlag, z. B. “Pro JPA 2“, das Buch „Spring im Einsatz“ von Craig Walls aus dem Hanser Verlag, das Buch “Spring In Practice”, das Buch „Hibernate” von Sebastian Hennebrüder, das Buch „Spring & Hibernate” aus dem Hanser Verlag, sowie eigene Erfahrungen mit dem Spring Framework, mit SOAP und REST, mit dem HibernateTemplate, ORM-Mappern, mit HibernateDaoSupport und JpaDaoSupport Klassen und mit Transaktionen.

Kommentare

4 Kommentare zu “Spring – JPA und Transaktionen”

  1. Von Eclipse RCP – Datenbank Viewer mit Swing, JPA : binaris informatik GmbH am Freitag, 30. November 2012 17:35

    […] existieren dazu in diesem Blog bereits mehrere Artikel bzw. Blog-Einträge, z.B. hier, hier, hier, und hier, weshalb hier nur auf das sehr gute “Pro JPA 2“-Buch aus dem apress-Verlag verwiesen […]

  2. Von Services – TDD mit Spring WebFlow, EasyMock : binaris informatik GmbH am Sonntag, 26. Januar 2014 13:05

    […] ist jedoch unabhängig von der aufrufenden Anwendungsoberfläche und basiert auf JPA. Als Datenbank wird wegen dem vorteilhaften Caching-Verhalten die In-memory HSQL DB […]

  3. Von WebApps – JSF 2, Primefaces, FileDownloads, JPA 2, EJB 3, JEE7, REST, MySQL, WildFly : Karriere als Software Entwickler | binaris informatik GmbH am Sonntag, 26. März 2017 20:02

    […] (Anm.: Die Tabelle Customer, welche den Bewerber („Job Applicant“) darstellt enthält dabei die Adresse, welche physisch in die Tabelle integriert ist und deshalb auf der Entity „Customer“ als @Embeddable Adresse annotiert wird. Zu JPA Annotationen gibt es bereits einen Eintrag in diesem Blog hier.) […]

  4. Von WebApps – JSF 2, Primefaces, FileDownloads, JPA 2, EJB 3, JEE7, REST, MySQL, WildFly : Karriere als Software Entwickler | binaris informatik GmbH am Sonntag, 26. März 2017 20:03

    […] (Anm.: Die Tabelle Customer, welche den Bewerber („Job Applicant“) darstellt enthält dabei die Adresse, welche physisch in die Tabelle integriert ist und deshalb auf der Entity „Customer“ als @Embeddable Adresse annotiert wird. Zu JPA u. der @Embeddable Annotationen gibt es bereits einen Eintrag in diesem Blog hier.) […]

Einen Kommentar hinzufügen...

Sie müssen registriert und angemeldet sein um einen Kommentar zu schreiben.