DEV Community

Shubham Bhati
Shubham Bhati

Posted on

Your `@Transactional` Internal Call Is a Lie

Got a Spring service? You write a @Transactional method. Inside it, you call another private or protected method also annotated @Transactional. You expect separate transaction behavior or isolation. Here's the kicker: it often won't happen.

Spring's @Transactional magic relies on AOP proxies. When you call a method on a proxied bean from outside, the proxy intercepts it applying the transaction logic. But an internal call, like this.myPrivateTransactionalMethod(), bypasses the proxy entirely. This means the inner method's @Transactional annotation is simply ignored. It runs within the caller's transaction context or with no transaction at all if the caller wasn't transactional.

Consider this:

@Service
public class OrderService {

    @Transactional
    public void placeOrder(Order order) {
        // save order
        this.updateInventory(order.getItems()); // internal call
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void updateInventory(List<Item> items) {
        // update inventory counts
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, updateInventory will not get its own REQUIRES_NEW transaction. It's bound to placeOrder's transaction. If updateInventory fails, both operations roll back. The REQUIRES_NEW is effectively moot.

To correctly isolate transactions for such logic, extract it into a separate, Spring-managed component and inject it. Call the method on the injected component. Understand Spring's proxy mechanics; they're key.

Top comments (0)