XA-Transaktionen mit Spring

Die Verwendung des J (2) EE-Anwendungsservers war eine Norm, wenn High-End-Funktionen wie Transaktionen, Sicherheit, Verfügbarkeit und Skalierbarkeit obligatorisch sind. Es gibt nur sehr wenige Optionen für Java-Anwendungen, für die nur eine Teilmenge dieser Unternehmensfunktionen erforderlich ist. In den meisten Fällen entscheiden sich Unternehmen für einen vollwertigen J (2) EE-Server. Dieser Artikel konzentriert sich auf verteilte Transaktionen mit der JTA (Java Transaction API) und erläutert, wie verteilte Transaktionen (auch als XA bezeichnet) in einer eigenständigen Java-Anwendung ohne JEE-Server unter Verwendung des weit verbreiteten Spring-Frameworks und Open Source verwendet werden können JTA-Implementierungen von JBossTS, Atomikos und Bitronix.

Verteilte Transaktionsverarbeitungssysteme sollen Transaktionen ermöglichen, die heterogene, transaktionsbewusste Ressourcen in einer verteilten Umgebung umfassen. Mithilfe verteilter Transaktionen kann eine Anwendung Aufgaben wie das Abrufen einer Nachricht aus einer Nachrichtenwarteschlange und das Aktualisieren einer oder mehrerer Datenbanken in einer einzelnen Transaktionseinheit ausführen, wobei die ACID-Kriterien (Atomicity, Consistency, Isolation and Durability) eingehalten werden. Dieser Artikel beschreibt einige Anwendungsfälle, in denen verteilte Transaktionen (XA) verwendet werden könnten, und wie eine Anwendung die Transaktionsverarbeitung mithilfe von JTA zusammen mit den besten Technologien der Rasse erreichen kann. Das Hauptaugenmerk liegt auf der Verwendung von Spring als Server-Framework und darauf, wie verschiedene JTA-Implementierungen nahtlos für verteilte Transaktionen auf Unternehmensebene integriert werden können.

XA-Transaktionen und die JTA-API

Da der Umfang des Artikels auf die Verwendung von JTA-Implementierungen unter Verwendung des Spring-Frameworks beschränkt ist, werden wir kurz auf die Architekturkonzepte der verteilten Transaktionsverarbeitung eingehen.

XA-Transaktionen

Die von Open Group (einem Anbieterkonsortium) entwickelte verteilte X / Open- Transaktionsverarbeitung definiert eine Standardkommunikationsarchitektur, mit der mehrere Anwendungen Ressourcen gemeinsam nutzen können, die von mehreren Ressourcenmanagern bereitgestellt werden, und deren Arbeit in globalen Transaktionen koordiniert werden kann. Die XA- Schnittstellen ermöglichen es den Ressourcenmanagern, Transaktionen zu verbinden, 2PC (Two Phase Commit) durchzuführen und zweifelhafte Transaktionen nach einem Fehler wiederherzustellen.

Abbildung 1: Konzeptionelles Modell der DTP-Umgebung.

Wie in Abbildung 1 dargestellt, verfügt das Modell über die folgenden Schnittstellen:

  1. Über die Schnittstelle zwischen der Anwendung und dem Ressourcenmanager kann eine Anwendung einen Ressourcenmanager direkt aufrufen, wobei die native API oder die native XA-API des Ressourcenmanagers verwendet wird, je nachdem, ob die Transaktion vom Transaktionsmonitor verwaltet werden muss oder nicht.

  2. Über die Schnittstelle zwischen der Anwendung und dem Transaktionsmonitor (TX-Schnittstelle) kann die Anwendung den Transaktionsmonitor für alle Transaktionsanforderungen wie Starten einer Transaktion, Beenden einer Transaktion, Zurücksetzen einer Transaktion usw. aufrufen.

  3. Die Schnittstelle zwischen dem Transaktionsmonitor und dem Ressourcenmanager ist die XA-Schnittstelle. Dies ist die Schnittstelle, die das Zwei-Phasen-Festschreibungsprotokoll erleichtert, um verteilte Transaktionen unter einer globalen Transaktion zu erzielen.

JTA API

Die von Sun Microsystems definierte JTA-API ist eine übergeordnete API , die Schnittstellen zwischen einem Transaktionsmanager und den an einem verteilten Transaktionssystem beteiligten Parteien definiert. Die JTA besteht hauptsächlich aus drei Teilen:

  • Eine übergeordnete Anwendungsschnittstelle für eine Anwendung zur Abgrenzung von Transaktionsgrenzen. Die UserTransactionSchnittstelle kapselt dies.

  • Eine Java-Zuordnung des X / Open XA-Protokolls nach Industriestandard (Punkt 3 in den oben aufgeführten X / Open-Schnittstellen). Dies umfasst die im javax.transaction.xaPaket definierten Schnittstellen , die aus XAResource, Xid and XAExceptionSchnittstellen bestehen.

  • Eine übergeordnete Transaktionsmanagerschnittstelle, über die ein Anwendungsserver Transaktionen für eine Benutzeranwendung verwalten kann. Die TransactionManager, Transaction, Status and SynchronizationSchnittstellen definieren ziemlich genau, wie der Anwendungsserver Transaktionen verwaltet.

