【Spring Boot】@Transactionalを使用したトランザクション制御(基本編)

【Spring Boot】 @Transactionalを使用した トランザクション(基本編) バックエンド
スポンサーリンク

はじめに

ぴんくうさぎ
ぴんくうさぎ

エラーが起こったら、それまでのinsert処理を

ロールバックしたいんだけど、どうしたらいいんだろう?

みどりがめ
みどりがめ

なるほど、今回はトランザクションについて

学習していこう!

本記事では、Spring Bootを使用したトランザクション制御の方法を解説しています。実際に動作確認したサンプルコードも併せて掲載しているので必要に応じてご利用ください!

この記事で分かること
  • トランザクションとは?
  • Springを使用したトランザクションの制御方法
  • @Transactionalの使い方と細かいオプションについて

そもそもトランザクションって何?

トランザクションは、データベースに対する一連の操作をひとまとめにする仕組みです。
例えば、銀行でお金を振り込む場合、送金と受け取りの2つの操作を一つのまとまり(トランザクション)として考えることができます。このトランザクションが成功すれば、お金は正しく送金され、失敗すれば元に戻されるといったイメージです。

トランザクションの重要な性質に以下があります。

  • 原子性(Atomicity):
    トランザクションは「すべて成功するか、すべて失敗する」原則です。途中で何か問題が起きた場合、トランザクション全体が無効になります。
  • 一貫性(Consistency):
    トランザクションが成功すると、データベースは一貫性がある状態になります。例えば、残高が正確であることなどが含まれます。
  • 隔離性(Isolation):
    複数のトランザクションが同時に行われても、お互いの操作が干渉しないように保護されます。一つのトランザクションが終わるまで、他のトランザクションがその変更を見ることができません。
  • 耐久性(Durability): トランザクションが成功したら、その結果はデータベースに永続的に保存され、システムがクラッシュしても失われません。

今回作成するサンプル

今回は下記のTaskテーブルに対して、10個のタスクを追加する処理を行います。
更新途中でエラーを発生させます。ロールバック、コミットを実際にDBを確認することで、トランザクションについて理解を深めていきます。

idtitlecontent
1titiele1content1
2titiele2content2
3titiele3content3
4titiele4content4
5titiele5content5
6titiele6content6
Taskテーブル

テーブル作成のDDLは以下の通りです。必要に応じてご使用ください。

create table if not exists task(
id VARCHAR(3),
title VARCHAR(50),
content VARCHAR(100)
);

Modelの作成

Taskテーブルに対応するTaskクラスを作成します。

package com.example.TransactionSample.model;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Task {

    private String id;
    private String title;
    private String content;
}

Repositoryの作成

DBへのinsert文を発行するRepositoryクラスを作成します。
赤字の箇所がDBへの処理部分です。

package com.example.TransactionSample.repository;

import com.example.TransactionSample.model.Task;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class TransactionRepository {
    private final JdbcTemplate jdbcTemplate;

    public TransactionRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void insert(Task task){
        jdbcTemplate.update("insert into task(id,title,content) values (?,?,?)",
                task.getId(),task.getTitle(),task.getContent());
    }

}

Serviceの作成

業務処理を行うServiceクラスを作成します。ここでは2つのメソッドを定義していて、
それぞれControllerクラスから呼び出されます。
・addTask()
→リポジトリクラスのinsertメソッドを呼び出して、タスクをDBにinsertします。
・createTask()
→タスクをTaskクラスのインスタンスとして生成します。ここでは試験的に10個のタスクを生成しています。

DBへの更新処理を行う、addTask()クラスに@Transactionalを付与しています。
動作確認時には、このアノテーションの有無での検証を行います。

package com.example.TransactionSample.service;

import com.example.TransactionSample.model.Task;
import com.example.TransactionSample.repository.TransactionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Service
public class TransactionService {

    @Autowired
    TransactionRepository repository;

    private static List<Task> taskList = new ArrayList<>();

    @Transactional
    public void addTask() {
        for (Task task : taskList) {
            repository.insert(task);
        }
    }

    public void createTask() {
        int max = 10;
        for (int i = 0; i < max; i++) {
            String num = String.valueOf(i + 1);
            taskList.add(new Task(num, "title:" + num, "content:" + num));
        }
    }
}

Controllerの作成

Controllerを作成します。
postでリクエストが送られた際に、サービスクラスの各種メソッドを使用して「タスクの生成」→「タスクのinsert」を行います。

package com.example.TransactionSample.controller;

import com.example.TransactionSample.service.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("")
public class TransactionController {

    @Autowired
    private TransactionService service;

    @PostMapping("/createTask")
    public void createTask() {
        service.createTask();
        service.addTask();
    }
}

動作確認

Serviceクラスの下記の箇所にブレークポイントを貼って、デバック起動します。

ブレークポイント

postでリクエスト(localhost:8080/createTask)を送り、ループの6回目(id=6)の箇所でRuntimeExceptionを発生させます。

RuntimeExceptionを発生

ロールバックされるので、1件もDBに登録されないことを確認できました。

DB結果

次に@Transactionalを外して同様の手順で動作確認をしてみると、下記の結果になります。

ブレークポイント

1~5までのタスクはDBに登録されていることが確認できます。(ロールバックされていない。)

DB結果

@Transactionalのあれこれ

最後に@Transactionalを使用する上での注意点や、指定できるプロパティについて紹介します。

注意点

  • @Transactionalアノテーションはpublicメソッドに付ける。
    →public以外ではロールバックされません。実行時エラーとなります。
  • @Transactionalアノテーションを付与したメソッドは別のクラスから呼び出す。
    →同じクラス内での呼び出しではロールバックされません。
  • 非検査例外(RuntimeException(サブクラス含む))はロールバックされるが、検査例外(Exception(サブクラス含む))はロールバックされずにコミットされる。
  • クラスに対して@Transactionalを設定した場合、そのクラス内のメソッド全てにトランザクション制御をかけることができる。

指定できるプロパティ

プロパティタイプ説明
valueString使用するトランザクションを指定する修飾子。複数のトランザクションマネージャーを使用している場合に利用する。
propagationeum(propagation)オプションの伝播設定。トランザクションを発生させる条件を指定できる。
isolationenum(isolation)オプションの分離レベル。REQUIREDREQUIRES_NEW のみ適用
timeoutintオプションのトランザクションタイムアウト。REQUIREDREQUIRES_NEW のみ適用
readOnlyboolean読み取り/書き込みトランザクションと読み取り専用トランザクション。
rollbackForClassオブジェクト配列ロールバックを引き起こしたいときに使用する。
noRollbackForClassオブジェクト配列ロールバックを引き起こしたくないときに使用する。

終わりに

最後にSpring学習のおすすめ教材を紹介します。
多くの方に支持されている人気商品です。気になる方は是非一度、覗いてみてください。

本記事はここまでとなります。ご覧いただきありがとうございました。

ご指摘等がございましたら頂けますと嬉しいです。
引き続き、プログラミングについて定期的に発信していきますのでよろしくお願いします!
また、もしよろしければtwitterもフォローしていただけると嬉しいです!🐢

コメント