Чому @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. Що таке proxy в Spring]]
- [[17. Що робить анотація @Transactional]]
- [[19. Як вирішити проблему з self-invocation]]