学習・技術検証用デモ — 本システムは実際の銀行サービスではありません。

公開情報をもとに構築した教育目的のシステムです。実在する個人情報・口座情報・機密情報・認証情報等を入力しないでください。

ミニ銀行ACADEMY SYSTEM LEARNING
教材メニュー
学習進捗0 / 134
あと134項目
INTERMEDIATE · 中級COURSE 05 · JAVA / ENTERPRISE DEVELOPMENT

Transaction:Javaで境界を増やさない

既存教材のTransactionを残したまま、Java / Spring Bootから見たトランザクション境界を学びます。重要なのは@Transactionalを付けることではなく、どこが原子性を保証するかです。

06 / 11
JAVA / ENTERPRISE DEVELOPMENT · Java・エンタープライズ開発

Java / Spring Bootを入口に、金融システムのService・Repository・Transaction設計を読む。

先に学ぶ内容 — Prerequisite: ソフトウェア工学 / 銀行システム

学ぶこと — LEARNING GOALS

このページで学ぶこと

  • @Transactionalが効く範囲
  • HTTP越しRPCとDB transactionの違い
  • Payment Serviceに@Transactionalがない理由

体験15 · JAVA TRANSACTION(境界を増やさない)

Javaの注釈が、どこまで「取り消し」を保証するか確かめよう

まずは予想をひと言。本文を読んだ後、教材内の参照元で実際のJavaコードを確認します。

体験学習 · STEP 1 / 5
  1. 01
    まず予想する — PREDICT

    まずここだけ:思ったことをひと言書く

    Javaのコードに「トランザクション」と書けば、複数のシステムをまたぐ処理も1つの取り消し可能な単位になるでしょうか?

    正解でなくて大丈夫です。「〜だと思う」から始めてみましょう。

    ヒント1を見る

    電話で別の店舗に会計処理を頼んだ後、自分の机で作業を取り消したら、電話先の会計も戻るでしょうか?

    ヒント2を見る

    Javaのメソッドの開始と終了は、データベースの処理の開始と終了と必ず同じでしょうか?

    もっと詳しく:この問いで使う専門用語
    • @Transactionalは、主に同じDB接続上の処理をまとめるSpringの仕組みです。
    • 原子性は、全部成功するか全部なかったことになる性質です。
    • RPC(Remote Procedure Call)は、別プロセスの処理をHTTP経由で呼び出す仕組みです。
    • 補償処理は、確定済みの処理を打ち消すために追加で行う業務操作です。
まずはひと言だけで大丈夫です。

この後の本文を最後まで読んでから、実際に操作して確かめるステップへ進みます。

日常語で理解する — PLAIN LANGUAGE

まず、ここだけつかむ

1

トランザクションの目的は注釈を付けることではなく、途中状態を残さない正式な境界を決めることです。

2

Payment ServiceはDBへ直接接続せずHTTPでRPCを呼ぶため、Javaの@TransactionalはRPC先の変更を巻き戻せません。

3

現在は1回のRPC内部でPostgreSQLが原子性を保証するため、PaymentApplicationServiceへ意図的に@Transactionalを付けていません。

4

振込の途中でお金の記録の更新に失敗すると、その部分だけ元の状態に戻します。ただし、振込を依頼した記録自体は消えず、「失敗した」という記録として残ります。この考え方を「ロールバック」と呼びます。

順番に理解する — DIFFICULT PARTS, STEP BY STEP

難しいポイントを、順番にほどく

専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。

01

@Transactional

やさしく言うと

主に同じDB connection上の処理をまとめるSpringの仕組みです。

なぜ必要?

複数DB更新をcommitかrollbackへまとめるためです。

Minibankでは

Payment ServiceはHTTP RPCなので現在は使いません。

02

原子性

やさしく言うと

全部成功するか、全部なかったことになる性質です。

なぜ必要?

振込元だけ減るような半端な状態を防ぐためです。

Minibankでは

金融更新の仕訳・transfers・残高cache・成功audit(TRANSFER_COMPLETEDと内側の変更trigger)は、confirm_transfer_otpの内側の例外blockでまとめてrollbackします。先に保存済みのrequestは消えません。transfer_auth.consumeによる認可済みintentの消費(consumed_at)、request=failed、TRANSFER_FAILEDは内側のrollbackの対象外で、外側のDB transactionがcommitすると残ります。

03

補償処理

やさしく言うと

確定済み処理を打ち消す別の業務操作です。

なぜ必要?

複数システムの確定を後から単純rollbackできないためです。

Minibankでは

将来の分散処理では逆仕訳など明示的な操作が必要です。

発展 — ADVANCEDもっと詳しく確認する最初の学習では読まなくても問題ありません。仕組みをもっと深く理解したい場合に開いてください。

