Spring Boot 2.x 基本チュートリアル: JTA を使用した分散トランザクションの実装

Spring Boot 2.x 基本チュートリアル: JTA を使用した分散トランザクションの実装

[[380215]]

Spring Boot プロジェクトでは、複数のデータ ソースに接続するのが非常に一般的です。

複数のデータ ソースを使用する場合、データ ソース A とデータ ソース B への更新をトランザクション的に行うという特別なシナリオも発生します。このような例は非常に一般的です。たとえば、注文データベースに注文レコードを作成し、製品データベースの製品在庫を減算するなどです。在庫減算が失敗した場合は、注文の作成もロールバックする必要があります。

これら 2 つのデータが同じデータベース内にある場合は、先に紹介したトランザクション管理を通じて簡単に解決できます。ただし、2 つの操作が異なるデータベースで行われる場合、これは不可能です。

この記事では、この問題の解決策である JTA トランザクションを紹介します。

JTAとは

JTA、正式名称: Java Transaction API。 JTA トランザクションは、JDBC トランザクションよりも強力です。 JTA トランザクションには複数の参加者が存在する可能性がありますが、JDBC トランザクションは単一のデータベース接続に限定されます。したがって、複数のデータベースを同時に操作する場合、JTA トランザクションを使用すると、JDBC トランザクションの欠点を補うことができます。

Spring Boot 2.x では、次の 2 つの JTA 実装が統合されています。

Atomikos: spring-boot-starter-jta-atomikos 依存関係を導入することで使用可能

Bitronix: spring-boot-starter-jta-bitronix依存関係を導入することで使用可能

Bitronix は Spring Boot 2.3.0 以降では非推奨となっているため、以下のハンズオンセッションでは Atomikos を例に JTA の使い方を紹介します。

自分で試してみましょう

Spring Boot で JTA を使用して、複数のデータ ソースでトランザクション管理を実装する方法を見てみましょう。

準備

  • ここでは、最も基本的な JdbcTemplate を使用してデータ アクセスを実装します。そのため、JdbcTemplate を使用して複数のデータ ソースを構成する方法がわからない場合は、まず JdbcTemplate の複数のデータ ソース構成を確認することをお勧めします。

シーン設定:

  • test1とtest2という2つのライブラリがあるとします。
  • 両方のデータベースにUserテーブルがあり、これら2つのテーブルのデータが一致していることを期待しています。
  • 両方のテーブルにすでにレコードが存在するものとします: name=aaa, age=30; 2 つのテーブルのデータは一致しているため、更新が必要な場合は、両方のデータベースの User テーブルを更新する必要があり、両方とも成功するか、両方とも失敗します。

操作の詳細

pom.xml に JTA 実装 Atomikos Starter を追加します。

  1. <依存関係>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-jta-atomikos</artifactId>
  4. </依存関係>

application.properties 構成ファイルで 2 つの test1 および test2 データ ソースを構成します。

  1. spring.jta.enabled = 
  2.  
  3. spring.jta.atomikos.datasource.primary .xa -properties.url=jdbc:mysql://localhost:3306/test1
  4. spring.jta.atomikos.datasource.primary.xa -プロパティ.user =ルート
  5. spring.jta.atomikos.datasource.primary.xa -プロパティ.パスワード= 12345678
  6. spring.jta.atomikos.datasource.primary .xa -データソースクラス=com.mysql.cj.jdbc.MysqlXADataSource
  7. spring.jta.atomikos.datasource.primary.unique -リソース-名前= test1
  8. spring.jta.atomikos.datasource.primary.max -プール-サイズ= 25
  9. spring.jta.atomikos.datasource.primary.min -プール-サイズ= 3
  10. spring.jta.atomikos.datasource.primary.max -有効期間=20000
  11. spring.jta.atomikos.datasource.primary.borrow-接続-タイムアウト=10000
  12.  
  13. spring.jta.atomikos.datasource.secondary.xa-properties.url=jdbc:mysql://localhost:3306/test2
  14. spring.jta.atomikos.datasource.secondary.xa -properties.user =root
  15. spring.jta.atomikos.datasource.secondary.xa- properties.password =12345678
  16. spring.jta.atomikos.datasource.secondary.xa-データソースクラス=com.mysql.cj.jdbc.MysqlXADataSource
  17. spring.jta.atomikos.datasource.secondary です。一意のリソース=test2
  18. spring.jta.atomikos.datasource.secondary です。最大プールサイズ= 25
  19. spring.jta.atomikos.datasource.secondary です。最小プールサイズ=3
  20. spring.jta.atomikos.datasource.secondary.max -有効期間 = 20000
  21. spring.jta.atomikos.datasource.secondary.borrow-接続-timeout=10000

