Why @Transactional Does Not Work with Self-Invocation?
4. AspectJ -> bytecode weaving (complex) 5. Avoid self-invocation 6. Check via TransactionSynchronizationManager
Junior Level
Self-invocation - when a method calls another method of the SAME class.
@Service
public class UserService {
public void outer() {
inner(); // @Transactional will NOT work!
}
@Transactional
public void inner() { }
}
Why: Spring created a Proxy around the object, but the call to inner() goes directly via this, bypassing the Proxy!
Middle Level
How Proxy Works
[ Client ] -> [ Proxy ] -> [ TransactionInterceptor ] -> [ Target ]
|
[ outer() ]
|
[ inner() ] <- around proxy!
External call (works):
Client -> Proxy.inner() -> TransactionInterceptor -> Target.inner()
Self-invocation (does NOT work):
Target.outer() -> this.inner() -> Target.inner() <- Proxy skipped!
Why CGLIB Does Not Help
CGLIB creates a subclass, but:
-> In CGLIB proxy - it is a subclass. When external code calls a method, it goes through MethodInterceptor. But when a method calls another method of the same class via `this`, the call goes directly to the original method code, bypassing the interceptor.
Which Other Annotations Do Not Work
@Async -> not asynchronous
@Cacheable -> not caching
@Validated -> not validating
@Retryable -> not retrying
-> All based on AOP Proxy!
Senior Level
Diagnostics
// Check for transaction:
TransactionSynchronizationManager.isActualTransactionActive();
// In debugger:
// Proxy: UserService$$EnhancerBySpringCGLIB$$abc123
// Target: UserService
Memory: JDK vs CGLIB Proxy
JDK Proxy:
-> com.sun.proxy.$Proxy123
-> Implements interfaces
CGLIB:
-> UserService$$EnhancerBySpringCGLIB$$abc123
-> Inherits class
Production Experience
Real scenario: frequent cause of bugs
Developers write:
@Transactional method calls another @Transactional method
-> Inner @Transactional is ignored
-> Data saved without transaction
-> Bug discovered only in production
Self-invocation is one of the most common reasons why @Transactional "doesn't work" in real projects.
Solution: Code Review for self-invocation
Best Practices
- Split into different beans
- TransactionTemplate -> programmatic control
- Self-injection -> @Lazy + ObjectProvider
- AspectJ -> bytecode weaving (complex)
- Avoid self-invocation
- Check via TransactionSynchronizationManager
Summary for Senior
- Proxy intercepts only EXTERNAL calls
- this.method() -> directly, around Proxy
- CGLIB does not help (this = Target)
- Self-invocation - frequent reason @Transactional “doesn’t work”
- TransactionSynchronizationManager -> for checking
- Solution: different beans or TransactionTemplate
Interview Cheat Sheet
Must know:
- Self-invocation = a method calls another method of the SAME class
- Spring AOP works via proxy, but
this.method()goes directly, bypassing proxy - This affects ALL AOP annotations: @Transactional, @Async, @Cacheable, @Validated, @Retryable
- CGLIB also does not help - inside Target object,
thisrefers to Target, not to the proxy subclass - Check for transaction: TransactionSynchronizationManager.isActualTransactionActive()
- One of the most common reasons “@Transactional doesn’t work” in real projects
- Diagnostics: in debugger proxy =
$$EnhancerBySpringCGLIB$$, target = regular class
Common follow-up questions:
- Why doesn’t CGLIB solve the problem? -> CGLIB intercepts calls from outside, but
this.method()inside Target goes directly - Which other annotations do not work with self-invocation? -> All AOP-based: @Async, @Cacheable, @Validated, @Retryable
- How to detect self-invocation in code? -> Code Review, calling a method of the same class inside a @Transactional method
- When does the bug appear? -> In production, when data is saved without expected transaction
Red flags (DO NOT say):
- “CGLIB solves self-invocation” - does not solve, this = Target
- “@Transactional on private methods works” - Spring AOP only for public
- “This is a rare problem” - one of the most common causes of bugs
- “Just put @Transactional on the class” - self-invocation still does not work
Related topics:
- [[13. What is a proxy in Spring]]
- [[17. What does the Transactional annotation do]]
- [[19. How to solve the self-invocation problem]]