金融更新を戻す範囲と、失敗後に残す記録

金融更新の仕訳・transfers・残高cache・成功audit(TRANSFER_COMPLETEDと内側の変更trigger)は、confirm_transfer_otpの内側の例外blockでまとめてrollbackします。先に保存済みのrequestは消えません。transfer_auth.consumeによる認可済みintentの消費(consumed_at)、request=failed、TRANSFER_FAILEDは内側のrollbackの対象外で、外側のDB transactionがcommitすると残ります。

TOTPコードの正誤はDBではなくSupabase Authが判定します。DBはNext.jsのtransfer_mfa_broker経由で検証前に試行を予約し、その時点でintentのattemptsを加算します(累積5回まで、間隔30秒以上)。認証に失敗しても変わるのはintentとAuth側の状態だけで、requestはpending_otpのまま、金融データも変わりません。新しい認可がないままconfirmすると、transfer_auth.consumeが42501で拒否し、その呼出し全体がrollbackします。request=failedは金融更新の例外を捕捉した経路です。未捕捉例外・deferred constraint・commit自体の失敗なら、その呼出し全体の変更がrollbackするため、失敗記録の永続化を無条件には保証しません。

場面金融更新外側commit後の状態・event
依頼作成まだ記帳しないpending_otp、TRANSFER_OTP_REQUESTED(event名は従来のまま)。確認コードは生成しない
TOTPの誤入力・Auth検証失敗資金移動に入らない予約時点でintent.attemptsが加算済み(累積5回まで)。requestはpending_otp
新しい認可なしのconfirm(未認可・60秒超過・別session・内容変更)consumeが42501で拒否し、その呼出し全体をrollbackrequestはpending_otp、intentは未消費のまま
認可後の資金移動で捕捉した例外内側の仕訳・transfer・cache・成功auditをrollbackintent消費済み、request=failed、TRANSFER_FAILED
成功金融更新と成功auditを保存request=completed、transfer_id、intent消費済み
外側transaction/commit失敗その呼出し全体をrollbackその呼出しの失敗eventも保証しない。過去のcommit済み依頼とは別

Java経路の実装と稼働確認を分ける

request/confirmはPAYMENT_BACKENDで選択します。未設定・空文字・legacyはNext.js→PostgREST RPC、javaはNext.js→Java Payment Service→PostgREST RPCです。不正な値はエラー、Javaのtimeout・5xx・通信断でもlegacyへ自動fallbackしません。旧OTP再発行はこの切替の対象外で、v7のRPCは常に42501で拒否します。TOTPの認可準備はNext.jsの/api/v1/transfers/mfaが担います。コードの存在は確認済みですが、本番・PreviewでJavaが選択されているかはUNKNOWNです。

  1. 01Next.jsがPAYMENT_BACKENDを読む
  2. 02legacy:呼出者tokenでPostgRESTへ / java:Java Payment ServiceへHTTP
  3. 03request_transfer / confirm_transfer_otpでDB処理
  4. 04Javaの@TransactionalではHTTP先のcommitを取り消せない
  5. 05応答消失時は結果不明。別backendへ自動fallbackしない

トランザクションは『全部成功か全部失敗』の境界

金融更新の仕訳・transfer・残高cache・成功auditは原子的に扱います。ただし依頼全体を消すわけではありません。confirm_transfer_otpは金融更新の例外を内側blockで捕捉し、外側に認可済みintentの消費・request=failed・TRANSFER_FAILEDを残してoutcomeを返します。外側がcommitして初めて保存され、未捕捉例外やcommit失敗ではその呼出し全体がrollbackします。

Javaから見えるのはHTTP RPCの成功または失敗です。Javaメソッドの開始と終了が、そのままDB transactionの開始と終了になるわけではありません。

@Transactionalは魔法の分散トランザクションではない

処理@Transactionalでまとめられるか理由
同じDataSourceへのJDBC更新通常は可能Springが同じDB connectionのcommitを管理
PostgRESTへのHTTP呼び出しまとめられない別プロセス・別connectionで完結
外部通知やメールまとめられないDB以外の副作用
PostgreSQL RPC内部の複数更新RPC側で原子的関数を実行するDB transactionが管理

PaymentApplicationServiceに@Transactionalがない理由

createPayment()とconfirmOtp()は、それぞれ1回のRPC呼び出しへ委譲します。正式なtransaction境界はPostgreSQL側です。

Java側に形だけのtransactionを追加すると、原子性が広がったように誤解しやすくなります。現在の設計では、Javaをステートレスなオーケストレーターとして保つ方が境界を正確に表します。

