本文へスキップ
BecomeCoder

Spring / Spring Bootコース · 第5章 データアクセス ― Spring Data JPA · レッスン20

@Transactional ― 複数の操作をひとまとまりにする

ローカル実施

導入

「Aさんの口座から1万円引いて、Bさんの口座に1万円足す」——この2つの操作の途中でエラーが起きたらどうなるでしょうか。Aさんからは引かれたのに、Bさんには届かない、という事態は絶対に避けなければなりません。

説明

複数のデータベース操作を「すべて成功する」か「すべて失敗(何も変更しない)」かのどちらかにまとめる仕組みを トランザクション と呼びます。Springでは、メソッドに @Transactional を付けるだけで、そのメソッド全体を1つのトランザクションとして扱えます。

@Service
class TransferService {
    private final AccountRepository accountRepository;

    public TransferService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Transactional
    public void transfer(Long fromId, Long toId, int amount) {
        Account from = accountRepository.findById(fromId).orElseThrow();
        Account to = accountRepository.findById(toId).orElseThrow();

        from.withdraw(amount); // Aさんの残高を減らす
        to.deposit(amount);    // Bさんの残高を増やす
        // ここまでの間に例外が起きたら、全部なかったことになる(ロールバック)

        accountRepository.save(from);
        accountRepository.save(to);
    }
}
sequenceDiagram
    participant Svc as TransferService.transfer()
    participant DB as データベース
    Svc->>DB: トランザクション開始
    Svc->>DB: Aさんの残高を更新
    Svc->>DB: Bさんの残高を更新
    alt すべて成功
        Svc->>DB: コミット(変更を確定)
    else 途中で例外が発生
        Svc->>DB: ロールバック(すべて取り消す)
    end

@Transactional が付いたメソッドの中で、予期しない例外(実行時例外)が発生すると、Springはそこまでの変更を自動的に ロールバック(取り消し) します。逆に、メソッドが最後まで正常に終われば、変更内容がまとめて コミット(確定) されます。

@Transactional はどこに付けるべきでしょうか。原則として Service層のメソッド に付けます。Repositoryの個々のメソッド呼び出しは、それぞれが小さすぎてトランザクションの境界としては不適切なことが多く、「複数のRepository呼び出しをまたいだ、業務としてひとまとまりの処理」を表現できるService層が適した置き場所になります。

やってみよう

「注文を確定する」処理が、「在庫を減らす」「注文レコードを作成する」「ポイントを付与する」の3ステップからなるとします。この3ステップのどこか1つでも失敗したら、それ以外もすべて取り消したい——このとき @Transactional をどのクラスのどのメソッドに付けるべきか考えてみましょう。