Understanding Spring Transaction Propagation Mechanism
Spring transaction propagation is the core and most confusing concept in Spring transactions. It defines how transactions are passed and applied when a method with a transaction calls another method (whether it has or does not have a transaction, such as whether to create a new transaction, join an existing one, or suspend the current one).
Key premise: Propagation behavior applies only to method calls between Spring-managed beans (i.e., through proxy objects). Internal calls will still cause transaction failure (as previously mentioned).
I. Understand Core Concepts
The main issue addressed by transaction propagation: When method A (with a transaction) calls method B (with or without a transaction), how does B's transaction interact with A's transaction?
- Current transaction: The transaction context that exists in the calling method;
- New transaction: A new independent transaction created by the called method;
- No transaction: The called method does not use any transaction.
II. Spring Transaction Propagation Mechanism (7 Core Types)
Spring defines 7 propagation behaviors in the TransactionDefinition interface, categorized into three main types based on usage frequency and scenarios:
| Propagation Behavior Constant | Chinese Name | Core Rules (Key!) | Typical Use Cases |
|---|---|---|---|
| **REQUIRED** (default) | Must have a transaction | If present, join; if not, create a new one | Most business scenarios (insert, delete, update) |
| **SUPPORTS** | Supports transaction | If present, join; if not, no transaction | Query methods (optional transaction) |
| **MANDATORY** | Mandatory transaction | If present, join; if not, throw exception | Core operations that must be executed within a transaction |
| **REQUIRES_NEW** | Create new transaction | Regardless of presence, create a new transaction, suspending the current one | Logging, asynchronous notifications (independent transaction) |
| **NOT_SUPPORTED** | Not suppported transaction | Regardless of presence, execute without transaction, suspending the current one | Memory-only operations (no transaction needed) |
| **NEVER** | Prohibits transaction | If present, throw exception; if not, no transaction | Operations that must not run within a transaction |
| **NESTED** | Nested transaction | If present, nest (savepoint); if not, create new | Sub-transaction not affected by main transaction rollback (rarely used) |
Detailed Explanation (With Code Examples)
1. Most Common: REQUIRED (Default)
Core Logic: If the caller has a transaction, it joins; otherwise, it creates a new one. This is the default propagation behavior in Spring and is chosen in 90% of daily development scenarios.
@Service
public class OrderService {
@Autowired
private StockService stockService;
// Caller: has transaction
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
// 1. Save order (current transaction)
saveOrder();
// 2. Call inventory service (join current transaction)
stockService.reduceStock();
// 3. Any step fails, entire transaction rolls back
}
}
@Service
public class StockService {
// Called method: REQUIRED (default)
@Transactional
public void reduceStock() {
updateStock(); // Executed within the same transaction as creating the order
}
}
Effect:
- If
createOrderruns first (with a transaction),reduceStockjoins that transaction, both succeed/fail together; - If
reduceStockis called directly (without a transaction), a new transaction is created for execution.
2. Independent Transaction: REQUIRES_NEW
Core Logic: Regardless of whether the caller has a transaction, a new independent transaction is created, and the current trnasaction is suspended. After the new transaction completes, the original transaction resumes. This is suitable for scenarios where the sub-operation must be committed even if the main transaction fails (e.g., logging operations).
@Service
public class OrderService {
@Autowired
private LogService logService;
// Main transaction
@Transactional
public void createOrder() {
saveOrder(); // Main transaction operation
try {
// Call log service (new independent transaction)
logService.recordLog("Creating order");
} catch (Exception e) {
// Log recording failure does not affect the main transaction
}
// Main transaction rolls back, log transaction has already been committed and not rolled back
}
}
@Service
public class LogService {
// New independent transaction
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog(String content) {
insertLog(content); // Independent transaction, unrelated to the main transaction
}
}
Key Difference (REQUIRED vs REQUIRES_NEW):
| Scenario | REQUIRED | REQUIRES_NEW |
|---|---|---|
| Main transaction rollback | Sub-operations also roll back | Sub-operations have already been committed and do not roll back |
| Transaction isolation | Same transaction, shared locks/data | Independent transaction, no impact on each other |
3. Other Common Propagation Behaviors
- SUPPORTS: If there is a transaction, join it; if not, no transaction. Suitable for query methods (e.g.,
getOrderById), using it if the outer layer has a transaction, but can also execute without it; - MANDATORY: Must be executed within a transaction, otherwise throws
IllegalTransactionStateException. For example, core financial operations, which require the outer layer to have a transaction; - NOT_SUPPORTED: Execute without a transaction regardless of whether there is one. For example, pure cache operations, which do not need a transaction;
- NEVER: Prohibits transactions, throwing an exception if present. For example, certain sensitive non-transactional operations;
- NESTED: Nested transaction (based on database savepoints), where the main transaction rolling back to a savepoint allows the sub-transaction to roll back independently. Rarely used (depends on database support, such as MySQL InnoDB).
III. Classic Scenario Comparison (Help You Choose)
| Business Scenario | Recommended Propagation Behavior | Reason |
|---|---|---|
| Order creation + inventory deduction | REQUIRED | Both succeed/fail to gether |
| Order creation + operation log recording | REQUIRES_NEW | Log must be saved, unaffected by the main transaction |
| Querying order details only | SUPPORTS | Use transaction if available, otherwise query directly |
| Core fund transfer operation | MANDATORY | Requires the outer layer to have a transaction, avoid unsecured execution |
| Pure memory calculation/cache update | NOT_SUPPORTED | No transaction needed, improves performance |
IV. Pitfalls to Avoid
- Propagation mechanism only works for proxy calls: Internal calls within the same class (e.g.,
A.method1()callingA.method2()) will not trigger the propagation mechanism, still following the rule of "internal calls leading to transaction failure"; - REQUIRES_NEW may cause locking issues: Creating a new transaction holds a database lock. If the main transaction takes too long, it may lead to lock waiting;
- Nested transactions (NESTED) depend on the database: Only supported by databases with savepoints (such as MySQL/Oracle), and Spring's support for it is limited. Prefer using REQUIRES_NEW instead;
- Exception handling affects propagation effect: If the sub-transaction throws an exception but is caught by the main transaction, manually set rollback (
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()) to avoid inconsistent transaction states.
V. Practical Verification Methods
To verify whether the propagation mechanism is effective, two methods are recommended:
- Log debugging: Enable Spring transaction logs to view logs about transaction creation, suspension, and commit:
<!-- logback configuration -->
<logger name="org.springframework.transaction" level="DEBUG"/>
<logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager" level="DEBUG"/>
- Database lock verification: Add
Thread.sleep(10000)in the method, and check the transaction status of the database connection usingshow processlist.