将来、複数の外部処理をつなぐなら

  • どの記録がSystem of Recordか決める
  • DB commit後の通知にはoutboxなどを検討する
  • 再試行可能な操作へidempotencyを設ける
  • 補償処理は削除ではなく明示的な逆操作にする
  • 途中状態を保存し、監視と人手復旧を設計する

体験学習の続き · LEARNING LOOP

ここまで読めましたか?続けて確認してみましょう

トランザクション境界の考え方を読んだところで、教材内の参照元で実際のJavaコードを確認し、自分の言葉で説明してみます。

STEP 2〜5 / 5
  1. 02
    仕組みを確かめる — CONCEPT CHECK

    画面操作なしで、仕組みを確かめる

    この仕組みは、Mini Bankの画面には現れません。次のEvidenceとCode Trail、教材本文だけで理解を進めます。

  2. 03
    証拠を確認する — EVIDENCE

    何を見たか、1つずつ確かめる

    Evidence(判断の根拠となる記録)を確認したらチェックします。各項目の「何を見るか」を手がかりにしてください。

    画面を開けない項目は、Source Trail(教材の参照元)と教材の説明で確認できます。失敗を実際に起こす必要はありません。

  3. 04
    コードの処理経路を追う — CODE TRAIL

    コードを書く必要はありません

    ここでは「説明されている仕組みが、実際のMini Bankのどこに実装されているか」を確認します。まずは日本語の役割説明を読み、入力した依頼がどこへ渡るかをつかみましょう。コードの全文を読む必要はありません。

    まずJavaのユースケース層を見て、次にPostgreSQLへの接続方法を見ます。原子性の最終的な保証がどちらの層にあるかを確かめましょう。

    振込のユースケースを担うService。@Transactionalが無く、1回のRPC呼び出しへ委譲する構造を探します。

    services/payment-service/src/main/java/com/minibank/payment/application/PaymentApplicationService.java

    PostgreSQLへ直接接続せず、HTTP経由でRPCを呼ぶ境界。原子性の保証がどこにあるかを探します。

    services/payment-service/src/main/java/com/minibank/payment/repository/SupabaseRpcClient.java
    教材の参照元を確認する — Source Trail ↓
  4. 05
    自分の言葉で説明する — EXPLAIN

    LEVEL 1 — やさしい説明(まずここだけ)

    なぜPayment Serviceの主要な処理には@Transactionalが付けられていないのでしょうか? 短い2文を目安に説明してください。

    日常語だけで完了できます。専門用語の数や正誤を自動採点するものではありません。

    ヒント1を見る

    「@Transactional」と「HTTP越しのRPC」という2つの言葉を使って書いてみましょう。

    ヒント2を見る

    Javaが失敗を検知したときと、DBの処理が実際に確定したときの前後関係を考えてみましょう。

    もっと詳しく:LEVEL 2 — 専門用語を使った説明(任意)

    @Transactional、原子性、補償処理の3つを使って、複数システムをまたぐ処理をどう安全に保つか説明してみましょう。

    挑戦する場合は、上の説明欄に追記できます。追加しなくても完了できます。

    説明の確認ポイント — Checkpoint
    • Javaの注釈がHTTP先の処理を巻き戻せないと説明できる
    • 原子性の最終的な保証がPostgreSQL側にあると説明できる
予想5文字以上・証拠2項目・説明20文字以上で完了できます。

証拠の確認:0 / 2。LEVEL 2(専門用語での説明)は任意です。

入力はこのブラウザに保存されます。Academy教材の進捗とは別の学習メモです。

理解を確かめる — CHECK YOUR UNDERSTANDING

4問で理解チェック

答えを頭の中で考えてから、各項目を開いて確認してください。

Q1Javaのコードがあれば本番で稼働中ですか?
答え

request/confirmはPAYMENT_BACKENDで選択します。未設定・空文字・legacyはNext.js→PostgREST RPC、javaはNext.js→Java Payment Service→PostgREST RPCです。不正な値はエラー、Javaのtimeout・5xx・通信断でもlegacyへ自動fallbackしません。旧OTP再発行はこの切替の対象外で、v7のRPCは常に42501で拒否します。TOTPの認可準備はNext.jsの/api/v1/transfers/mfaが担います。コードの存在は確認済みですが、本番・PreviewでJavaが選択されているかはUNKNOWNです。

Q2Serviceへ@Transactionalを付ければHTTP先もrollbackしますか?
答え

しません。PostgREST側は別connection・別transactionです。

Q3失敗時に金融更新と依頼状態は同じように戻りますか?
答え

