## Summary
Self-invocation occurs when a method within a Spring bean calls another `@Transactional` method in the same bean. Despite using CGLIB proxies for transaction interception, the transactional behavior fails because the call bypasses the proxy layer, executing directly on the target instance.
## Root Cause
- Spring's proxy-based AOP creates wrappers around beans to intercept method calls.
- Self-invocation uses the internal `this` reference instead of the proxied instance.
- The proxy remains uninvolved when methods within the same bean call each other.
- Transaction interceptors (like `TransactionInterceptor`) never receive the call.
## Why This Happens in Real Systems
- Logical grouping encourages co-location of related methods in one class.
- Refactoring moves transactional methods into existing service classes.
- Over-reliance on annotations obscures underlying proxy mechanics.
- Feature expansion introduces self-calls incrementally without AOP validation.
## Real-World Impact
- Data integrity violations from partial database updates.
- Missing transaction isolation (e.g., dirty reads due to unprotected queries).
- Silent failures where exceptions trigger rollback failures.
- Uncovered edge cases in production during logical rollback attempts.
- Increased debugging cycles due to non-obvious proxy behavior.
## Example or Code
```java
@Service
public class PaymentService {
public void processPayment(PaymentData data) {
validate(data); // Self-invocation → bypasses @Transactional
}
@Transactional
public void validate(PaymentData data) {
// DB call requiring atomicity → runs WITHOUT transaction
}
}
How Senior Engineers Fix It
-
Extract Transactional Logic: Move
@Transactionalmethods to separate beans:@Service public class ValidationService { @Transactional public void validate(PaymentData data) { /* ... */ } } @Service public class PaymentService { @Autowired ValidationService validator; public void processPayment(PaymentData data) { validator.validate(data); } } -
AspectJ Mode: Replace proxy-based AOP with bytecode weaving via:
spring.aop.proxy-target-class=false spring.aop.auto=true -
Self-Injection Workaround (occasionally):