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!