Self-invocation: method trong cùng class gọi method khác cũng trong class đó → @Transactional bị bỏ qua.
Spring transaction hoạt động qua AOP proxy — proxy bọc bean, chặn call từ ngoài để bắt đầu/kết thúc transaction. Khi method A gọi method B trong cùng class, gọi là this.B() — đi thẳng vào object, không qua proxy → transaction annotation trên B không có hiệu lực.
@Service
class OrderService {
public void processOrders(List<Long> ids) {
ids.forEach(id -> processOne(id)); // ❌ self-invocation!
}
@Transactional
public void processOne(Long id) {
// Transaction KHÔNG bắt đầu vì gọi qua this
}
}Cách fix:
1. Tách ra service khác:
@Service class OrderProcessor {
@Transactional
public void processOne(Long id) { ... }
}
// OrderService inject OrderProcessor2. Self-inject (hack, không khuyến nghị):
@Service
class OrderService {
@Lazy @Autowired
private OrderService self; // inject proxy của chính mình
public void processOrders(List<Long> ids) {
ids.forEach(id -> self.processOne(id)); // qua proxy ✅
}
}Nguyên tắc: @Transactional chỉ hoạt động với public method và call từ bên ngoài bean.
Đây cũng là lý do tương tự @Async, @Cacheable bị bỏ qua khi self-invoke.
Self-invocation: a method calling another method in the same class → @Transactional is ignored.
Spring transactions work via an AOP proxy — the proxy wraps the bean, intercepting calls from outside to start/end a transaction. When method A calls method B in the same class, it calls this.B() — directly on the object, bypassing the proxy → the transaction annotation on B has no effect.
@Service
class OrderService {
public void processOrders(List<Long> ids) {
ids.forEach(id -> processOne(id)); // ❌ self-invocation!
}
@Transactional
public void processOne(Long id) {
// Transaction DOES NOT start — called via this
}
}Fixes:
1. Extract to another service:
@Service class OrderProcessor {
@Transactional
public void processOne(Long id) { ... }
}
// OrderService injects OrderProcessor2. Self-inject (hack, not recommended):
@Service
class OrderService {
@Lazy @Autowired
private OrderService self; // injects own proxy
public void processOrders(List<Long> ids) {
ids.forEach(id -> self.processOne(id)); // goes through proxy ✅
}
}Rule: @Transactional only works on public methods called from outside the bean.
The same self-invocation issue applies to @Async, @Cacheable, and other AOP-based annotations.