マルチデータソース構成クラスを作成する

  1. @構成
  2. パブリッククラス DataSourceConfiguration {
  3.  
  4. @主要な 
  5. @ビーン
  6. @ConfigurationProperties(プレフィックス = "spring.jta.atomikos.datasource.primary" )
  7. パブリックデータソースプライマリデータソース() {
  8. 新しい AtomikosDataSourceBean()を返します
  9. }
  10.  
  11. @ビーン
  12. @ConfigurationProperties(プレフィックス = "spring.jta.atomikos.datasource.secondary" )
  13. パブリックデータソースセカンダリデータソース() {
  14. 新しい AtomikosDataSourceBean()を返します
  15. }
  16.  
  17. @ビーン
  18. パブリックJdbcTemplate primaryJdbcTemplate(@Qualifier( "primaryDataSource" ) データソース primaryDataSource) {
  19. 新しい JdbcTemplate(primaryDataSource)を返します
  20. }
  21.  
  22. @ビーン
  23. パブリックJdbcTemplate secondaryJdbcTemplate(@Qualifier( "secondaryDataSource" ) データソース secondaryDataSource) {
  24. 新しい JdbcTemplate(secondaryDataSource)を返します
  25. }
  26.  
  27. }

ここでの異なる構成に加えて、DataSource は AtomikosDataSourceBean も使用することに注意してください。複数のデータ ソースの以前の構成で使用された構成クラスと実装クラスの違いに注意してください。

2 つの異なる状況をシミュレートするサービス実装を作成します。

  1. @サービス
  2. パブリッククラスTestService{
  3.  
  4. プライベート JdbcTemplate primaryJdbcTemplate;
  5. プライベート JdbcTemplate セカンダリ JdbcTemplate;
  6.  
  7. パブリックTestService(JdbcTemplate プライマリ JdbcTemplate、JdbcTemplate セカンダリ JdbcTemplate) {
  8. プライマリJdbcテンプレートを作成します。
  9. this.secondaryJdbcTemplate = セカンダリJdbcTemplate;
  10. }
  11.  
  12. @トランザクション
  13. パブリックvoidtx() {
  14. // test1ライブラリのデータを変更する
  15. プライマリJdbcテンプレート。更新( "更新ユーザー set age = ? where name = ?" 、 30、 "aaa" );
  16. // test2ライブラリのデータを変更する
  17. セカンダリJdbcテンプレート。更新( "更新ユーザー set age = ? where name = ?" 、 30、 "aaa" );
  18. }
  19.  
  20. @トランザクション
  21. パブリックボイドtx2() {
  22. // test1ライブラリのデータを変更する
  23. プライマリJdbcテンプレート。更新( "更新ユーザー set age = ? where name = ?" 、 40、 "aaa" );
  24. // シミュレーション: test2 ライブラリを変更する前に例外をスローします
  25. 新しい RuntimeException() をスローします。
  26. }
  27.  
  28. }

ここで、tx 関数は 2 つのステートメントの更新操作であり、通常は成功します。 tx2 関数では、例外を人為的に作成します。この例外は、test1 データベースのデータが更新された後に生成されます。このようにして、test1 の更新が成功したかどうか、また JTA を使用してロールバックできるかどうかをテストできます。

