🔒 Spring Framework 講座【第26回】処理の信頼性を守る!〜トランザクション管理の基本〜

docs

前回、JPAのプロジェクションを使って効率的にデータの一部を取得するテクニックを学びました。これで、データの入出力に関する知識はほぼ完璧です。

今回は、業務システムの信頼性、特にデータベース操作の信頼性を保証するための非常に重要な概念である「トランザクション管理」について学びます。

トランザクションは、複数のデータベース操作を一つのまとまりとして扱い、データの一貫性と整合性を守るための仕組みです。

1. トランザクションとは何か?

**トランザクション(Transaction)とは、一連の不可分(Atomic)**なデータベース操作のことです。

例えば、「銀行口座Aから10,000円を引き出し、口座Bに10,000円を振り込む」という処理を考えます。この処理は、以下の2つのDB操作から成り立っています。

  1. 口座Aの残高を減らす(UPDATE)
  2. 口座Bの残高を増やす(UPDATE)

もし、1が成功した直後にシステム障害が発生し、2が実行されなかったらどうなるでしょうか?口座Aからはお金が減ったのに、口座Bには増えず、お金が消滅してしまいます。

トランザクションは、このような事態を防ぎ、2つの操作が必ず両方成功するか、あるいは両方失敗することを保証します。

トランザクションの4つの特性(ACID特性)

トランザクションは、以下の4つの特性(ACID特性)を満たす必要があります。

特性意味補足
Atomicity(原子性)全ての操作が成功するか、全て失敗して元に戻る(Rollback)。「全部やるか、全くやらないか」を保証。
Consistency(一貫性)トランザクションの開始前と終了後で、データの整合性が保たれる。矛盾した状態にならないことを保証。
Isolation(隔離性)複数のトランザクションが同時に実行されても、互いに影響を与えない。処理は排他的に行われる(同時実行制御)。
Durability(永続性)トランザクションが成功(Commit)したら、その結果は永続的に保持される。DBに物理的に書き込まれることを保証。

2. Springにおけるトランザクション管理

従来のJava開発では、トランザクションの開始(begin())と終了(commit()/rollback())を、開発者が手動でコードに記述する必要がありました。

Spring Frameworkでは、この面倒なトランザクション制御を、宣言的に、つまりアノテーションを付けるだけで実現できます。

@Transactional アノテーション

Springでトランザクション管理を行う中心的なアノテーションが @Transactional です。

このアノテーションをService層のクラス全体、または特定のメソッドに付与します。

Java

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;

// Service層のクラス全体に @Transactional を付与するケースが多い
@Service
@Transactional 
@RequiredArgsConstructor
public class TransferService {
    
    private final AccountRepository accountRepository;
    
    // このメソッド内の複数のDB操作が、一つのトランザクションとして扱われる
    public void transfer(Long fromId, Long toId, int amount) {
        
        // 1. 口座Aから引き出し
        Account fromAccount = accountRepository.findById(fromId).orElseThrow();
        fromAccount.setBalance(fromAccount.getBalance() - amount);
        accountRepository.save(fromAccount); // DB操作①
        
        // 【システムエラーやランタイム例外が発生した場合を想定】
        if (amount > 100000) {
            // RuntimeException (実行時例外)をスロー
            throw new IllegalArgumentException("一回の振り込み額が上限を超えています"); 
        }
        
        // 2. 口座Bへ入金
        Account toAccount = accountRepository.findById(toId).orElseThrow();
        toAccount.setBalance(toAccount.getBalance() + amount);
        accountRepository.save(toAccount); // DB操作②
        
        // 3. 例外が発生しなければ、自動的にコミット(Commit)される
    } 
    // メソッドが正常終了した場合 -> コミット
    // RuntimeExceptionが発生した場合 -> ロールバック(Rollback)
}

3. トランザクションの自動制御

@Transactional が付与されたメソッドが実行されると、Springは以下の制御を自動で行います。

  1. 開始: メソッドが呼び出される前に、トランザクションを開始します。
  2. 実行: メソッド内の全てのDB操作を実行します。
  3. コミット: メソッドが正常に終了した場合、全ての変更をDBに永続的に反映(Commit)します。
  4. ロールバック: メソッド実行中に RuntimeException(実行時例外) または Error が発生した場合、それまでに実行された全てのDB操作を取り消し(Rollback)、データ開始前の状態に戻します。

ポイント: @Transactional は、デフォルトでチェック例外(Checked Exception)(例: IOException)が発生した場合はロールバックしません。ロールバックさせたい場合は、@Transactional(rollbackFor = Exception.class) のように明示的に設定する必要があります。

4. 読み取り専用トランザクション

データを変更せず、単にDBから読み取るだけのメソッド(例: findById、findAll)には、readOnly 属性を指定するのがベストプラクティスです。

Java

@Service
@Transactional(readOnly = true) // クラス全体を読み取り専用に設定
public class ItemService {
    
    // データ変更を行うメソッドのみ、個別に上書き
    @Transactional(readOnly = false) // または単に @Transactional
    public Item saveItem(Item item) {
        // ... 保存処理
    }
    
    // 読み取り専用のメソッド(高速化とDBリソースの節約に貢献)
    // クラス設定により readOnly = true が適用される
    public List<Item> findAllItems() {
        // ... 検索処理
    }
}

readOnly = true に設定することで、DB側でも最適化(ロックの省略など)が行われ、パフォーマンスが向上し、DBリソースの節約に繋がります。

✅ 本日のまとめ

  • トランザクションは、複数のDB操作を不可分な一つの単位として扱い、ACID特性(原子性、一貫性、隔離性、永続性)を保証する。
  • Springでは、@Transactional アノテーションをService層のメソッドに付けることで、トランザクションの開始、コミット、ロールバックを宣言的に自動管理できる。
  • デフォルトでは、RuntimeException のみロールバックの対象となる。
  • データの読み取りのみを行うメソッドには、@Transactional(readOnly = true) を設定し、パフォーマンスを向上させる。

🔔 次回予告

トランザクション管理を覚えたことで、業務処理の信頼性は格段に向上しました。しかし、システムが大きくなると、同時に複数のユーザーが同じデータにアクセスする並行処理の問題が出てきます。

次回は、トランザクションの隔離レベル(Isolation Level)という概念を学び、同時実行時のデータの競合(コンフリクト)をどのように防ぐかについて深く理解します。

次回:【第27回】トランザクションの隔離レベルと並行処理 にご期待ください!

コメント

タイトルとURLをコピーしました