Spring事务传播行为
Spring 的事务传播机制(Transaction Propagation)定义了多个事务方法相互调用时 , 事务如何传播与协作的规则。Spring 提供了 7 种传播行为,通过 Propagation[ˌprɒpə'ɡeɪʃ(ə)n] 枚举定义,常用的是前两种。
7中行为
| 传播行为 | 含义 | 说明 |
|---|---|---|
| required | 如果当前存在事务,则加入;否则新建一个事务。 | 最常用,适合大多数业务场景。 |
| requires_new | 总是新建一个事务,如果当前存在事务,则挂起当前事务。 | 新事务独立提交/回滚,适合隔离性强的方法。 |
| supports | 如果当前存在事务,则加入;否则以非事务方式执行。 | 不强制事务,适合查询类操作。 |
| not_supported | 以非事务方式执行,如果当前存在事务,则挂起当前事务。 | 明确不使用事务。 |
| mandatory | 必须在已有事务中运行,否则抛出异常。 | 强制依赖外层事务。 |
| never | 必须在非事务环境中运行,否则抛出异常。 | 禁止事务。 |
| nested | 如果当前存在事务,则在嵌套事务中执行;否则新建事务。 | 嵌套事务可独立回滚,但依赖数据库支持(如 JDBC savepoint)。 |
REQUIRED 实例
下面给出 Spring REQUIRED 的“最小可运行”例子,一条 SQL 就能看见效果。
复制到任何 Spring Boot 工程(含 jdbc + h2)即可直接跑通。
- 依赖(已有可跳过)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
- 建表脚本(resources/schema.sql)
CREATE TABLE IF NOT EXISTS t_log(
id INT PRIMARY KEY,
msg VARCHAR(100)
);
- 业务代码(一个类搞定)
package demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RequiredDemoService {
@Autowired
private JdbcTemplate jdbc;
// 外层事务( REQUIRED )
@Transactional
public void outer() {
jdbc.update("INSERT INTO t_log(id,msg) VALUES (1,'outer')");
inner(); // 调用同事务的方法
}
// 内层事务( REQUIRED )——加入当前事务
@Transactional
public void inner() {
jdbc.update("INSERT INTO t_log(id,msg) VALUES (2,'inner')");
// 模拟运行时异常
throw new RuntimeException("inner error");
}
}
- 单元测试(看回滚效果)
package demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
class RequiredTest {
@Autowired
private RequiredDemoService service;
@Autowired
private JdbcTemplate jdbc;
@Test
void requiredRollback() {
int before = jdbc.queryForObject("SELECT COUNT(*) FROM t_log", Integer.class);
try {
service.outer(); // 两个 INSERT + 异常
} catch (RuntimeException ignored) {}
int after = jdbc.queryForObject("SELECT COUNT(*) FROM t_log", Integer.class);
// 因为共用同一个事务,异常后两条都回滚
assertEquals(before, after); // 0 == 0
}
}
-
运行测试
-
直接跑
RequiredTest。 -
日志关键行:
Creating new transaction Executing SQL: INSERT INTO t_log(id,msg) VALUES (1,'outer') Executing SQL: INSERT INTO t_log(id,msg) VALUES (2,'inner') Rolling back -
结论:
内外方法共用同一个事务,inner()抛异常 → 两条 INSERT 全部回滚,数据保持 0 条。
REQUIRES_NEW 实例
下面给出 Spring REQUIRES_NEW 的“最小可运行”例子。
目标:外层方法有事务,内层方法用 REQUIRES_NEW 自己新开一个事务;
内层回滚不影响外层,外层最终仍能提交。
- 依赖(同前面示例,可跳过)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
- 建表脚本(resources/schema.sql)
CREATE TABLE IF NOT EXISTS t_pay(
id INT PRIMARY KEY,
amount INT
);
- 业务代码(一个类搞定)
package demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RequiresNewDemoService {
@Autowired
private JdbcTemplate jdbc;
// 外层事务
@Transactional
public void outer() {
jdbc.update("INSERT INTO t_pay(id,amount) VALUES (1,100)");
try {
inner(); // 挂起当前事务,新建独立事务
} catch (RuntimeException e) {
// 捕获,让外层继续
}
// 外层继续提交
}
// 内层:REQUIRES_NEW —— 完全独立事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
jdbc.update("INSERT INTO t_pay(id,amount) VALUES (2,200)");
throw new RuntimeException("inner fail");
}
}
- 单元测试(看“部分提交”效果)
package demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
class RequiresNewTest {
@Autowired
private RequiresNewDemoService service;
@Autowired
private JdbcTemplate jdbc;
@Test
void requiresNewCommit() {
service.outer();
int rows = jdbc.queryForObject("SELECT COUNT(*) FROM t_pay", Integer.class);
// 外层提交 1 条,内层回滚 0 条
assertEquals(1, rows);
}
}
-
运行测试
-
直接跑
RequiresNewTest。 -
日志关键行:
Creating new transaction for outer Suspending current transaction Creating new transaction for inner Rolling back inner transaction Resuming suspended transaction Committing outer transaction -
结论:
内层独立事务回滚,不影响外层事务提交;数据库最终只剩id=1这一条记录。
nested 和 requires_new区别
nested:在大部分的数据库中,一段SQL语句中可以设置一个标志位,运行后面的SQL语句时如果有问题,只回滚到这个标志位的数据状态,而不会让这个标志位之前的SQL语句也回滚。这个标志位在数据库概念中被称为保存点save point)。
虽然 NESTED 和 REQUIRES_NEW 都能“开出一条新执行路径”,但事务边界、回滚范围、提交时机、对外可见性完全不同,使用效果并不等价,选型错误会直接破坏数据一致性。
- 事务从属关系
- NESTED:子事务,仍是外层事务的组成部分,必须等外层最终 commit 才算真正落地。
- REQUIRES_NEW:完全独立的兄弟事务,自己先 commit/rollback,与外层无先后依赖。
- 回滚粒度
- NESTED 异常时只 rollback to savepoint,外层可继续运行并最终提交 → 实现“局部失败”。
- REQUIRES_NEW 异常时 整事务回滚,外层如果 catch 住异常,不影响外层;但外层 rollback 时不会牵连它(因为它已先提交)。
- 数据可见性
- NESTED 在内层“提交”后,其他并发事务仍看不到,因为外层还没 commit。
- REQUIRES_NEW 一旦内层 commit,立即对外可见(隔离级别允许的前提下)。
- 资源开销
- NESTED 仅创建一个 JDBC Savepoint,轻量。
- REQUIRES_NEW 要 挂起/获取连接、新建事务上下文,成本更高。
- 典型误用案例
- 想用 REQUIRES_NEW 做“可回滚的试算”,结果内层一提交,外层再回滚也追不回来,造成脏数据。
- 把 NESTED 当“独立日志”用,外层回滚后子事务也被全盘回滚,日志照样丢失。