テストクラスを作成し、テストケースを書く

  1. @SpringBootTest(クラス = Chapter312Application.class)
  2. パブリッククラス Chapter312ApplicationTests {
  3.  
  4. オートワイヤード
  5. 保護された Jdbc テンプレート primaryJdbcTemplate;
  6. オートワイヤード
  7. 保護された JdbcTemplate セカンダリ JdbcTemplate;
  8.  
  9. オートワイヤード
  10. プライベート TestService testService;
  11.  
  12. @テスト
  13. パブリックvoid test1()は例外をスローします{
  14. // 正しい更新状況
  15. テストサービス.tx();
  16. Assertions.assertEquals(30, primaryJdbcTemplate.queryForObject( "select age from user where name=?" , Integer .class, "aaa" ));
  17. Assertions.assertEquals(30, secondaryJdbcTemplate.queryForObject( "select age from user where name=?" , Integer .class, "aaa" ));
  18. }
  19.  
  20. @テスト
  21. パブリックvoid test2() は例外をスローします {
  22. // 更新に失敗しました
  23. 試す {
  24. テストサービス.tx2();
  25. } キャッチ (例外 e) {
  26. e.printStackTrace();
  27. ついに
  28. // 部分的な更新に失敗しました。test1 の更新をロールバックする必要があります
  29. Assertions.assertEquals(30, primaryJdbcTemplate.queryForObject( "select age from user where name=?" , Integer .class, "aaa" ));
  30. Assertions.assertEquals(30, secondaryJdbcTemplate.queryForObject( "select age from user where name=?" , Integer .class, "aaa" ));
  31. }
  32. }
  33.  
  34. }

ここに 2 つのテストケースがあります:

  • テスト 1: 意図的な例外がないため、2 つのデータベースの更新は期待どおりに成功します。したがって、2 つのデータは name=aaa に従ってチェックされ、年齢が 30 に更新されているかどうかが確認されます。
  • test2: tx2 関数は、test1 の name=aaa のユーザーの年齢を 40 に更新し、例外をスローします。 JTA トランザクションが有効になると、経過時間は 30 にロールバックされます。したがって、ここでのチェックは、2 つのデータベース内の aaa ユーザーの経過時間が 30 である必要があるということです。つまり、JTA トランザクションが有効になり、test1 および test2 データベース内のユーザー テーブル データがダーティ データを作成せずに一貫して更新されることが保証されます。

テスト検証

上記の単体テストを実行します。

起動フェーズ中のログを見ると、次のような Atomikos 初期化ログ出力が確認できます。

  1. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.default_max_wait_time_on_shutdown = 9223372036854775807  
  2. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.allow_subtransactions = true  
  3. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.recovery_delay = 10000  
  4. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.automatic_resource_registration = true  
  5. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.oltp_max_retries = 5  
  6. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.client_demarcation = false  
  7. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.threaded_2pc = false  
  8. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.serial_jta_transactions = true  
  9. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.log_base_dir = /Users/didi/Documents/GitHub/SpringBoot-Learning/2.x/chapter3-12/transaction-logs  
  10. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.rmi_export_class = none  
  11. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.max_actives = 50  
  12. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.checkpoint_interval = 500  
  13. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.enable_logging = true  
  14. 2021-02-02 19:00:36.145 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.log_base_name = tmlog  
  15. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.max_timeout = 300000  
  16. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.trust_client_tm = false  
  17. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: java.naming.factory.initial = com.sun.jndi.rmi.registry.RegistryContextFactory  
  18. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.tm_unique_name = 127.0.0.1.tm  
  19. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.forget_orphaned_log_entries_delay = 86400000  
  20. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.oltp_retry_interval = 10000  
  21. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: java.naming.provider.url = rmi://localhost:1099  
  22. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.force_shutdown_on_vm_exit = false  
  23. 2021-02-02 19:00:36.146 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: USING: com.atomikos.icatch.default_jta_timeout = 10000  
  24. 2021-02-02 19:00:36.147 INFO 8868 --- [main] caicatch.provider.imp.AssemblerImp: デフォルト (ローカル) のログ記録とリカバリを使用しています...  
  25. 2021-02-02 19:00:36.184 INFO 8868 --- [main] cadxa.XATransactionalResource: test1: XAResource を更新しました 
  26. 2021-02-02 19:00:36.203 INFO 8868 --- [メイン] cadxa.XATransactionalResource  

