質問: DataSorceUtil.getConnection(jdbcTemplate.DateSouce) Bクラス側でcommitまで完了させ、その後、 API側でロールバックを完了させようとすると、 exceptionが発生してしまう状態です。 なお、APIとBクラスがアクセスするDBはORACLE11gで、同じDB内の別テーブルをそれぞれが更新します(スキーマは同一です) 【フロー図】 API実行 ↓ APIでトランザクション開始 ↓ APIがBクラス(共通処理的な)呼出 ↓ Bクラスでトランザクション開始 ↓ Bクラスがコミット実行。Bクラス動作終了。 ↓ APIに処理が戻ってきた後、エラー処理でロールバック実行 ↓ APIでexception発生 【実現したいこと】 ・APIはロールバックを行ってテーブル更新させたくないが、Bクラスはコミットまで完了させたい (クラス単位で、途中でトランザクション終了させることなく、別々にトランザクション管理をしたい) 【質問】 ・複数のトランザクション管理を明示的に行う事は可能なのでしょうか? ・お互いのトランザクション管理が干渉しないようにする為にはどういった実装が理想でしょうか? ・複数のトランザクション管理について、参考になるページなどありましたらご教示頂ければ幸いです。 以上、よろしくお願い致します。
回答: ### 方針 よくあるのは、業務トランザクションの成功/失敗にかかわず、ログだけはデータベースに書いておきたい、という要件です。 Springのクラスははっきりと把握していないので、JTAを適用する一般論として書きます。TransactionManagerのAPIを使います。TransactionManagerはSpringのAPIから取得してください。同じことが、EJBのコンテナ管理トランザクションで、トランザクション属性をRequiresNewにすると、できます。 API実行 ↓ APIでトランザクション開始 ↓ %%%%%%%%%%%%%%%%%%%%%%%%%%% トランザクションをsuspend() %%%%%%%%%%%%%%%%%%%%%%%%%%% ↓ APIがBクラス(共通処理的な)呼出 ↓ Bクラスでトランザクション開始 ↓ Bクラスがコミット実行。Bクラス動作終了。 ↓ %%%%%%%%%%%%%%%%%%%%%%%%%%% トランザクションをresume() %%%%%%%%%%%%%%%%%%%%%%%%%%% ↓ APIに処理が戻ってきた後、エラー処理でロールバック実行 ↓ APIでexception発生 B は外側のトランザクションのことを知らなくても良いので、自分のトランザクションだけに注力でます。 ### Java EE API docs.oracle.com/javaee/7/api/index.html?javax/transaction/TransactionManager.html **トランザクションの一時停止** TransactionManager.suspend() > > Suspend the transaction currently associated with the calling thread and return a Transaction object that represents the transaction context being suspended. > > > **トランザクションの再開** TransactionManager.resume(Transaction tobj) > > Resume the transaction context association of the calling thread with the transaction represented by the supplied Transaction object. > > > ### B のトランザクション管理 **トランザクションの開始** JTAのAPIは、TransactionManager.begin()になりますが、Springはどうするのですか?新なトランザクションのために、AOPのProxyがここに必要でしょうか。 **Connectionの取得** 外側のトランザクションとは別のトランザクションになるので、新たにConnectionを取得する必要があります(XAResourceのトランザクションへの参加)。データソースはXAでなければなりません。 **コミット** **ロールバック** Springのやりかたでやってください。ただしBのトランザクションをロールバックさせるために例外を投げるのであれば、B のスコープ内で処理する必要があります。外側に伝播させたいのなら、B を一度ロールバックしてから、伝えてください。 **その他** 外側のトランザクションで更新するレコードを、B のトランザクションで更新しないでください。別トランザクションです。排他制御され、B の処理が止まります。 **代替案** JTAを使わないトランザクション管理。XAではないデータソースを別に用意。Connectionを取得して、Connection.commit()/.rollback()処理を行う。TransactionManagerを使わないやり方です。 以上、ひとつの解決策として提案します。 ### B のトランザクション管理(追記) トランザクションのsuspend/resumeは、Bでやる方が開発に影響がでないでしょう。TransactionManager.status()でトランザクションの状態を取得できます。トランザクションがあればsuspend/resumeする。そして自分のトランザクションを開始する。 ### 参考書の情報(追記) 技術評論社の『改訂新版 Spring入門』を買いました。第4章のトランザクションマネージャの解説を読んでみたところ、上記のように自分でカスタムコードを書く必要がないことがわかりました。うんと簡単です。 方法 1. トランザクションマネージャを使う。 2. 外側のトランザクションを開始する。 トランザクションの伝搬属性を、PROPERGATION_REQUIREDにする。 3. 内側のトランザクションを開始する。(別に開始してください) トランザクションの伝搬属性を、PROPERGATION_REQUIRES_NEWにする。 PROPERGATION_REQUIRES_NEWを指定することで、suspend, resumeが自動的に行われる。 (動作させて確認したわけではありません。(それじゃダメじゃん 。はい、時間が取れればやってみます。)) 問題が出そうなところは以下の点です。 トランザクションマネージャーの違いによる動作の違い 同様に、DAOの違いによる動作の違い データソースの定義による動作の違い 宣言型トランザクションの設定の間違い どなたか、親切な方、コードサンプルをポストしてください。