金融更新の仕訳・transfers・残高cache・成功audit(TRANSFER_COMPLETEDと内側の変更trigger)は、confirm_transfer_otpの内側の例外blockでまとめてrollbackします。先に保存済みのrequestは消えません。transfer_auth.consumeによる認可済みintentの消費(consumed_at)、request=failed、TRANSFER_FAILEDは内側のrollbackの対象外で、外側のDB transactionがcommitすると残ります。

Q4複数の外部処理を追加する時に必要なものは?
答え

状態保存、冪等性、再試行、補償、監視などを含む分散処理設計が必要です。

ここまで読めましたか?

完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。

教材の参照元 — SOURCE TRAIL

根拠と関連教材

CURRENT IMPLEMENTATIONは確認したrepository内の実装です。HISTORICAL IMPLEMENTATIONは導入・修正の履歴で、後続migrationによる再定義があり得ます。RELATED LESSONは教材への案内で、実装の一次Sourceではありません。ASSUMPTIONは仮定、UNKNOWNは未確認を表します。

確認基点: d02eae8914f39cbf06bfe422dc39c71886f82b3e / 2026-09-11。稼働環境への適用確認: UNKNOWN。パス実在の自動検査と、記述した技術的事実の確認は別です。

Implementation Status: IMPLEMENTED IN MINIBANK(この教材が扱うコードの範囲)

Runtime Verification Status: UNKNOWN(本番・Previewでの経路選択)

  • 現在の実装 — CURRENT IMPLEMENTATION — Primary Sourcesupabase/migrations/20260920074653_transfer_totp_intent_authorization.sqlpublic.request_transfer / public.confirm_transfer_otp / public.reissue_transfer_otp / public.prepare_transfer_mfa#128。依頼は確認コードを生成せずotp_plaintext=null。confirmはtransfer_auth.consumeで認可済みintentを消費してからexecute_transferを呼ぶ。reissueは常に42501。稼働DBへの適用はUNKNOWN。
  • 現在の実装 — CURRENT IMPLEMENTATION — Primary Sourcesupabase/migrations/20260920083426_require_fresh_totp_on_intent_session.sqlpublic.transfer_mfa_broker#129。intentのAuth session自身に、予約・検証より後のTOTP AMRを要求する最新broker定義。
  • 現在の実装 — CURRENT IMPLEMENTATION — Primary Sourcesupabase/migrations/20260920115556_scalar_journal_posting_atomic_cache.sqlpublic.execute_transfer最新定義。ledger.post_journal_entryの戻り値をscalar uuidで受ける。検証・lock・上限・冪等性は従来どおり。既存ACLは維持。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/app/api/v1/transfers/mfa/route.tsintent準備・challenge束縛・試行予約・認可完了のserver入口。TOTPコードは受け取らない。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/components/auth/TransferMfa.tsxブラウザがSupabase Authへchallenge/verifyを直接送る部分。成功の自己申告は認可にならない。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/app/api/v1/transfers/route.ts認証済みの旧直接振込入口はOTP_REQUIREDを返す。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/lib/errors/apiError.tsOTP_REQUIREDのHTTP statusは409。
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/mfa/intent-db.test.mjsintent束縛・回数/期限・単回消費・session検査のSQL受入test。Auth行はfixtureで、実Authの証明ではない。当日のPASSも意味しない。
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsrc/lib/config/demoOtpDisplay.ts#128以前のデモコード表示フラグ。v7ではDBがコードを返さず、Productionでは常にfalse。
  • 過去の実装 — HISTORICAL IMPLEMENTATIONscripts/regression/suites/transferOtp.mjs#128以前のデモOTP(誤入力・再発行・期限)の回帰test。v7の認可方式の根拠にしない。
  • 現在の実装 — CURRENT IMPLEMENTATION — Primary Sourcesrc/lib/config/paymentBackend.tsgetPaymentBackend切替の許容値・default・不正値拒否。稼働環境の値はUNKNOWN。
  • 現在の実装 — CURRENT IMPLEMENTATION — Primary Sourcesrc/services/TransferService.tsrequestTransfer / confirmTransferOtp / reissueTransferOtprequest/confirmのみbackend切替。残存createTransfer関数のコメントは現ACLの根拠にしない。
  • 現在の実装 — CURRENT IMPLEMENTATIONsrc/services/JavaPaymentBackend.tsJavaへのHTTP adapterとエラー処理。自動fallbackなし。
  • 現在の実装 — CURRENT IMPLEMENTATIONservices/payment-service/src/main/java/com/minibank/payment/application/PaymentApplicationService.java@Transactionalを付けない設計理由
  • 現在の実装 — CURRENT IMPLEMENTATIONservices/payment-service/src/main/java/com/minibank/payment/repository/SupabaseRpcClient.javaDBへ直接接続せずHTTP RPCを呼ぶ境界
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/実際の原子性を担うPostgreSQL関数