同時に、トランザクションに関するログ情報は、transaction-logs ディレクトリでも見つかります。

  1. { "id" : "127.0.0.1.tm161226409083100001" "wasCommitted" : true "participants" :[{ "uri" : "127.0.0.1.tm1" "state" : "COMMITTING" "expires" :1612264100801、 "resourceName" : "test1" },{ "uri" : "127.0.0.1.tm2" "state" : "COMMITTING" "expires" :1612264100801、 "resourceName" : "test2" }]}
  2. { "id" : "127.0.0.1.tm161226409083100001" "wasCommitted" : true "participants" :[{ "uri" : "127.0.0.1.tm1" "state" : "TERMINATED" "expires" :1612264100804、 "resourceName" : "test1" },{ "uri" : "127.0.0.1.tm2" "state" : "TERMINATED" "expires" :1612264100804、 "resourceName" : "test2" }]}
  3. { "id" : "127.0.0.1.tm161226409092800002" "wasCommitted" : false "participants" :[{ "uri" : "127.0.0.1.tm3" "state" : "TERMINATED" "expires" :1612264100832、 "resourceName" : "test1" }]}

コードサンプル

この記事に関連する例については、次のリポジトリの chapter3-12 ディレクトリを参照してください。

Github: https://github.com/dyc87112/SpringBoot-Learning/

gitee: https://gitee.com/didispace/SpringBoot-Learning/

<<:  クラウド コンピューティングはビジネスとソフトウェア アーキテクチャに革命をもたらすことができるでしょうか?

>>:  Canalysの最新レポートによると、2020年の世界のクラウドインフラサービス支出は合計1,420億ドルになる見通し

推薦する

Webmaster.com の毎日のレポート: 共同購入ウェブサイトが生死の分かれ目に直面している; Xiaomi が監査に合格

1. 年末に資本チェーンは大きな試練に直面する。春節は共同購入サイトの生き残りを左右する分岐点となる...

あらゆる業界で差別化を洗練させる9つの方法

多くのビジネスオーナーは、ウェブサイトで次のような見解をよく目にするかもしれません。企業やウェブサイ...

chicagovps-8g メモリ スマート サーバー シンプル評価

先日、「chicagovps-15ドルのE3-1240v3/8gメモリ/200gハードディスク/10...

4人の大学生が食べ物や飲み物を配達するウェブサイトを立ち上げました。彼らのビジネスのきっかけはTaobaoでした。

■ 江東氏(中央)と彼の仲間たちクラスメイトが寮にこもって外出をせず、それでも食べ物を注文したい場合...

なぜあなたのウェブサイトの記事は掲載されていないのですか?王奇は100%明確な分析を持っています

1. 概要現在、多くのウェブマスターのウェブサイトは適切にインクルードされていないか、ウェブサイトの...

「疫病との戦い」から都市統治の近代化まで、デジタルイノベーションが成都ハイテク区をよりスマートにする

中国政府による感染予防・抑制の常態化への対応として、先ほど終了した2回の会議では、感染予防・抑制に関...

地域不動産ウェブサイトの3つの運用リスクの簡単な分析

地方不動産ネットワークの運営ターゲットは非常に明確で、つまり、住宅を購入して装飾する準備をしている一...

Baidu ライブラリを使用して外部リンクを作成する際のヒントと誤解

インターネット上で図書館の運営に関する経験の共有をよく見かけます。経験の多くは非常に優れていますが、...

クラウド コンピューティングはヘルスケアにおいてどのような役割を果たすのでしょうか?

ヘルスケアは、人工知能やクラウド コンピューティングなどの最新テクノロジーの助けを借りて、患者により...

オンラインマーケティングトレーニングで学ぶべき6つの最も重要なマーケティング分野

最近、友人から私のマーケティング本「デートはこうすればもっと楽しくなる」がいつ出版されるのかと聞かれ...

ライブストリーミングについて知っておくべき37の統計

疫病によりライブストリーミングの輪は完全に崩壊し、ライブストリーミングは現在では主要アプリの基本機能...

VMwareは持続可能なイノベーションを推進し続けます

暗号通貨、機械学習、ビッグデータなどの計算集約型テクノロジーの急速な導入により、データセンターの電力...

「チャイナパートナーズ」が優れたウェブサイトの運営方法を教えてくれる

数日前、映画館に行って、最近公開された感動的な映画「チャイニーズ・パートナーズ」を見ました。この映画...

企業サイトのSEOの隠れたコストを説明する

多くの企業は、安価であるという理由で SEO を選択します。表面的には、SEO では高額な広告費を支...