2017-01-26 3 views
10

Wir haben eine Anwendung, die eine Anzahl von Entitätsklassen hat, für die es zwei Tabellen geben muss. Die Tabellen sind identisch, mit dem einzigen Unterschied ist der Name. Die allgemeinen Lösungen, die hier auf SO angeboten werden, sind die Verwendung von Vererbung (eine zugeordnete Superklasse und eine Tabelle-für-Klasse-Strategie) oder zwei Persistenzeinheiten mit unterschiedlichen Zuordnungen. Wir verwenden die letztere Lösung und die Anwendung baut auf diesem Ansatz auf, so dass sie jetzt als gegeben betrachtet wird.Ist es möglich, zwei MSSQL-Persistenzeinheiten in einer Transaktion ohne XA zu haben?

Es gibt EJB-Methoden, die Aktualisierungen für beide Persistenzkontexte durchführen und dies innerhalb einer Transaktion tun müssen. Beide Persistenzkontexte haben die gleiche Datenquelle, eine XA-fähige Verbindung zu einer Microsoft SQL Server-Datenbank (Version 2012). Der einzige Unterschied zwischen den Kontexten besteht darin, dass eine Zuordnungs-XML vorhanden ist, um die Tabellennamen für einige Entitätsklassen zu ändern, und somit an diesen Tabellen arbeitet.

Einer der Architektur-Leads möchte, dass XA-Transaktionen eliminiert werden, da sie einen erheblichen Overhead für die Datenbank verursachen und anscheinend auch die Protokollierung und Analyse der ausgeführten Abfragen erschweren und möglicherweise auch vorbereitete Caches von Anweisungen verhindern . Ich kenne nicht alle Details, aber für viele Anwendungen konnten wir XA eliminieren. Für diesen können wir derzeit jedoch nicht wegen der zwei Persistenzkontexte.

Gibt es einen Weg in dieser Situation, um die Updates für beide Kontexte in einer Transaktionsart ohne XA zu erhalten? Wenn das so ist, wie? Falls nicht, ist es möglich, eine Architektur- oder Konfigurationsänderung vorzunehmen, um einen Persistenzkontext zu verwenden, ohne für die beiden Tabellen Unterklassen verwenden zu müssen?

Ich bin mir bewusst, diese Fragen: Is it possible to use more than one persistence unit in a transaction, without it being XA? und XA transaction for two phase commit

Vor diesem als Duplikat schließen Abstimmung zur Kenntnis nehmen, dass die Situationen verschiedene sind. Wir befinden uns nicht in einer schreibgeschützten Situation wie in der ersten Frage, beide Kontexte arbeiten auf der gleichen Datenbank, wir verwenden ausschließlich MSSQL und wir sind auf GlassFish, nicht auf Weblogic.

+0

Wenn die beiden Persistenzeinheiten die gleiche Datenquelle verwenden, dann sollten Sie nicht XA –

+0

@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 –

+0

Ich würde erwarten, dass sie beide an der gleichen JTA-Transaktion teilnehmen würden ... –

Antwort

5

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.

connection pool and resource

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.

persistence unit connections

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:

shared connection

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.

+1

Sind Sie sicher, dass Persistenzkontexte (Sitzungen) beider Entitätsmanager ordnungsgemäß geschlossen sind? Wenn nicht, können Sie nach einer Weile ohne Arbeitsspeicher auskommen oder mit veralteten Daten arbeiten, wenn eine Persistenzkontextinstanz in nachfolgenden Anforderungen wiederverwendet wird. –

+0

@DraganBozanovic Guter Punkt. Ich würde (möglicherweise zu Unrecht) davon ausgehen, dass die Hibernate-Entwickler mit der Mühe, explizit festzustellen, ob sich zwei Kontexte in der gleichen Container-Transaktion befinden und eine Verbindung haben, dies berücksichtigt haben. Würde es eine gute Möglichkeit geben, das zu testen? Sobald ich einen Anruf verlasse, bin ich außerhalb des Transaktionskontextes, also habe ich nicht wirklich etwas zum Debuggen. Ich denke, ich könnte die Hibernate-Quelle holen –

+0

Für den Anfang könnten Sie einfach viele Anfragen mit beiden Entity-Manager (über Jmeter oder ähnliches) abfeuern und über einen Profiler überprüfen, ob der Speicherverbrauch wächst. –

Verwandte Themen