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

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

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

Concurrency:同時リクエストを正しく扱う

Spring Bootは複数のHTTPリクエストを同時に処理します。Javaサービスをステートレスに保ち、残高競合の最終判断をPostgreSQLへ委ねる理由を学びます。

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

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

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

学ぶこと — LEARNING GOALS

このページで学ぶこと

  • Webアプリケーションの同時実行
  • singleton Beanと共有可変状態
  • DBロックとJava側事前確認の限界

体験38 · JAVA CONCURRENCY(並行を正しく扱う)

複数のリクエストが同時に届いた時、Payment Serviceは毎回新しいインスタンスを作るか確かめよう

まずは予想をひと言。本文を読んだ後、教材内の参照元で実際のServiceと相関IDの実装を確認します。

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

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

    複数のリクエストが同時に届いた時、Payment Serviceは毎回新しいServiceインスタンスを作ると思いますか?

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

    ヒント1を見る

    SpringのControllerやServiceは、既定でいくつ作られるでしょうか?

    ヒント2を見る

    同じインスタンスを複数のリクエストが使う時、何に注意すべきでしょうか?

    もっと詳しく:この問いで使う専門用語
    • singleton Beanは、アプリ内で通常1つだけ作られ共有される部品です。
    • ステートレスは、リクエスト固有の状態を共有フィールドへ残さない設計です。
    • row lockは、同じDB行の競合更新へ順番を付ける仕組みです。
まずはひと言だけで大丈夫です。

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

日常語で理解する — PLAIN LANGUAGE

まず、ここだけつかむ

1

Spring Bootは複数リクエストを同時に処理し、同じsingleton Serviceが複数スレッドから呼ばれます。

2

Payment Serviceはリクエスト固有値をBeanのフィールドへ保存せず、メソッド内だけで扱うステートレスな構成です。

3

残高の競合はJavaの事前確認では解決できません。更新と同じDB transactionでlockし、最新状態を再確認します。

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

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

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

01

singleton Bean

やさしく言うと

アプリ内で通常1つだけ作られ共有される部品です。

なぜ必要?

軽量ですが、可変状態を持つとリクエスト間で混線するためです。

Minibankでは

ControllerやServiceは引数とローカル変数を使います。

02

ステートレス

やさしく言うと

リクエスト固有の状態を共有フィールドへ残さない設計です。

なぜ必要?

並行処理と複数インスタンスで結果を一貫させるためです。

Minibankでは

amountやtokenをServiceフィールドへ保存しません。

03

row lock

やさしく言うと

同じDB行の競合更新へ順番を付ける仕組みです。

なぜ必要?

古い残高を基に複数送金が確定するのを防ぐためです。

Minibankでは

既存PostgreSQL RPCが更新地点でlockします。

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

同じServiceインスタンスが複数リクエストから使われる

SpringのController、Service、Componentは既定でsingletonです。複数スレッドが同じインスタンスのメソッドを同時に呼びます。

PaymentApplicationServiceはリクエスト固有の金額やtokenをフィールドへ保存せず、メソッド引数とローカル変数だけで扱います。このステートレス性がスレッド間の混線を防ぎます。

残高を先に読んでも安全にはならない

Java側で残高を表示・事前確認しても、RPC実行までに状態は変わり得ます。正式な検査は、更新と同じDB transaction内で行う必要があります。

  1. 01リクエストAが残高10,000円を読む
  2. 02リクエストBも同じ残高を読む
  3. 03Aが8,000円の振込を実行
  4. 04Bも8,000円を実行しようとする
  5. 05最終更新地点でlockと残高再確認がなければ過剰送金になる

排他制御の責任は既存RPCに残す

層できることできないこと
Controller入力形式を拒否同時送金間の残高を直列化
Service処理順序を表す別HTTP接続のDB lockを共有
Gateway同じtokenでRPCを呼ぶ業務lock規則を置き換える
PostgreSQL RPC対象行をlockし同じtransactionで再確認Javaプロセス内の共有状態管理

相関IDは並行処理を追跡するためにある

RequestIdFilterはX-Request-Idを引き継ぐかUUIDを生成し、request属性、レスポンスヘッダー、MDCログへ設定します。

同時に大量のリクエストが動いても、requestIdで1件の経路を追えます。ただし外部入力のrequestIdを無制限に信頼する場合は、長さや文字種、ログ注入への対策も検討対象です。

体験学習の続き · LEARNING LOOP

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

singleton Beanとステートレスの考え方を読んだところで、教材内の参照元で実際のServiceと相関IDの実装を確認し、自分の言葉で説明してみます。

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のどこに実装されているか」を確認します。まずは日本語の役割説明を読み、入力した依頼がどこへ渡るかをつかみましょう。コードの全文を読む必要はありません。

    まずServiceが可変状態を持たないことを見て、次に並行リクエストを追跡する相関IDの実装を見ます。

    共有可変状態を持たないService。

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

    並行リクエストを追う相関ID。

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

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

    なぜPaymentApplicationServiceのようなsingleton Beanで、リクエスト固有の値をインスタンスのフィールドへ保存してはいけないのでしょうか? 短い2文を目安に説明してください。

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

    ヒント1を見る

    「ステートレス」という言葉を使って書いてみましょう。

    ヒント2を見る

    リクエスト固有の値をどこに置くべきか考えてみましょう。

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

    singleton Bean、ステートレス、row lockの3つを使って、並行リクエストを安全に扱う設計を説明してみましょう。

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

    説明の確認ポイント — Checkpoint
    • singletonが複数リクエストから共有されると説明できる
    • リクエスト固有値を共有フィールドへ置かない理由を説明できる
予想5文字以上・証拠2項目・説明20文字以上で完了できます。

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

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

理解を確かめる — CHECK YOUR UNDERSTANDING

3問で理解チェック

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

Q1ServiceのフィールドへcurrentUserを保存してよいですか?
答え

いけません。別リクエストから上書きされる共有可変状態になります。

Q2Javaで残高を読んで十分なら、その後のlockは不要ですか?
答え

不要にはできません。読み取り後から更新までに別処理が残高を変えられます。

Q3requestIdは排他制御を行いますか?
答え

行いません。並行するログから同じリクエストを追跡する識別子です。

ここまで読めましたか?

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

教材の参照元 — SOURCE TRAIL

根拠と関連教材

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

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

  • 現在の実装 — CURRENT IMPLEMENTATIONservices/payment-service/src/main/java/com/minibank/payment/application/PaymentApplicationService.java共有可変状態を持たないService
  • 現在の実装 — CURRENT IMPLEMENTATIONservices/payment-service/src/main/java/com/minibank/payment/config/RequestIdFilter.java並行リクエストを追う相関ID
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/口座lockと残高再確認の実装