Почему @Transactional не работает при self-invocation?
4. AspectJ → байт-код weaving (сложно) 5. Избегайте self-invocation 6. Проверяйте через TransactionSynchronizationManager
🟢 Junior Level
Self-invocation — когда метод вызывает другой метод ТОГО ЖЕ класса.
@Service
public class UserService {
public void outer() {
inner(); // ❌ @Transactional НЕ сработает!
}
@Transactional
public void inner() { }
}
Почему: Spring создал Proxy вокруг объекта, но вызов inner() идёт напрямую через this, минуя Proxy!
🟡 Middle Level
Как работает Proxy
[ Клиент ] → [ Proxy ] → [ TransactionInterceptor ] → [ Target ]
↓
[ outer() ]
↓
[ inner() ] ← мимо Proxy!
Внешний вызов (работает):
Клиент → Proxy.inner() → TransactionInterceptor → Target.inner()
Self-invocation (НЕ работает):
Target.outer() → this.inner() → Target.inner() ← Proxy пропущен!
Почему CGLIB не помогает
CGLIB создаёт подкласс, но:
→ В CGLIB прокси — это подкласс. Когда внешний код вызывает метод, он идёт через MethodInterceptor. Но когда метод вызывает другой метод того же класса через `this`, вызов идёт напрямую к коду оригинального метода, минуя interceptor.
Какие ещё аннотации не работают
@Async → не асинхронно
@Cacheable → не кэширует
@Validated → не валидирует
@Retryable → не повторяет
→ Все на базе AOP Proxy!
🔴 Senior Level
Диагностика
// Проверка наличия транзакции:
TransactionSynchronizationManager.isActualTransactionActive();
// В дебаггере:
// Прокси: UserService$$EnhancerBySpringCGLIB$$abc123
// Target: UserService
Память: JDK vs CGLIB Proxy
JDK Proxy:
→ com.sun.proxy.$Proxy123
→ Реализует интерфейсы
CGLIB:
→ UserService$$EnhancerBySpringCGLIB$$abc123
→ Наследует класс
Production Experience
Реальный сценарий: частая причина багов
Разработчики пишут:
@Transactional метод вызывает другой @Transactional метод
→ Внутренний @Transactional игнорируется
→ Данные сохраняются без транзакции
→ Баг обнаруживается только в production
Self-invocation — одна из самых частых причин, почему @Transactional «не работает» в реальных проектах.
Решение: Code Review на self-invocation
Best Practices
- Разделяйте на разные бины
- TransactionTemplate → программное управление
- Self-injection → @Lazy + ObjectProvider
- AspectJ → байт-код weaving (сложно)
- Избегайте self-invocation
- Проверяйте через TransactionSynchronizationManager
Резюме для Senior
- Proxy перехватывает только ВНЕШНИЕ вызовы
- this.method() → напрямую, мимо Proxy
- CGLIB не помогает (this = Target)
- Self-invocation — частая причина, почему @Transactional «не работает»
- TransactionSynchronizationManager → для проверки
- Решение: разные бины или TransactionTemplate
🎯 Шпаргалка для интервью
Обязательно знать:
- Self-invocation = метод вызывает другой метод ТОГО ЖЕ класса
- Spring AOP работает через прокси, но
this.method()идёт напрямую, минуя прокси - Это касается ВСЕХ AOP-аннотаций: @Transactional, @Async, @Cacheable, @Validated, @Retryable
- CGLIB тоже не помогает — внутри Target-объекта
thisссылается на Target, а не на подкласс-прокси - Проверить наличие транзакции: TransactionSynchronizationManager.isActualTransactionActive()
- Одна из самых частых причин «не работает @Transactional» в реальных проектах
- Диагностика: в дебаггере прокси =
$$EnhancerBySpringCGLIB$$, target = обычный класс
Частые уточняющие вопросы:
- Почему CGLIB не решает проблему? → CGLIB перехватывает вызовы извне, но
this.method()внутри Target идёт напрямую - Какие ещё аннотации не работают при self-invocation? → Все AOP-based: @Async, @Cacheable, @Validated, @Retryable
- Как обнаружить self-invocation в коде? → Code Review, вызов метода того же класса внутри @Transactional-метода
- Когда баг проявляется? → В production, когда данные сохраняются без ожидаемой транзакции
Красные флаги (НЕ говорить):
- «CGLIB решает self-invocation» — не решает, this = Target
- «@Transactional на private-методах работает» — Spring AOP только для public
- «Это редкая проблема» — одна из самых частых причин багов
- «Достаточно поставить @Transactional на класс» — self-invocation всё равно не работает
Связанные темы:
- [[13. Что такое прокси в Spring]]
- [[17. Что делает аннотация @Transactional]]
- [[19. Как решить проблему с self-invocation]]