Java EE JTAガイド

1概要

より一般的にはJTAと呼ばれるJava Transaction APIは、Javaでトランザクションを管理するためのAPIです。** リソースに依存しない方法でトランザクションを開始、コミット、およびロールバックすることができます。

JTAの真の力は、単一のトランザクションで複数のリソース(データベース、メッセージングサービス)を管理できることにあります。

このチュートリアルでは、概念レベルでJTAを理解し、ビジネスコードがJTAと一般的にどのように相互作用するのかを確認します。

2ユニバーサルAPIと分散トランザクション

JTAは、トランザクション制御(開始、コミット、およびロールバック)をビジネスコードに抽象化します。

この抽象化がなければ、各リソースタイプの個々のAPIを扱う必要があります。

たとえば、JDBCリソースhttps://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html[このように]を処理する必要があります。同様に、JMSリソースにはhttps://docs.oracle.com/cd/E19798-01/821-1841/bncgh/index.html[類似しているが互換性のないモデル]があります。

JTAを使用すると、 さまざまな種類の複数のリソースを一貫した調整された方法で管理できます

APIとして、JTAは トランザクションマネージャ によって実装されるインタフェースとセマンティクスを定義します。実装はhttp://narayana.io[Narayana]やhttps://github.com/bitronix/btm[Bitronix]などのライブラリによって提供されます。

3サンプルプロジェクト設定

サンプルアプリケーションは、銀行業務アプリケーションの非常に単純なバックエンドサービスです。 2つのサービス、 __ BankAccountService AuditService__があります。 ** これらの独立したデータベースは、トランザクションの開始時、コミット時、またはロールバック時に調整する必要があります。

最初に、私たちのサンプルプロジェクトは設定を簡単にするためにSpring Bootを使います:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
</parent>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-bitronix</artifactId>
</dependency>

最後に、各テストメソッドの前に、 AUDIT LOG を空のデータで、データベース ACCOUNT__を2行で初期化します。

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

4宣言的トランザクション区分

JTAでトランザクションを処理する最初の方法は、https://docs.oracle.com/javaee/7/api/javax/transaction/Transactional.html[ @Transactional ]アノテーションを使用することです。より詳細な説明と設定についてはhttps://www.baeldung.com/transaction-configuration-with-jpa-and-spring[この記事]を参照してください。

ファサードサービスメソッド executeTranser() _ @ Transactionalのアノテーションを付けましょう。 これは トランザクションマネージャ にトランザクションの開始を指示します : _

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

ここでは、 executeTranser()メソッドは AccountService AuditServiceの2つの異なるサービスを呼び出します。これらのサービスは2つの異なるデータベースを使用します。

executeTransfer() が戻ったとき、 トランザクションマネージャ はそれがトランザクションの終わりであると認識し、両方のデータベースにコミットします :

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. 宣言的な区切りでロールバック

メソッドの最後に、 executeTransfer() が口座残高をチェックし、元の資金が足りない場合は RuntimeException をスローします。

@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.");
    }
}

最初の @ Transactional を過ぎた 未処理の RuntimeException は、トランザクションを 両方のデータベース にロールバックします。実際には、残高よりも大きい金額で振替を実行すると、ロールバックが発生します :

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プログラムによるトランザクションの区分

JTAトランザクションを制御するもう1つの方法は、https://javaee.github.io/javaee-spec/javadocs/javax/transaction/UserTransaction.html[ UserTransaction ]を使用してプログラムで制御することです。

それでは手動でトランザクションを処理するように executeTransfer() を修正しましょう:

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();
}

この例では、 begin() メソッドが新しいトランザクションを開始します。残高の検証に失敗した場合は、両方のデータベースをロールバックする rollback() を呼び出します。それ以外の場合、 commit() への呼び出しは両方のデータベースへの変更をコミットします

commit() rollback() の両方が現在のトランザクションを終了することに注意することが重要です。

最終的に、プログラムによる境界設定を使用すると、きめ細かいトランザクション制御の柔軟性が得られます。

6. 結論

この記事では、JTAが解決しようとしている問題について説明しました。コード例は、注釈を使ってトランザクションを制御する方法と、プログラムを使ってトランザクションを制御する方法を説明しています。

いつものように、コード例はhttps://github.com/eugenp/tutorials/tree/master/jta[over on GitHub]にあります。

前の投稿:JAX-RSクライアントwith Jersey
次の投稿:Tomcatルートにアプリケーションをデプロイする