[Spring Boot] @Transactional の使い方
Spring Boot でロールバックさせたいときは、@Transactional
アノテーションを使う。このアノテーションの使い方を調べたのでメモ。ただ付ければいいわけでなく、いくつか知っておかなければならないポイントがあった。
使い方
@Transactional
アノテーションはクラス、またはメソッドに付与する- ロールバックさせたい処理は、 public メソッドに記述
- ロールバックさせたい処理は、別クラスから呼び出されるメソッドに記述
-> 同じクラス内でのメソッド呼び出しは、トランザクション管理対象にならない
以下のように、コントローラーなど別クラスから hoge メソッドを呼び出しても、ロールバックされない。
@Service
public class BookService {
public void hoge() {
save();
}
@Transactional
public void insert() {
// Insert 処理
}
}
ロールバックされる例外の種類
ロールバックされるのは、トランザクション範囲内で、非検査例外( RuntimeException
及びそのサブクラス)が発生した場合
-> 検査例外(検査例外クラス( java.lang.Exception
)またはそのサブクラス( java.lang.RuntimeException
を除く))をトランザクション対象にしたい場合は、以下のプロパティを設定する
@Transactional(rollbackForClassName={"Exception"})
トランザクションの範囲
@Transactional
アノテーションを付けたメソッド以降の処理が同一トランザクション管理範囲となる
例えば REST の場合 ( 以下の実装例 ) だと、Service の tran
メソッドからトランザクション管理が開始される。 tran
メソッドの実行が終了し、するとトランザクション管理が終了する。そのため Controller の save
メソッド内の「何らかの処理...
」の間に例外が発生しても、ロールバックされない。
Controller
@GetMapping("/save")
public String save() {
// 何らかの処理...
// @Transactional アノテーションの付いたServiceクラスのメソッドを呼び出す
bookService.tran(book);
// 何らかの処理...(↓の例外が発生してもロールバックされない)
// throw new RuntimeException();
return "Transactional";
}
Service クラスとここで呼び出している Component クラスの Insert
メソッドで例外が発生するとロールバックされる。
Service
@Service
public class BookService {
@Autowired
BookRepository repository;
@Autowired
MyComponent component;
@Transactional
public void tran(Book book) {
// Insert 処理
repository.insert(book);
// ComponentクラスのInsert メソッド呼び出し
component.insert(book2);
}
}
Component
@Component
public class MyComponent {
@Autowired
BookRepository repository;
public void insert(Book book) {
// Insert 処理
repository.insert(book);
// throw new RuntimeException("------!!!");
}
}
Component クラスに @Transaction
al アノテーションが付いていなくても、Component クラスの Insert
メソッドはトランザクション対象となっている。
Component クラスの Insert
メソッドを、別トランザクションとして扱いたい場合は、 propagation
プロパティの値を REQUIRES_NEW
にする。
@Transactional(propagation=Propagation.REQUIRES_NEW)
コントローラーに @Transactional の付与
トランザクション処理を入れる前は、コントローラーで、このサービスとコンポーネントを呼び出すことを考えていた。しかし、サービスまたはコンポーネントで例外が発生した場合、サービスとコンポーネントの両方の処理をロールバックする必要があった。
この場合、コントローラーに @Transactional
アノテーションをつけるとロールバックさせることができる。(コントローラーに @Transactional
アノテーションをつけることも可能)
しかし、トランザクション管理は、コントローラーの役割ではないので、ドメイン層にトランザクション処理を持たせた方がいい。そうすると上記実装例のように、サービス -> コンポーネント( SharedService など)のような呼び出し方になる。
トランザクション境界は、原則Serviceに設ける。アプリケーション層(Web層)にトランザクション境界が設けられている場合、業務ロジックの抽出が正しく行われていない可能性があるので、見直しを行うこと。
https://terasolunaorg.github.io/guideline/5.0.0.RELEASE/ja/ImplementationAtEachLayer/DomainLayer.html#id17