- 執筆は自分で行い、翻訳にはAIを使用しました
- 現在日本語を勉強しています。翻訳内容を確認しましたが、もし間違いや不自然な表現があれば教えてください
- 内容についてのフィードバックも歓迎です
前の記事
前の記事で@Transactionalがなぜprivateメソッドで動作しないかを見ました。
その記事の延長線として、Self-Invocationの問題についてもまとめてみました。
問題の状況
- クラスの内部から
@Transactionalを呼び出すとどうなるのか? - 正常な呼び出しと、問題のあるSelf-Invocationの状況を作ってみました
@Transactionalの自己呼び出し(実質的にターゲットオブジェクト内のメソッドが同じオブジェクト内の別のメソッドを呼び出すこと)は、実行時に実際のトランザクションを発生させません
IntelliJでも前回と同様に警告が表示され、答えを教えてくれます。
なぜ発生するのか?
前の記事で@Transactionalはプロキシがメソッドの呼び出しをインターセプトして動作すると整理しました。
Self-Invocation問題の核心は、同じクラスの内部から呼び出すとプロキシを経由しないという点です。
@Transactional
public void saveInternalCall(String tag) {
hashtagRepository.save(Hashtag.create(tag));
throw new RuntimeException("RollBack!");
}
public void saveViaSelfInvocation(String tag) {
this.saveInternalCall(tag);
}
-
saveViaSelfInvocation()からthis.saveInternalCall()を呼び出すと -
thisはプロキシではなく実際のオブジェクトを指します - つまり、
TransactionInterceptorが介入できず、トランザクションは動作しません
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
プロキシモード(デフォルト)では、プロキシを通じて入ってくる外部メソッドの呼び出しのみがインターセプトされます。つまり、Self-Invocation(ターゲットオブジェクト内のメソッドが同じオブジェクトの別のメソッドを呼び出すこと)は、呼び出されるメソッドに
@Transactionalが付いていても、実行時に実際のトランザクションは動作しません。
- Spring公式ドキュメントでもこの問題を明確に説明しています
テスト結果
前回と同様に
- 外部から直接呼び出し:
@Transactionalが動作する - 内部から
thisで呼び出し:@Transactionalが動作しない
この2つを検証してみます。
テストは両方とも通過しました。
これにより、動作について検証されました。
結果分析
外部から直接呼び出し
Hibernate: delete from hashtags where id=?
2026-02-24 02:51:49.948 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [com.example.demo.blog.TestService.saveDirectCall]
2026-02-24 02:51:49.948 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
Hibernate: insert into hashtags (created_at,tag,updated_at,usage_count) values (?,?,?,?)
2026-02-24 02:51:49.950 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
2026-02-24 02:51:49.950 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [com.example.demo.blog.TestService.saveDirectCall] after exception: java.lang.RuntimeException: RollBack!
2026-02-24 02:51:49.954 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByTag]: This method is not transactional.
Hibernate: select h1_0.id,h1_0.created_at,h1_0.tag,h1_0.updated_at,h1_0.usage_count from hashtags h1_0 where h1_0.tag=?
2026-02-24 02:51:49.948 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [com.example.demo.blog.TestService.saveDirectCall]
- TestService(プロキシ)がトランザクションを開きました
2026-02-24 02:51:49.948 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
-
SimpleJpaRepositoryのsave()がサービスのトランザクションに参加します
2026-02-24 02:51:49.950 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [com.example.demo.blog.TestService.saveDirectCall] after exception: java.lang.RuntimeException: RollBack!
- 例外を検知してトランザクション全体をロールバックします。
save()のinsertも取り消されます
内部からthisで呼び出し
2026-02-24 02:51:49.821 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
Hibernate: insert into hashtags (created_at,tag,updated_at,usage_count) values (?,?,?,?)
2026-02-24 02:51:49.853 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
2026-02-24 02:51:49.875 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByTag]: This method is not transactional.
Hibernate: select h1_0.id,h1_0.created_at,h1_0.tag,h1_0.updated_at,h1_0.usage_count from hashtags h1_0 where h1_0.tag=?
2026-02-24 02:51:49.821 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
Hibernate: insert into hashtags (created_at,tag,updated_at,usage_count) values (?,?,?,?)
2026-02-24 02:51:49.853 [Test worker] [] [userId=anonymous] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
-
save()が自らトランザクションを開いてコミットしてしまいます - ログのどこにも例外関連のログがありません
- したがって、プロキシが生成されておらず、データが残っていることがわかります
解決策
- 解決策については次の記事に書きたいと思います