Nachdem wir nun eine kurze Zusammenfassung der JTA- und XA-Standards haben, wollen wir einige Anwendungsfälle durchgehen, um die Integration verschiedener JTA-Implementierungen mit Spring für unsere hypothetische Java-Anwendung zu demonstrieren.

Unsere Anwendungsfälle

Um die Integration verschiedener JTA-Implementierungen in Spring zu demonstrieren, werden die folgenden Anwendungsfälle verwendet:

  1. Aktualisieren Sie zwei Datenbankressourcen in einer globalen Transaktion. Wir werden JBossTS als JTA-Implementierung verwenden. Dabei werden wir sehen, wie wir die Semantik verteilter Transaktionen deklarativ auf einfache POJOs anwenden können.

  2. Aktualisieren Sie eine Datenbank und senden Sie eine JMS-Nachricht an eine Warteschlange in einer globalen Transaktion. Wir werden die Integration in Atomikos- und Bitronix-JTA-Implementierungen demonstrieren.

  3. Konsumieren Sie eine JMS-Nachricht und aktualisieren Sie eine Datenbank in einer globalen Transaktion. Wir werden sowohl Atomikos- als auch Bitronix-JTA-Implementierungen verwenden. Dabei werden wir sehen, wie wir Transaktions-MDPs (Message Driven POJOs) emulieren können.

Wir werden MySQL für die Datenbanken und Apache ActiveMQ als unseren JMS-Messaging-Anbieter für unsere Anwendungsfälle verwenden. Bevor wir die Anwendungsfälle durchgehen, werfen wir einen kurzen Blick auf den Technologie-Stack, den wir verwenden werden.

Frühlingsrahmen

Das Spring Framework hat sich als eines der nützlichsten und produktivsten Frameworks in der Java-Welt etabliert. Neben den zahlreichen Vorteilen bietet es auch die erforderliche Installation für die Ausführung einer Anwendung mit einer JTA-Implementierung. Dies macht es einzigartig in dem Sinne, dass eine Anwendung nicht in einem JEE-Container ausgeführt werden muss, um die Vorteile von JTA-Transaktionen zu nutzen. Bitte beachten Sie, dass Spring keine JTA-Implementierung als solche bietet. Aus Anwendersicht besteht die einzige Aufgabe darin, sicherzustellen, dass die JTA-Implementierung so verkabelt ist, dass sie die JTA-Unterstützung des Spring-Frameworks verwendet. Darauf werden wir uns in den folgenden Abschnitten konzentrieren.

Transaktionen im Frühjahr

Spring bietet sowohl programmatisches als auch deklaratives Transaktionsmanagement unter Verwendung eines einfachen Transaktionsframeworks. Dies erleichtert es eigenständigen Java-Anwendungen, Transaktionen (JTA oder Nicht-JTA) entweder programmgesteuert oder deklarativ einzuschließen. Die programmatische Transaktionsabgrenzung kann mithilfe der API erfolgen, die von der PlatformTransactionManagerSchnittstelle und ihren Unterklassen bereitgestellt wird. Andererseits verwendet die deklarative Transaktionsabgrenzung eine AOP-basierte Lösung (Aspect Oriented Programming). In diesem Artikel werden wir die deklarative Transaktionsabgrenzung untersuchen, da sie unter Verwendung der TransactionProxyFactoryBeanKlasse weniger aufdringlich und leicht zu verstehen ist . Die Transaktionsmanagementstrategie besteht in unserem Fall darin, die zu verwendenJtaTransactionManager, da wir mit mehreren Ressourcen umgehen müssen. Wenn es nur eine einzige Ressource gibt, gibt es abhängig von der zugrunde liegenden Technologie mehrere Auswahlmöglichkeiten, und alle implementieren die PlatformTransactionManagerSchnittstelle. Für den Ruhezustand kann beispielsweise die Verwendung ausgewählt werden, HibernateTransactionManagerund für die JDO-basierte Persistenz kann die Option verwendet werden JdoTransactionManager. Es gibt auch eine JmsTransactionManager, die nur für lokale Transaktionen gedacht ist.

Spring's transaction framework also provides the necessary tools for applications to define the transaction propagation behavior, transaction isolation and so forth. For declarative transaction management, the TransactionDefinition interface specifies the propagation behavior, which is very much similar to EJB CMT attributes. The TransactionAttribute interface allows the application to specify which exceptions will cause a rollback and which ones will be committed. These are the two crucial interfaces, which make the declarative transaction management very easy to use and configure, and we will see as we go through our use cases.

Asynchronous Message Consumption using Spring

Spring has always supported sending messages using JMS API via its JMS abstraction layer. It employs a callback mechanism, which consists of a message creator and a JMS template that actually sends the message created by the message creator.

