[Spring Boot] @Transactional の使い方

JavaSpringBoot

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 クラスに @Transactional アノテーションが付いていなくても、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

Posted by Agopeanuts