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億ドルになる見通し

推薦する

検索エンジンのスパイダーにウェブサイトをクロールさせる方法

SEO 最適化に携わる人なら誰でも、ウェブサイトが検索エンジンで上位にランクインするには、スパイダー...

クラウドからクラウドインテリジェンスまで、Alibaba Cloud は何をリリースしましたか?

[51CTO.com からのオリジナル記事] 10 年前、ジャック・マー氏は「クラウドなしに未来はな...

フォーラム署名の外部リンクを作成するときに注意すべきことは何ですか?

毎日のウェブサイトのメンテナンス作業に加えて、すべてのウェブマスターは外部リンク作業も毎日行う必要が...

マイクロソフトの Mixed Reality がハノーバーメッセでその実力を披露

今週、ドイツのハノーバーメッセで、当社の顧客とパートナーが最もエキサイティングな複合現実アプリケーシ...

ターゲットキーワードのシステム最適化により、ウェブサイトの重みとランキングが効果的に向上します。

ターゲット キーワードとは、キーワード分析によって決定された Web サイトの「メイン」キーワードを...

SEO 実践者は限られたリソースをどのように合理的に割り当てることができるでしょうか?

検索エンジンマーケティングを通じてトラフィックをもたらすのは簡単ではないため、誰もがより高い費用対効...

20 年の歴史を持つ Java、クラウド ネイティブに対応_IT テクノロジー ウィークリー 621 号

過去 10 年間で、企業の IT 環境は、大規模なモノリシック アプリケーションから、より軽量なモジ...

2013 年を振り返って、Baidu、Alibaba、Tencent は今年何をしたでしょうか?

3人が一緒に集まることはめったにない(写真はインターネットから)馬化騰氏は次のように語った。「インタ...

tragicservers - 40% オフ/メモリアルデーセール/ロサンゼルス VPS

tragicservers は、米国の戦没将兵追悼記念日を記念して、KVM 仮想、1Gbps 帯域幅...

ウェブサイトのキーワードランキングを早く改善したいなら「分析」から始めましょう

ウェブサイトのキーワードランキングをすぐに向上させたい場合は、「分析」から始めましょう。分析とは何で...

Big Bird 草の根 SEO チュートリアル キーワード

この記事で説明の起源や、なぜ説明と呼ばれているのかを私に尋ねる必要はありません。私はそれを知りません...

モバイル検索は独立して発展するべきでしょうか、それとも二次的な機能になるべきでしょうか?

誰もが話題の合併や買収、製品のアップグレードについて話しているとき、かつてのデスクトップの覇者であっ...

ショックホスティングはどうですか?オーストラリアのシドニーのVPSの簡単なレビュー

ショックホスティングはどうですか? shockhosting には多数のデータセンターがあるため、今...

クラウドコンピューティングの急成長:地方自治体による盲目的な建設は非効率的な投資につながる可能性がある

[現在、中国では30以上の地方政府がクラウドコンピューティング産業計画を発表し、土地、税制、資金面で...

Websound - $3.49/KVM/512M メモリ/10gSSD/2T トラフィック/ロサンゼルス

websound.co.uk は、psychz.net のロサンゼルス データ センターでホストされ...