Since the release of Spring 2.0, asynchronous message consumption has been made possible using the JMS API. Though Spring provides different message listener containers, for consuming the messages, the one that is mostly suited to both JEE and J2SE environments is the DefaultMessageListenerContainer (DMLC). The DefaultMessageListenerContainer extends the AbstractPollingMessageListenerContainer class and provides full support for JMS 1.1 API. It primarily uses the JMS synchronous receive calls( MessageConsumer.receive()) inside a loop and allows for transactional reception of messages. For J(2)SE environment, the stand-alone JTA implementations can be wired to use the Spring's JtaTransactionManager, which will be demonstrated in the following sections.

The JTA implementations

JBossTS

JBossTS, formerly known as Arjuna Transaction Service, comes with a very robust implementation, which supports both JTA and JTS API. JBossTS comes with a recovery service, which could be run as a separate process from your application processes. Unfortunately, it doesn't support out-of-the box integration with Spring, but it is easy to integrate as we will see in our exercise. Also there is no support for JMS resources, only database resources are supported.

Atomikos Transaction Essentials

Atomikos's JTA implementation has been open sourced very recently. The documentation and literature on the internet shows that it is a production quality implementation, which also supports recovery and some exotic features beyond the JTA API. Atomikos provides out of the box Spring integration along with some nice examples. Atomikos supports both database and JMS resources. It also provides support for pooled connections for both database and JMS resources.

Bitronix JTA

Bitronix's JTA implementation is fairly new and is still in beta. It also claims to support transaction recovery as good as or even better than some of the commercial products. Bitronix provides support for both database and JMS resources. Bitronix also provides connection pooling and session pooling out of the box.

XA Resources

JMS Resources

The JMS API specification does not require that a provider supports distributed transactions, but if the provider does, it should be done via the JTA XAResource API. So the provider should expose its JTA support using the XAConnectionFactory, XAConnection and XASession interfaces. Fortunately Apache's ActiveMQ provides the necessary implementation for handling XA transactions. Our project (see Resources section) also includes configuration files for using TIBCO EMS (JMS server from TIBCO) and one can notice that the configuration files require minimal changes when the providers are switched.

Database Resources

MySQL database provides an XA implementation and works only for their InnoDB engines. It also provides a decent JDBC driver, which supports the JTA/XA protocol. Though there are some restrictions placed on the usage of some XA features, for the purposes of the article, it is good enough.

The Environment

Setup for Databases:

The first database mydb1 will be used for use cases 1 and 2 and will have the following table:

mysql> use mydb1; Database changed mysql> select * from msgseq; +---------+-----------+-------+ | APPNAME | APPKEY | VALUE | +---------+-----------+-------+ | spring | execution | 13 | +---------+-----------+-------+ 1 row in set (0.00 sec)

The second database mydb2 will be used for use case 3 and will have the following table:

mysql> use mydb2; Database changed mysql> select * from msgseq; +---------+------------+-------+ | APPNAME | APPKEY | VALUE | +---------+------------+-------+ | spring | aaaaa | 15 | | spring | allocation | 13 | +---------+------------+-------+ 2 rows in set (0.00 sec)

Setup for JMS provider (for use case 2 and 3)

For creating a physical destination in ActiveMQ, do the following:

  1. Add the following destination to the activmq.xml file under the conf folder of ActiveMQ installation:
  2. Fügen Sie der Datei jndi.properties die folgende Codezeile hinzu , um den jndi-Namen für das Ziel einzuschließen, und stellen Sie sicher, dass sich die Datei im Klassenpfad befindet:queue.test.q1=test.q1

Anwendungsfall 1 - Aktualisieren von zwei Datenbanken in einer globalen Transaktion mit JBossTS

Abbildung 2: UseCase1 aktualisiert zwei Datenbanken in einer globalen Transaktion.

Nehmen wir an, dass unsere Anwendung eine Anforderung hat, bei der eine mit einem Ereignis verknüpfte Sequenznummer in zwei verschiedenen Datenbanken ( mydb1 und mydb2 ) innerhalb derselben Transaktionsarbeitseinheit wie in Abbildung 2 oben beibehalten werden muss . Um dies zu erreichen, schreiben wir eine einfache Methode in unsere POJO-Klasse, die die beiden Datenbanken aktualisiert.

Der Code für unsere EventHandlerPOJO-Klasse sieht wie folgt aus:

public void handleEvent(boolean fail) throws Exception { MessageSequenceDAO dao = (MessageSequenceDAO) springContext.getBean("sequenceDAO"); int value = 13; String app = "spring"; String appKey = "execution"; int upCnt = dao.updateSequence(value, app, appKey); log.debug(" sql updCnt->" + upCnt); if (springContext.containsBean("sequenceDAO2")) { // this is for use case 1 with JBossTS MessageSequenceDAO dao2 = (MessageSequenceDAO) springContext.getBean("sequenceDAO2"); appKey = "allocation"; upCnt = dao2.updateSequence(value, app, appKey); log.debug(" sql updCnt2->" + upCnt); } ... if (fail) { throw new RuntimeException("Simulating Rollback by throwing Exception !!"); } }