Java EE JTAガイド

Java EE JTAのガイド

1. 概要

Java Transaction API、より一般的にはJTA、is an API for managing transactions in Java.これにより、リソースに依存しない方法でトランザクションを開始、コミット、およびロールバックできます。

JTAの真の力は、複数のリソース(つまり、 データベース、メッセージングサービス)を1つのトランザクションで。

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

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

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

この抽象化がない場合、各リソースタイプの個々のAPIを処理する必要があります。

たとえば、JDBCリソースlike thisを処理する必要があります。 同様に、JMSリソースにはsimilar but incompatible modelが含まれる場合があります。

JTAを使用すると、manage multiple resources of different types in a consistent and coordinated mannerを実行できます。

JTAは、APIとして、transaction managersによって実装されるインターフェースとセマンティクスを定義します。 実装は、NarayanaBitronixなどのライブラリによって提供されます。

3. サンプルプロジェクトのセットアップ

サンプルアプリケーションは、銀行業務アプリケーションの非常にシンプルなバックエンドサービスです。 2つの異なるデータベースThese independent databases need to be coordinated upon transaction begin, commit or rollbackを使用するBankAccountService AuditServiceの2つのサービスがあります。

まず、サンプルプロジェクトでは、Spring Bootを使用して構成を簡素化します。


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



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

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

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

4. 宣言的トランザクションの区別

JTAでトランザクションを操作する最初の方法は、@Transactionalアノテーションを使用することです。 より詳細な説明と構成については、this articleを参照してください。

ファサードサービスメソッドexecuteTranser()@Transactional. の注釈を付けましょうこれはtransaction managerにトランザクションを開始するように指示します:

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

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

executeTransfer()が戻ると、the 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. 宣言型境界でのロールバック

メソッドの最後に、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.");
    }
}

unhandled RuntimeException past the first @Transactional will rollback the transactionto both databases。 事実上、残高よりも大きい金額で転送を実行すると、ロールバック:が発生します。

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トランザクションを制御する別の方法は、プログラムで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()を呼び出します。 それ以外の場合、the call to commit()commits the changes to both databases

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

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

6. 結論

この記事では、JTAが解決しようとする問題について説明しました。 コード例illustrate controlling transaction with annotations and programmaticallyには、単一のトランザクションで調整する必要がある2つのトランザクションリソースが含まれます。

いつものように、コード例はover on GitHubにあります。