1. test 1
  2. test 2
  3. test 3
  4. test 4
  5. test 5
  6. test 6
コース一覧
Spring Boot入門
トランザクションの巻き戻し

Spring Boot入門

Java で Web API サーバーを作るための Spring Boot の入門コースです。DI コンテナ、REST コントローラ、リクエスト検証、レイヤ分割、Spring Data JPA、例外ハンドリング、テストまでを扱います。毎レッスン、Spring が裏側でやっている判断そのものを pure Java の関数として自分で書き、フレームワークが何を肩代わりしてくれているのかを手触りで理解できる構成にしました。約 12 時間 (1 日 30 分 × 24 日) で 48 レッスンを修了できます。

1
Spring Bootとは何か
01. Spring Bootで何が作れるのか10分
02. サーブレットからフレームワークへ15分
03. ルーティング表という発想15分
04. プロジェクトの構成10分
05. 起動のしくみと自動設定15分
06. パスのマッチング15分
2
DIコンテナ
01. newで書くと何が困るのか15分
02. コンストラクタインジェクション15分
03. インターフェースへの依存15分
04. @Componentと@Bean15分
05. 自作のDIコンテナ20分
06. Beanのスコープとライフサイクル15分
3
RESTコントローラ
01. RESTという設計方針15分
02. @RestControllerの役割15分
03. @GetMappingとパス変数15分
04. クエリパラメータの受け取り15分
05. @PostMappingとリクエストボディ15分
06. ステータスコードの選び方15分
07. ResponseEntityで組み立てる15分
4
リクエストとレスポンスの変換
01. JSONとオブジェクトの往復15分
02. DTOとエンティティを分ける15分
03. recordでDTOを書く15分
04. 入力検証の基本15分
05. 検証エラーをまとめて返す15分
06. アノテーションによる検証15分
5
レイヤ構成とビジネスロジック
01. 3層アーキテクチャ10分
02. @Serviceに置くもの15分
03. 状態遷移をルールにする15分
04. 料金計算のような業務ロジック15分
05. @Transactionalとは何か15分
06. トランザクションの巻き戻し15分
6
データアクセス
01. @Entityとテーブルの対応15分
02. リポジトリという抽象15分
03. メソッド名からクエリを作る20分
04. 検索条件の組み立て15分
05. ページングと並び替え15分
06. N+1問題15分
7
例外処理と横断的関心事
01. 例外をHTTPに変換する15分
02. @ExceptionHandlerと@ControllerAdvice15分
03. エラーレスポンスの形を決める15分
04. フィルタとインターセプタ15分
05. ログとリクエストの追跡15分
06. 設定値の外部化15分
8
テストと仕上げ
01. テストの種類と使い分け15分
02. Serviceの単体テスト15分
03. モックで依存を差し替える15分
04. MockMvcでエンドポイントを試す15分
05. 本番へ出すための次の一歩10分

トランザクションの巻き戻し

このレッスンでは、原子性という考え方を、実際に巻き戻す処理を書くことで理解します。

途中まで成功した状態が一番困ります

注文の処理が、次の3つの操作からできているとします。

  1. 注文を保存する
  2. 在庫を1つ減らす
  3. ポイントを付与する

2 番目まで成功して 3 番目で失敗したとき、何もしなければ「注文はあり、在庫は減っているが、ポイントは付いていない」という状態が残ります。これは失敗でも成功でもない、どちらとしても扱えない状態です。利用者からの問い合わせで発覚し、担当者が1件ずつ手で直すことになります。

こうならないための決まりが原子性です。一連の操作は、すべて成功するか、1つも実行されなかったことにするか、そのどちらかにする。中途半端な状態を作らない、という約束です。

Spring では宣言するだけです

この巻き戻しを自分で書く必要はありません。@Transactional を付ければ、失敗時にデータベースの変更がまとめて取り消されます。

Java

