SoFunction
Updated on 2025-04-14

Analysis and solution to the reason why @Transactional annotation does not take effect in Spring transactions

1. Introduction

In the Spring framework, the @Transactional annotation is the core way to manage database transactions. However, many developers encounter a common problem when using it: In the same class, a transaction does not take effect when one method calls another method with the @Transactional annotation. This phenomenon is called transaction self-call failure.

This article will deeply analyze the underlying principles of transaction self-calling, explain why transactions do not take effect, and provide a variety of solutions to help developers correctly use Spring transaction management.

2. Transaction self-call problem reappears

2.1 Sample Code

@Service
public class OrderService {

    public void placeOrder(Order order) {
        checkInventory(order);       // Check inventory (non-transaction)        deductInventory(order);      // Deduct inventory (transaction method)        createOrder(order);          // Create an order (non-transaction)    }

    @Transactional
    public void deductInventory(Order order) {
        ((), ());
    }
}

In this example:

placeOrder() is a business method that calls deductInventory().

deductInventory() is marked @Transactional, expecting transactions to be started when deducting inventory.

2.2 Problem phenomenon

When placeOrder() calls deductInventory(), the transaction does not take effect. If deductInventory() throws an exception, the database operation will not roll back.

3. Why does transaction self-call fail?

3.1 Agent mechanism for Spring transactions

Spring's transaction management is implemented based on AOP (sectional programming), specifically:

  • Proxy mode: Spring will generate a proxy object (JDK dynamic proxy or CGLIB proxy) for a class with @Transactional.
  • Interceptor: A proxy object adds transaction management logic before and after the target method is executed (such as opening a transaction, commit, or rollback).

3.2 Self-call bypass proxy

public void placeOrder(Order order) {
    (order);  // Call directly without proxy}

When placeOrder() calls deductInventory(), it uses this (i.e. the current object), rather than a proxy object generated by Spring.

Therefore, the transaction interceptor is not triggered, and the transaction will naturally not take effect.

4. Solution

4.1 Method 1: Split into different categories (recommended)

Move the transaction method to another Service, making sure the call is made through the proxy:

@Service
public class OrderService {

    @Autowired
    private InventoryService inventoryService;

    public void placeOrder(Order order) {
        checkInventory(order);
        (order);  // Call via proxy        createOrder(order);
    }
}

@Service
public class InventoryService {

    @Transactional
    public void deductInventory(Order order) {
        ((), ());
    }
}

advantage:

  • Comply with the Single Responsibility Principle (SRP).
  • The transaction management is clear and there will be no self-calling problems.

4.2 Method 2: Use ()

If you have to call it yourself, you can get the current proxy object:

@Service
public class OrderService {

    public void placeOrder(Order order) {
        checkInventory(order);
        ((OrderService) ()).deductInventory(order);  // Call via proxy        createOrder(order);
    }

    @Transactional
    public void deductInventory(Order order) {
        ((), ());
    }
}

Notice:

Need to enable @EnableAspectJAutoProxy(exposeProxy = true):

@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class MyApp {
    public static void main(String[] args) {
        (, args);
    }
}

shortcoming:

Relying on the Spring AOP mechanism, the code is highly invasive.

4.3 Method 3: Add @Transactional to the calling method

If the entire process requires transaction management, you can directly add @Transactional to placeOrder():

@Service
public class OrderService {

    @Transactional
    public void placeOrder(Order order) {
        checkInventory(order);
        deductInventory(order);  // Even if it is called, the outer transaction still takes effect        createOrder(order);
    }

    public void deductInventory(Order order) {
        ((), ());
    }
}

Applicable scenarios:

The entire method requires transaction management, not a single operation.

4.4 Method 4: Use Programmatic Transactions

If you cannot split the classification or modify the proxy settings, you can use TransactionTemplate:

@Service
public class OrderService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void placeOrder(Order order) {
        checkInventory(order);
        (status -> {
            deductInventory(order);  // Execute within a transaction            return null;
        });
        createOrder(order);
    }

    public void deductInventory(Order order) {
        ((), ());
    }
}

advantage:

More flexible, allowing manual control of transaction boundaries.

5. Best Practices

Avoid self-calling: Try to split transaction methods into different classes.

Use transactions reasonably: Do not perform time-consuming operations (such as HTTP requests, IO operations) in transaction methods.

Transaction propagation mechanism: Understand options such as @Transactional(propagation = ).

Exception handling: Ensure that exceptions can trigger rollback (default only RuntimeException).

6. Summary

plan Applicable scenarios advantage shortcoming
Split into different categories Recommended plan Comply with SRP, clear transactions Requires additional classes
() When it must be called by itself Can solve the problem of self-calling Strongly invasive
External method plus @Transactional The entire process requires transactions Simple and direct Expand the scope of transactions
Programming transactions Need for fine control flexible Code redundancy

Key Conclusion:

  • Spring transactions are based on proxy, and self-calls will bypass the proxy, causing transactions to expire.
  • The best solution is to split the transaction method to different classes to avoid self-calling problems.

This is the article about the reasons why @Transactional annotation does not take effect in Spring transactions. This is all about this article. For more related contents of Spring @Transactional annotation not taking effect, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!