← Spring コース
54. @Transactional はプロキシとスレッドでできている
を付けたを持つの は、実体ではなくです。実測では AccountService$$SpringCGLIB$$0 というクラスでした。外からメソッドを呼ぶと、プロキシが先にを始め、戻るときにコミットします。始めたトランザクションと DB 接続は、(ごとに別の値を持つ変数)で今のスレッドに結び付けられます。
この形なら、接続を引数で渡して回らなくても、同じスレッドで動く処理はみな同じトランザクションに入れます。1リクエストを1本のスレッドが受け持つ作りと噛み合っています。代わりに、メソッドの中で別のスレッドを起こすと、そちらには引き継がれません。実測でも、起こしたスレッドではトランザクションが無い状態で、そこで保存した行は呼び出し側が巻き戻しても残りました。
つまずきどころは2つです。1つは プロキシの回と同じで、同じクラスの中から this 経由で呼ぶとプロキシを通らず、トランザクションは始まりません。もう1つはの種類で、既定で巻き戻るのは と Error だけです。 のような( での宣言をコンパイラが求める例外)を投げたメソッドは、コミットされて行が残りました。
点線の付いた語はタップすると説明が出ます。用語集を開く →
外からの呼び出しservice.innerWithTx(...)
コンテナが渡した参照はプロキシ
プロキシAccountService$$SpringCGLIB$$0
トランザクションを始める今のスレッドに結び付ける
ThreadLocal(スレッド main)EntityManagerHolder と ConnectionHolder の2件
別のスレッド worker-1結び付いた物は 0 件
実体のメソッドを呼ぶ
AccountService の実体innerWithTx の本体。txActive=true
ここから this.xxx() と呼んでもプロキシには戻らない戻りをプロキシが受ける
正常終了・検査例外コミット
RuntimeException・Errorロールバック
- ・検査例外でコミットされるのは Spring の既定です。rollbackFor = Exception.class を付けると検査例外でも巻き戻りました
- ・ここで扱ったのはスレッドに結び付く方式です。リアクティブな方式では ThreadLocal ではなく Reactor のコンテキストを使うとリファレンスにあります
- ・リポジトリの Bean も l21.tx.$Proxy101 というプロキシでした($Proxy に続く数字は実行で変わります)
サンプルコード(フレームワーク環境が必要なため表示のみ)
// TxApp.java(抜粋)— Spring Boot 4.1.1 / Spring Framework 7.0.9、DB は H2
@Service
class AccountService {
private final AccountRepository repo;
AccountService(AccountRepository repo) { this.repo = repo; }
static String tx() {
return "txActive=" + TransactionSynchronizationManager.isActualTransactionActive()
+ " thread=" + Thread.currentThread().getName();
}
public void outerWithoutTx(String owner) {
System.out.println(" outerWithoutTx: " + tx());
this.innerWithTx(owner); // プロキシではなく this を通る呼び出し
}
@Transactional
public void innerWithTx(String owner) {
System.out.println(" innerWithTx: " + tx());
repo.save(new Account(owner));
throw new IllegalStateException("unchecked");
}
}
// main から順に呼び、そのつど全行の owner を表示した結果(rows の checked は前の実験で残った行)
//
// AccountService bean class = l21.tx.AccountService$$SpringCGLIB$$0
// --- 4. innerWithTx called from outside (through the proxy)
// innerWithTx: txActive=true thread=main
// threw java.lang.IllegalStateException: unchecked
// rows = [checked]
// --- 5. innerWithTx called via this.innerWithTx (self-invocation)
// outerWithoutTx: txActive=false thread=main
// innerWithTx: txActive=false thread=main
// threw java.lang.IllegalStateException: unchecked
// rows = [checked, via-this]
図やコードに出てくる用語(タップで説明)
サンプルコードで身につける(5本) →
学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。
公式ドキュメントで詳しく ↗