Nach einigen Experimenten habe ich festgestellt, dass es tatsächlich möglich ist, zwei Persistenzeinheiten zu haben, die nicht-XA-Ressourcen innerhalb einer Container-verwalteten Transaktion verwenden. Es kann jedoch implementierungsabhängig sein. TL, DR am unteren Rand.
JTA sollte XA-Ressourcen erfordern, wenn mehr als eine Ressource an einer Transaktion teilnimmt. Es verwendet X/Open XA, um verteilte Transaktionen zu ermöglichen, beispielsweise über mehrere Datenbanken oder eine Datenbank und eine JMS-Warteschlange. Es gibt anscheinend eine Optimierung (es kann GlassFish-spezifisch sein, ich bin mir nicht sicher), die dem letzten Teilnehmer erlaubt, nicht-XA zu sein. In meinem Anwendungsfall sind jedoch beide Persistenzeinheiten für die gleiche Datenbank (aber eine andere Gruppe von Tabellen, mit einer möglichen Überlappung) und beide sind Nicht-XA. Das bedeutet, dass wir erwarten, dass eine Ausnahme ausgelöst wird, wenn die zweite Ressource XA nicht unterstützt.
Angenommen, das ist unser persistence.xml ist
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="playground" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>jdbc/playground</jta-data-source>
<properties>
<property name="hibernate.dialect" value="be.dkv.hibernate.SQLServer2012Dialect" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
<persistence-unit name="playground-copy" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>jdbc/playground</jta-data-source>
<mapping-file>META-INF/orm-playground-copy.xml</mapping-file>
<properties>
<property name="hibernate.dialect" value="be.dkv.hibernate.SQLServer2012Dialect" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
Es gibt zwei Persistenzeinheiten, eines mit Namen playground
, der andere mit dem Namen playground-copy
. Letzteres hat eine ORM-Mapping-Datei, aber das ist ein bisschen neben dem Punkt hier. Wichtig ist, dass beide die gleichen <jta-data-source>
haben.
Im Anwendungsserver (in diesem Fall GlassFish) haben wir einen JDBC-Verbindungspool mit einer JDBC-Ressource namens playground
, die diesen Pool verwendet.
Wenn nun zwei Persistenzkontexte in einen EJB injiziert und ein Verfahren genannt, die innerhalb eines Container verwalteten Transaktion betrachtet wird, würden Sie Dinge erwarten wie folgt aussehen.
Beide Persistenzkontexte nutzen die gleiche Datenquelle, aber weder der Transaktionsmanager noch PPV Schicht sollte über das wirklich viel kümmern. Schließlich haben sie möglicherweise unterschiedliche Datenquellen. Da die Datenquelle ohnehin von einem Verbindungspool unterstützt wird, erwarten Sie, dass beide Einheiten ihre eigene Verbindung erhalten. XA würde es ermöglichen, dass die Arbeit auf transaktionale Weise abläuft, da XA-aktivierte Ressourcen ein 2-Phasen-Commit implementieren.
Wenn jedoch oben mit der Datenquelle auf einen Verbindungspool mit einer Nicht-XA-Implementierung (und einige tatsächliche Persistenz Arbeit) versucht, gab es keine Ausnahme und alles hat gut funktioniert! Die XA-Unterstützung im MSSQL-Server wurde sogar deaktiviert, und der Versuch, einen XA-Treiber zu verwenden, würde zu einem Fehler führen, bis er aktiviert wurde. Es ist also nicht so, dass ich versehentlich XA verwendet habe, ohne es zu wissen.
Mit einem Debugger in den Code gegangen ergab, dass beide Persistence-Kontexte, die unterschiedliche Entity-Manager (wie sie sollten) in der Tat die gleiche Verbindung verwenden. Einige weitere Ausgrabungen zeigten, dass die Verbindung nicht als XA-Transaktion festgelegt wurde und dieselbe Transaktionskennung auf JDBC-Ebene hatte. So wurde die Situation folgendermaßen aus:
Ich kann nur annehmen, dass die JPA-Provider eine Optimierung hat die gleiche Verbindung zu nutzen, wenn mehrere Einheiten für die gleiche Transaktion erstellt. Also, warum sollte das okay sein? Auf der JDBC-Ebene werden Transaktionen für eine Verbindung festgeschrieben. Soweit ich weiß, bietet die JDBC-Spezifikation keine Möglichkeit, mehrere Transaktionen auf einer einzelnen Verbindung auszuführen. Das heißt, wenn die Arbeit für einen Persistenzkontext festgeschrieben wird, würde das Commit auch für den anderen passieren.
Aber das ist eigentlich warum es funktioniert. Der Festschreibungspunkt für eine verteilte Transaktion sollte so wirken, als ob alle Teile ein Ganzes bilden würden (vorausgesetzt, alle stimmen in der Abstimmungsphase mit "Ja"). In diesem Fall arbeiten beide Persistenzkontexte auf der gleichen Verbindung, also sind sie implizit eine Arbeitseinheit. Da die Transaktion vom Container verwaltet wird, gibt es keinen sofortigen Zugriff darauf, was bedeutet, dass Sie nicht zum Commit des einen und nicht des anderen Contexts wechseln können. Und mit nur einer einzigen Verbindung, um sich tatsächlich bei der Transaktion zu registrieren, muss es nicht XA sein, da es aus der Sicht des Transaktionsmanagers nicht als verteilt betrachtet wird.
Beachten Sie, dass dies nicht die Lokalität der Persistenzkontexte verletzt. Das Abrufen einer Entität aus der Datenbank führt zu einem separaten Objekt in beiden Kontexten. Sie können immer noch unabhängig voneinander arbeiten, genauso wie sie es mit getrennten Verbindungen tun würden. Im obigen Diagramm stellen die abgerufenen Entitäten desselben Typs mit demselben Primärschlüssel die gleiche Datenbankzeile dar, sind jedoch separate Objekte, die von ihren jeweiligen Entitätsmanagern verwaltet werden.
Um zu überprüfen, ob dies tatsächlich eine Optimierung durch den JPA-Provider ist, habe ich einen zweiten Verbindungspool (zu derselben Datenbank) und eine separate JDBC-Ressource erstellt, für die zweite Persistenzeinheit festgelegt und getestet. Daraus ergibt sich die erwartete Ausnahme:
Caused by: java.sql.SQLException: Error in allocating a connection.
Cause: java.lang.IllegalStateException: Local transaction already has 1 non-XA Resource: cannot add more resources.
Wenn Sie zwei JDBC-Ressourcen erstellen, aber beide auf den gleichen Verbindungspool zeigen, dann wieder es funktioniert gut.Dies funktionierte sogar, wenn explizit die Klasse com.microsoft.sqlserver.jdbc.SQLServerConnectionPoolDataSource
verwendet wurde, was bestätigt, dass es sich wahrscheinlich um eine Optimierung auf der JPA-Ebene handelt, anstatt versehentlich dieselbe Verbindung zweimal für dieselbe Datenquelle zu erhalten (was das GlassFish-Pooling besiegen würde). Wenn Sie eine XA-Datenquelle verwenden, handelt es sich in der Tat um eine XA-fähige Verbindung, aber der JPA-Anbieter verwendet dieselbe für beide Persistenzkontexte. Nur wenn separate Pools verwendet werden, sind es tatsächlich zwei völlig separate XA-aktivierte Verbindungen, und Sie erhalten die obige Ausnahme nicht mehr.
Also, was ist der Haken? Zuallererst habe ich nichts gefunden, was dieses Verhalten in den JPA- oder JTA-Spezifikationen beschreibt (oder vorschreibt). Das bedeutet wahrscheinlich eine implementierungsspezifische Optimierung. Wechseln Sie zu einem anderen JPA-Anbieter oder sogar zu einer anderen Version und es funktioniert möglicherweise nicht mehr.
Zweitens ist es möglich, Deadlocks zu bekommen. Wenn Sie die Entität im obigen Beispiel in beiden Kontexten abrufen, dann ändern Sie sie in eine und flush, es ist in Ordnung. Rufen Sie es in einem Kontext ab, rufen Sie die Flush-Methode auf und versuchen Sie dann, sie in der anderen zu holen, und Sie könnten einen Deadlock haben. Wenn Sie die Lese-nicht festgeschriebene Transaktionsisolation zulassen, würden Sie dies vermeiden, aber was Sie in einem Kontext sehen würden, würde davon abhängen, wann Sie es mit Rücksicht auf einen Flush in dem anderen abgerufen haben. So können manuelle Flush-Anrufe knifflig sein.
Als Referenz wurde die GlassFish-Version verwendet . Der JPA-Anbieter war die Hibernate-Version 3.6.4.Final
.
TL; DR
Ja, können Sie zwei Persistenzkontexte mit der gleichen Nicht-XA-Ressource in einer JavaEE Container verwalteten Transaktion verwenden und ACID-Eigenschaften erhalten bleiben. Dies liegt jedoch an einer möglichen Hibernate-Optimierung, wenn mehrere EntityManager für die gleiche Transaktion mit derselben Datenquelle erstellt werden. Da dies nicht durch die JPA- oder JTA-Spezifikationen vorgeschrieben scheint, können Sie sich wahrscheinlich nicht auf dieses Verhalten in JPA-Implementierungen, Versionen oder Anwendungsservern verlassen. Testen Sie also und erwarten Sie keine volle Portabilität.
Wenn die beiden Persistenzeinheiten die gleiche Datenquelle verwenden, dann sollten Sie nicht XA –
@SteveC Sie werden ihre eigene Verbindung von einem Pool bekommen, so sehe ich keine Möglichkeit, sie zu zwingen, die gleiche Verbindung (und Transaktion) oder wie die JPA dies tatsächlich –
Ich würde erwarten, dass sie beide an der gleichen JTA-Transaktion teilnehmen würden ... –