Springのトランザクション例外とロールバック完全ガイド!初心者でもわかるrollbackFor/noRollbackForの使い方
Spring Bootを使ったWebアプリケーション開発を、 環境構築から実践まで一通り学びたい方には、 定評のある入門書が参考になります。
Spring Boot 3 プログラミング入門をAmazonで見る※ Amazon広告リンク
生徒
「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)のように明示的な制御が可能になります。