@Service public class OrderService { @Transactional public void placeOrder(Long userId, Long itemId) { orderRepository.save(new Order(userId, itemId)); stockRepository.decrease(itemId); // ここで失敗しても pointRepository.add(userId, 10); // 注文の保存ごと取り消される } }

書き手がすることは、境界を宣言することだけです。しかし裏側では、変更を記録しておき、失敗したら記録をたどって元に戻す、という作業が行われています。それを一度自分で書いておくと、@Transactional が何を肩代わりしているのかが具体的に分かります。

巻き戻す方法は2つあります

途中の状態を元に戻す方法は、大きく次の2通りです。

方法やり方向いている場面
開始前の状態を控えておく処理前の値をコピーし、失敗したらそれで上書きする状態が小さく、まるごと保存できるとき
逆の操作を積む適用した操作と反対の操作を記録し、失敗したら逆順に適用する状態が大きく、差分だけ扱いたいとき

1 つ目は単純で確実です。2 つ目は補償トランザクションと呼ばれる考え方で、外部の決済サービスのように後から取り消せないものを扱うときに使われます。データベース1つに閉じている限りは、@Transactional に任せるのが最善です。

途中で止めるという判断も含まれます

もう1つ大事なのが、失敗を見つけた時点で残りの操作を実行しないことです。3 番目で失敗したのに 4 番目を続けてしまうと、巻き戻す対象が増えるうえ、失敗した状態の上に処理を重ねることになります。失敗を検知したら即座に中断し、それから戻す。この順番が原則です。

何を失敗と呼ぶかは業務が決めます

巻き戻しを実装するうえで、技術より先に決めておくことがあります。それは、何が起きたら失敗と見なすのかという線引きです。残高が 0 を割ることを失敗とするのか、マイナスを許して後から請求するのか。在庫が足りないときに 0 で止めるのか、注文自体を無効にするのか。これらはすべて業務側の決めごとで、@Transactional は答えを持っていません。フレームワークが引き受けるのは「失敗と決まったものを取り消す」ところからで、失敗かどうかの判定は Service に書く仕事です。

この線引きを Service の1か所に置いておくと、後から条件が変わったときに触る場所が1つで済みます。逆に判定が呼び出し側に散っていると、片方だけ緩められて中途半端な状態が生まれます。

演習で書くもの

演習では、操作の並びを順に適用し、途中で失敗条件に当たったら開始前の値に戻して返す関数を書きます。上の表の1つ目の方法、つまり開始前の状態を控えておいて上書きするやり方が素直です。失敗を見つけた時点で残りを実行しないこと、そして戻した後にどこで失敗したのかを呼び出し元へ伝えることの2つに注意してください。Spring が @Transactional の裏側でやっている判断を、自分の手で書くことになります。

要件

  1. すべて成功したら committed=<最終残高> を、途中で失敗したら rolledback=,failedAt=<1 から数えた位置> を返すこと
  2. 適用結果が 0 未満になる操作を失敗とし、その時点で残りの操作を実行しないこと
  3. ops が空文字列または null のときは committed= を返すこと

入出力例

applyAll("+300,-100,+50", 1000) → "committed=1250" applyAll("-500,-400,-200", 1000) → "rolledback=1000,failedAt=3" applyAll("-1", 0) → "rolledback=0,failedAt=1" applyAll("-1000,+9999", 1000) → "committed=9999" applyAll("", 700) → "committed=700" applyAll("+10,-20,+1000", 5) → "rolledback=5,failedAt=2"

ヒント

ops.split(",") で操作の配列にできます。各要素は Integer.parseInt でそのまま符号付きの整数に変換できます

作業用の変数に start をコピーしてから適用していけば、失敗時は start をそのまま返すだけで巻き戻せます

適用してから判定するのではなく、適用後の値を仮の変数で確かめてから採用すると、失敗時に作業用の変数が汚れません

生田 陸人
監修生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
編集 ゆめさく編集部·公開 2026/08/09

関連レッスン

  • @Entityとテーブルの対応

    エンティティのマッピングと主キーの指定を確認します。

  • 例外をHTTPに変換する

    例外の種類からステータスコードを決める処理を実装します。

  • テストの種類と使い分け

    @SpringBootTest と @WebMvcTest の違いを確認します。

このレッスンに出てくる用語

意味があいまいなまま進んだ語は、ここから読み直せます。

  • 処理計算や代入を表す長方形
  • データベースデータを構造化して保存する仕組み
  • トランザクション「全部成功 or 全部なかったことに」をまとめる単位
  • 判断YES/NO 分岐を表す菱形
  • 関数処理に名前を付けて再利用できる単位
main.java
学習モード
エディタを読み込んでいます

メモ

トランザクションの巻き戻し

⌘S で保存