Leitfaden für Java EE JTA

Anleitung zu Java EE JTA

1. Überblick

Java Transaction API, besser bekannt als JTA,is an API for managing transactions in Java. Ermöglicht das ressourcenunabhängige Starten, Festschreiben und Zurücksetzen von Transaktionen.

Die wahre Stärke von JTA liegt in der Fähigkeit, mehrere Ressourcen zu verwalten (d. H. Datenbanken, Nachrichtendienste) in einer einzigen Transaktion.

In diesem Tutorial lernen wir JTA auf konzeptioneller Ebene kennen und sehen, wie Geschäftscode üblicherweise mit JTA interagiert.

2. Universelle API und verteilte Transaktion

JTA bietet eine Abstraktion über die Transaktionssteuerung (Starten, Festschreiben und Zurücksetzen) für Geschäftscode.

Ohne diese Abstraktion müssten wir uns mit den einzelnen APIs jedes Ressourcentyps befassen.

Zum Beispiel müssen wir uns mit der JDBC-Ressourcelike thisbefassen. Ebenso kann eine JMS-Ressourcesimilar but incompatible model haben.

Mit JTA können wirmanage multiple resources of different types in a consistent and coordinated manner.

Als API definiert JTA Schnittstellen und Semantik, die vontransaction managers implementiert werden sollen. Implementierungen werden von Bibliotheken wieNarayana undBitronix bereitgestellt.

3. Beispielprojekt-Setup

Die Beispielanwendung ist ein sehr einfacher Back-End-Service einer Bankanwendung. Wir haben zwei Dienste,BankAccountService andAuditService, die zwei verschiedene DatenbankenThese independent databases need to be coordinated upon transaction begin, commit or rollback verwenden.

Zunächst verwendet unser Beispielprojekt Spring Boot, um die Konfiguration zu vereinfachen:


    org.springframework.boot
    spring-boot-starter-parent
    2.0.4.RELEASE



    org.springframework.boot
    spring-boot-starter-jta-bitronix

Schließlich initialisieren wir vor jeder TestmethodeAUDIT_LOG mit leeren Daten und die DatenbankACCOUNT mit 2 Zeilen:

+-----------+----------------+
| ID        |  BALANCE       |
+-----------+----------------+
| a0000001  |  1000          |
| a0000002  |  2000          |
+-----------+----------------+

4. Deklarative Transaktionsabgrenzung

Die erste Möglichkeit, mit Transaktionen in JTA zu arbeiten, besteht in der Verwendung der Annotation@Transactional. Für eine ausführlichere Erklärung und Konfiguration siehethis article.

Kommentieren wir die FassadendienstmethodeexecuteTranser() mit@Transactional. . Dies weist dietransaction manager an, eine Transaktion: zu beginnen

@Transactional
public void executeTransfer(String fromAccontId, String toAccountId, BigDecimal amount) {
    bankAccountService.transfer(fromAccontId, toAccountId, amount);
    auditService.log(fromAccontId, toAccountId, amount);
    ...
}

Hier ruft die MethodeexecuteTranser() 2 verschiedene Dienste auf,AccountService undAuditService.. Diese Dienste verwenden 2 verschiedene Datenbanken.

WennexecuteTransfer() zurückkehrt, wirdthe transaction manager recognizes that it is the end of the transaction and will commit to both databases:

tellerService.executeTransfer("a0000001", "a0000002", BigDecimal.valueOf(500));
assertThat(accountService.balanceOf("a0000001"))
  .isEqualByComparingTo(BigDecimal.valueOf(500));
assertThat(accountService.balanceOf("a0000002"))
  .isEqualByComparingTo(BigDecimal.valueOf(2500));

TransferLog lastTransferLog = auditService
  .lastTransferLog();
assertThat(lastTransferLog)
  .isNotNull();
assertThat(lastTransferLog.getFromAccountId())
  .isEqualTo("a0000001");
assertThat(lastTransferLog.getToAccountId())
  .isEqualTo("a0000002");
assertThat(lastTransferLog.getAmount())
  .isEqualByComparingTo(BigDecimal.valueOf(500));

4.1. Rollback in deklarativer Abgrenzung

Am Ende der Methode überprüftexecuteTransfer() den Kontostand und wirftRuntimeException, wenn der Quellfonds nicht ausreicht:

@Transactional
public void executeTransfer(String fromAccontId, String toAccountId, BigDecimal amount) {
    bankAccountService.transfer(fromAccontId, toAccountId, amount);
    auditService.log(fromAccontId, toAccountId, amount);
    BigDecimal balance = bankAccountService.balanceOf(fromAccontId);
    if(balance.compareTo(BigDecimal.ZERO) < 0) {
        throw new RuntimeException("Insufficient fund.");
    }
}

Einunhandled RuntimeException past the first @Transactional will rollback the transactionto both databases. Tatsächlich führt das Ausführen einer Übertragung mit einem Betrag, der größer als der Kontostand ist, zu einem Rollback von:

assertThatThrownBy(() -> {
    tellerService.executeTransfer("a0000002", "a0000001", BigDecimal.valueOf(10000));
}).hasMessage("Insufficient fund.");

assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(1000));
assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2000));
assertThat(auditServie.lastTransferLog()).isNull();

5. Programmatische Transaktionsabgrenzung

Eine andere Möglichkeit, die JTA-Transaktion zu steuern, besteht programmgesteuert überUserTransaction.

Ändern wir nunexecuteTransfer(), um die Transaktion manuell abzuwickeln:

userTransaction.begin();

bankAccountService.transfer(fromAccontId, toAccountId, amount);
auditService.log(fromAccontId, toAccountId, amount);
BigDecimal balance = bankAccountService.balanceOf(fromAccontId);
if(balance.compareTo(BigDecimal.ZERO) < 0) {
    userTransaction.rollback();
    throw new RuntimeException("Insufficient fund.");
} else {
    userTransaction.commit();
}

In unserem Beispiel startet die Methodebegin()eine neue Transaktion. Wenn die Kontostandsüberprüfung fehlschlägt, rufen wirrollback() auf, wodurch beide Datenbanken zurückgesetzt werden. Ansonstenthe call to commit()commits the changes to both databases.

Es ist wichtig zu beachten, dass sowohlcommit() als auchrollback() die aktuelle Transaktion beenden.

Letztendlich gibt uns die programmatische Abgrenzung die Flexibilität einer differenzierten Transaktionssteuerung.

6. Fazit

In diesem Artikel haben wir das Problem behandelt, das JTA zu lösen versucht. Der Code zeigtillustrate controlling transaction with annotations and programmatically mit zwei Transaktionsressourcen, die in einer einzelnen Transaktion koordiniert werden müssen.

Wie üblich kann das Codebeispielover on GitHub gefunden werden.