Springのトランザクション例外とロールバック完全ガイド!初心者でもわかるrollbackFor/noRollbackForの使い方
生徒
「Spring Bootでトランザクションを使ってるんですが、例外が発生してもロールバックされないことがあります。なぜですか?」
先生
「Springのトランザクション管理では、どの例外でロールバックするかを明示的に指定できるんですよ。」
生徒
「なるほど…。どうやって設定するんですか?」
先生
「それでは、rollbackForとnoRollbackForの使い方について、具体例を交えて解説していきましょう!」
1. Springのトランザクションとは何か?
Spring FrameworkやSpring Bootを使ったJavaアプリケーション開発では、データベース処理をトランザクションで管理することが非常に重要です。トランザクションは、複数の処理をひとまとまりとして実行し、すべて成功したときだけコミット(確定)し、エラーが起きたときにはロールバック(取り消し)することで、データの整合性を保ちます。
Springでは@Transactionalアノテーションを使うことで、トランザクション制御を簡単に実現できます。
@Transactional
public void registerUser(User user) {
userRepository.save(user);
sendWelcomeMail(user);
}
2. 例外とロールバックの関係とは?
Springのトランザクション管理では、実行中に例外が発生するとロールバックされるのが基本です。ただし、すべての例外でロールバックされるわけではなく、Springは「デフォルトでRuntimeExceptionとError」が発生したときのみロールバックを行います。Exception(特にChecked Exception)がスローされても、ロールバックされないケースがあるのです。
これを理解せずに開発すると、「なぜかエラーが起きてるのにデータが保存されたまま…」という困った状況になります。
3. rollbackForで例外を指定する方法
ロールバックしたい例外がRuntimeException以外の場合、@TransactionalアノテーションのrollbackFor属性を使って、対象の例外クラスを指定できます。
@Transactional(rollbackFor = CustomCheckedException.class)
public void processPayment(Payment payment) throws CustomCheckedException {
paymentService.withdraw(payment);
throw new CustomCheckedException("処理中に例外が発生しました");
}
上記のように、CustomCheckedExceptionをスローしたときにもトランザクションがロールバックされるようになります。
4. noRollbackForでロールバックしない例外を指定
逆に、「この例外ではロールバックさせたくない」というケースでは、noRollbackFor属性を使います。例えば、バリデーションエラーや通知送信の失敗など、処理は続けたいけど記録は残したいといったときに使います。
@Transactional(noRollbackFor = NotificationException.class)
public void notifyUser(User user) {
userRepository.save(user);
throw new NotificationException("通知に失敗しました");
}
このようにすれば、NotificationExceptionが発生してもデータベースへの保存処理はロールバックされずにコミットされます。
5. rollbackForClassName/noRollbackForClassNameの活用
Springではクラス名(文字列)で例外を指定するrollbackForClassNameやnoRollbackForClassNameもあります。これは動的に例外クラスが決まるケースや、依存関係の関係で直接クラスを指定できない場合に役立ちます。
@Transactional(rollbackForClassName = "com.example.exception.ExternalApiException")
public void callExternalService() throws Exception {
throw new ExternalApiException("外部APIの呼び出しに失敗");
}
6. 複数の例外を指定する方法
複数の例外に対してロールバックや非ロールバックの設定をするには、カンマ区切りで配列として指定します。
@Transactional(
rollbackFor = {IOException.class, SQLException.class},
noRollbackFor = {ValidationException.class}
)
public void executeTask() throws IOException, SQLException {
// 複雑な業務処理
}
7. 注意点:例外を握りつぶすとロールバックされない
メソッド内で例外をキャッチして処理してしまうと、Springはロールバックのトリガーとして例外を検出できなくなります。その結果、エラーが発生しているのにトランザクションがコミットされてしまう危険があります。
@Transactional
public void updateData() {
try {
riskyService.run();
} catch (RuntimeException ex) {
System.out.println("エラーをログに出力: " + ex.getMessage());
}
}
上記のコードでは、例外がキャッチされてしまうため、ロールバックは起きません。どうしてもキャッチする必要がある場合は、再スローするか、明示的にTransactionAspectSupport.currentTransactionStatus().setRollbackOnly()を呼びましょう。
8. 実務での設計ポイントとリカバリ戦略
トランザクション例外処理の設計では、どのような例外でロールバックすべきか、業務要件に基づいて明確に設計することが重要です。特に外部システムとの連携や非同期処理などが絡む場合には、柔軟な例外制御が求められます。
例えば以下のようなルールを設けておくと、設計が安定します。
- 業務エラー(ユーザー入力ミスなど)→
noRollbackFor - システムエラー(DB障害、ネットワーク断)→
rollbackFor - 外部APIの一時的失敗 → リトライ戦略と組み合わせ
9. 例外のカスタム設計とベストプラクティス
アプリケーションで扱う例外は、独自の例外クラスとして明確に定義しておくと、rollbackForやnoRollbackForの指定がしやすくなります。また、例外の継承関係を整理しておくことで、意図しないロールバックの防止にもつながります。
public class BusinessException extends Exception {
public BusinessException(String message) {
super(message);
}
}
このような例外クラスを設けることで、@Transactional(rollbackFor = BusinessException.class)のように明示的な制御が可能になります。
まとめ
Springのトランザクション例外とロールバックの仕組みを理解することは、Spring BootやSpring Frameworkを使ったJava開発でとても重要です。データベースに登録、更新、削除などの処理を行うとき、途中で例外が発生した場合に、どこまでの処理を確定し、どこから取り消すのかを正しく設計しなければ、データの整合性が崩れてしまいます。
Springでは、@Transactionalを使うことでトランザクション管理を簡単に設定できます。しかし、@Transactionalを付ければすべての例外で必ずロールバックされるわけではありません。Springの標準設定では、RuntimeExceptionやErrorが発生した場合にはロールバックされますが、Checked Exceptionと呼ばれるException系の例外では、何も指定しないとロールバックされない場合があります。
そのため、ファイル処理のIOException、データベース処理のSQLException、業務上の独自例外であるBusinessExceptionなどを使う場合は、rollbackForを指定することが大切です。rollbackForを使うと、通常はロールバック対象にならない例外でも、明示的にロールバック対象にできます。
一方で、すべての例外でロールバックすればよいわけでもありません。たとえば、ユーザーの入力ミスや通知メールの送信失敗など、データベースへの登録処理自体は成功として残したいケースもあります。そのような場合には、noRollbackForを使って、この例外ではロールバックしないという指定ができます。
rollbackForとnoRollbackForの考え方
rollbackForは、指定した例外が発生したときにトランザクションをロールバックするための設定です。特に、Checked Exceptionを使う業務処理では重要になります。業務システムでは、在庫更新、注文登録、決済処理、会員登録、予約処理など、複数のテーブルをまとめて更新する場面が多くあります。
たとえば、注文情報を登録したあとに在庫数を減らす処理で例外が発生した場合、注文だけが登録されて在庫が減っていない状態になると、データの整合性が崩れてしまいます。このような場合は、全体を一つのトランザクションとして扱い、途中で失敗したら最初から取り消す設計が必要です。
@Transactional(rollbackFor = Exception.class)
public void orderItem(Order order) throws Exception {
orderRepository.save(order);
stockService.decreaseStock(order.getItemId());
}
上記のようにrollbackForを指定すると、Exceptionが発生した場合でもロールバックできます。ただし、何でもException.classでまとめればよいわけではありません。実務では、どの例外でロールバックするのかを業務要件に合わせて整理することが大切です。
noRollbackForは、指定した例外が発生してもロールバックしないための設定です。たとえば、会員登録は成功したが、登録完了メールの送信に失敗した場合、会員情報まで取り消す必要がないケースがあります。このようなときは、メール送信の失敗をロールバック対象から外すことで、登録処理を確定できます。
@Transactional(noRollbackFor = MailSendException.class)
public void registerMember(Member member) {
memberRepository.save(member);
mailService.sendCompleteMail(member);
}
このように、Springのトランザクション制御では、例外の種類ごとにコミットするのかロールバックするのかを明確に決めることが重要です。rollbackForとnoRollbackForを正しく使い分けることで、業務に合った安全なデータベース処理を実現できます。
例外を握りつぶさないことが大切
Springのトランザクションで初心者が間違えやすいのが、try catchで例外を捕まえたあと、そのまま処理を終えてしまうことです。メソッド内で例外をキャッチして外に出さない場合、Springは例外が発生したことを検知できません。その結果、本来ロールバックしたい処理がコミットされてしまうことがあります。
@Transactional
public void updateItem(Item item) {
try {
itemRepository.save(item);
stockService.updateStock(item);
} catch (RuntimeException ex) {
System.out.println("エラーが発生しました");
}
}
このコードでは、RuntimeExceptionが発生してもcatchの中で処理されてしまうため、Springのトランザクション管理がロールバックの判断をしにくくなります。ログを出したい場合でも、必要であれば例外を再スローするか、setRollbackOnlyを使って明示的にロールバック対象にする必要があります。
@Transactional
public void updateItem(Item item) {
try {
itemRepository.save(item);
stockService.updateStock(item);
} catch (RuntimeException ex) {
TransactionAspectSupport
.currentTransactionStatus()
.setRollbackOnly();
}
}
ただし、setRollbackOnlyは便利ですが、乱用すると処理の流れが分かりにくくなることもあります。基本的には、例外を適切にスローし、@TransactionalのrollbackForやnoRollbackForで制御する設計を優先すると理解しやすくなります。
実務でのトランザクション設計のポイント
実務のSpring Boot開発では、トランザクション処理はService層に書くことが多いです。Controllerは画面やリクエストの受け取りを担当し、Repositoryはデータベース操作を担当し、Serviceは業務処理をまとめます。そのため、@TransactionalはServiceクラスやServiceメソッドに付けると、役割が分かりやすくなります。
また、ロールバックの対象は技術的な理由だけで決めるのではなく、業務上どうあるべきかで判断することが大切です。注文処理であれば、注文登録、在庫更新、支払い情報登録は一体として扱う必要があります。一方で、メール送信やログ保存などは、失敗しても主処理を確定したい場合があります。
つまり、Springのトランザクション例外処理では、システムエラー、業務エラー、入力エラー、外部連携エラーを分けて考えることが重要です。システムエラーやデータ不整合につながる例外はロールバックし、入力ミスや通知失敗などは必要に応じてnoRollbackForで制御します。
rollbackForClassNameやnoRollbackForClassNameは、例外クラスを文字列で指定できる機能です。ただし、文字列指定はタイプミスに気づきにくいため、通常はrollbackForやnoRollbackForでクラスを直接指定するほうが分かりやすく安全です。依存関係の都合など、特別な理由がある場合に使うとよいでしょう。
Spring Boot、Spring Framework、Java、トランザクション、ロールバック、コミット、@Transactional、rollbackFor、noRollbackFor、Checked Exception、RuntimeException、Service層、Repository層、データベース整合性、例外処理、業務例外、システム例外といったキーワードは、実務のWebアプリケーション開発でもよく登場します。今回の内容を理解しておくことで、データベース更新に強く、保守しやすいアプリケーションを作れるようになります。
特に初心者のうちは、エラーが出たらとりあえずcatchするという書き方をしてしまいがちです。しかし、トランザクション処理では、例外を捕まえることよりも、その例外を発生させたあとにデータを確定するのか取り消すのかを考えることが大切です。
@Transactionalの基本動作を理解し、rollbackForとnoRollbackForを使い分けられるようになると、Spring Bootの開発力は大きく上がります。今後は、実際の登録処理、更新処理、削除処理、決済処理、予約処理などを例にしながら、どの処理を一つのトランザクションにまとめるべきかを意識して設計していきましょう。
生徒
Springのトランザクションは、処理が成功したらコミットして、失敗したらロールバックする仕組みだと分かりました。
先生
その通りです。データベースの登録や更新を安全に行うために、トランザクションはとても重要です。
生徒
ただ、@Transactionalを付ければ、すべての例外で自動的にロールバックされるわけではないのですね。
先生
はい。Springでは標準でRuntimeExceptionとErrorがロールバック対象です。Checked Exceptionではロールバックされないことがあるため、rollbackForを使います。
生徒
IOExceptionやSQLException、独自のBusinessExceptionなどでロールバックしたい場合は、rollbackForを指定すればよいのですね。
先生
その理解で大丈夫です。業務上、失敗したら処理全体を取り消したい例外は、rollbackForで明示しておくと安全です。
生徒
逆に、メール送信の失敗のように、データ登録は残したい場合はnoRollbackForを使うのですね。
先生
はい。noRollbackForを使うと、その例外が発生してもロールバックせずにコミットできます。ただし、本当に残してよい処理なのか、業務要件を確認することが大切です。
生徒
例外をtry catchで捕まえて、そのまま何もしないと、Springがロールバックできない場合があることも重要ですね。
先生
その通りです。例外を握りつぶすと、エラーが起きているのにコミットされる危険があります。必要に応じて再スローするか、明示的にロールバックを指定しましょう。
生徒
トランザクション例外処理は、単にエラーを処理するだけではなく、データを守るための設計なのですね。
先生
はい。Spring Bootの実務開発では、どの処理をまとめて成功させるのか、どの例外で取り消すのかを考えることが大切です。rollbackForとnoRollbackForを正しく使えるようになると、より安全で信頼性の高いシステムを作れるようになります。