━目次━
プロジェクトでTERASOLUNA(Spring Frameworkをラップしているフレームワーク)を使っていたのですが、
開発が進んできたときにあることが話題になりました。
@Transactionalでのトランザクション管理はデフォルトでは検査例外をコミットする!
なんだと~!(遅い)
そこからSpringのトランザクション管理の仕組みについて色々調べたのでまとめます。
検査例外と非検査例外については別記事にしましたので合わせて読んでみて下さい
【Java】検査例外と非検査例外の違いを図でまとめてみた
@Transactionalのオプション一覧
初めに@Transactionalの選択可能な属性を見てみます。
属性名 | 設定値 | 説明 | デフォルト |
---|---|---|---|
propagation トランザクションの伝播方法を指定する。 |
REQUIRED | トランザクションが開始されていなければ開始する。 | ○ |
REQUIRES_NEW | 常に新しいトランザクションを開始する。 | ||
SUPPORTS | トランザクションが開始されていればそれを利用する。 開始されていなければ利用しない。 |
||
NOT_SUPPORTED | トランザクションを利用しない。 | ||
MANDATORY | トランザクションが開始されていなければ例外が発生する。 | ||
NEVER | トランザクションが開始していれば例外が発生する。 | ||
NESTED | セーブポイントが設定される。JDBCのみ有効。 | ||
isolation トランザクションの独立レベルを指定する。 DBの仕様に依存 |
DEFAULT | DBが提供するデフォルトの独立性レベル。 | ○(↓参照) |
READ_UNCOMMITTED | 他のトランザクションで変更中(未コミット)のデータが読める。 | ||
READ_COMMITTED | 他のトランザクションで変更中(未コミット)のデータは読めない。 | Oracle,DB2,PostgreSQL,SQL Server | |
REPEATABLE_READ | 他のトランザクションが読み出したデータは更新できない。 | MySQL | |
SERIALIZABLE | トランザクションを完全に独立させる。 | ||
timeout | 時間(秒) | トランザクションのタイムアウト時間(秒)を指定。 | -1 |
readOnly | true/false | トランザクションの読取専用フラグを指定。 | false |
rollbackFor | クラスリスト | トランザクションのロールバック対象とする例外クラスのリストを指定。 | 指定なし |
rollbackForClassName | クラス名リスト | rollbackForのクラス名バージョン | 指定なし |
noRollbackFor | クラスリスト | トランザクションのコミット対象とする例外クラスのリストを指定。 | 指定なし |
noRollbackForClassName | クラス名リスト | noRollbackForのクラス名バージョン | 指定なし |
基本的にpropagationは指定なし(デフォルトのREQUIRED)にしておくのが一般的でしょう。
(@Transactionalを指定したサービスクラスを入れ子に実行しても、例外が発生した時は全てロールバックされる)
isolationは主要なDBだとMySQLだけ挙動が異なるので注意が必要です。
デフォルトでは検査例外はコミットされる!
先程の@Transactionalのロールバック関係のオプション(rollbackFor,rollbackForClassName,noRollbackFor,noRollbackForClassName)を指定しない場合、Spring Frameworkはデフォルトで以下の動作をします。
●非検査例外クラス(java.lang.RuntimeExceptionおよびjava.lang.Error)またはそのサブクラスの例外が発生
ロールバック
●検査例外クラス(java.lang.Exception)またはそのサブクラス(java.lang.RuntimeExceptionを除く)の例外が発生
コミット
検査例外にはIOException(java.io.IOException)やSQLException(java.sql.SQLException)も含まれます。
DB関係の例外でロールバックされないのはまずい!と思って挙動を確認しましたが、
あれ?SQLExceptionを発生させてもロールバックされるぞ??
んん?IOExceptionを発生させるとコミットされている…
1番上のExceptionクラスを発生させると…コミットされる…
何故か検査例外の中でもSQLExceptionだけ記述と違い、ロールバックされてしまう事象が起きました。
色々調べてみると、Spring Frameworkの仕様に謎が隠されていました。
Spring FrameworkではSQLExceptionをspring独自のExceptionに変換している!
ある記述を見つけました
Spring Frameworkでは、JDBCの例外(java.sql.SQLException)や、O/R Mapper固有の例外を、Spring Frameworkから提供しているデータアクセス例外(org.springframework.dao.DataAccessExceptionのサブクラス)に変換する機能がある。
なるほど、納得です。
SQLExceptionが発生した場合、Spring Frameworkが内部的にDataAccessException系に変換しているため、検査例外と認識されずにロールバックされていたんですね。
たぶんSQL系の例外は良くあるから開発者がハンドリングを意識しないで良いようにっていう思想でこうなったのかな?
もうそれだったら検査例外もデフォルトで全部ロールバックにしといて欲しいけどのぉ~
検査例外も含め全てロールバックするコード
因みに検査例外も含めて全ての例外発生時にロールバックしたい場合は以下のように記述すればOKです。
1 2 3 4 5 |
@Service @Transactional(rollbackFor = Exception.class) public class HogehogeServiceImpl implements HogehogeService { /* 処理内容 */ } |
さいごに
いやー時間とられました。
というかたぶんSpring Framework開発者なら知ってて当然の知識かもしれませんが、何分初Springだったもので…
正直例外の検査・非検査なんて意識したくないのでそこはJavaの「ん~~」ってとこですね。
他の言語も採用しなかったみたいですし…
それでは!また!!
人気の記事だけ集めたので是非覗いていってください^^
厳選!目的別にオススメ記事を紹介-あなたの欲しい情報がここに-