Spring事务和事务传播机制
Spring 编程式事务(Programmatic Transaction)
在 Spring 中,事务管理有两种方式:声明式事务和编程式事务。声明式事务使用 @Transactional 注解,而编程式事务是通过 PlatformTransactionManager 手动控制事务的开始、提交和回滚。
编程式事务适合复杂业务逻辑或者动态选择事务行为的场景。
1. 核心概念
- PlatformTransactionManager:事务管理器接口,Spring 提供
DataSourceTransactionManager(JDBC)、JpaTransactionManager(JPA)、HibernateTransactionManager(Hibernate)等实现。 - TransactionDefinition:事务定义,用来指定事务的隔离级别、传播行为、超时时间等。
- TransactionStatus:事务状态对象,用于提交或回滚事务。
2. Maven/Gradle 依赖
以 Spring Boot + JDBC 为例:
<!-- Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
3. 配置事务管理器
Spring Boot 自动配置了 DataSourceTransactionManager,如果是普通 Spring,则可以手动配置:
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
4、Spring 编程式事务示例
1. 使用 TransactionTemplate
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate; // 事务模板
@Autowired
private UserMapper userMapper; // 数据操作接口
/**
* 注册用户示例
* @param name 用户名
*/
public void registerUser(String name) {
// execute 方法会自动开启事务
transactionTemplate.execute(status -> {
try {
// 插入用户
userMapper.insert(name);
// 模拟异常回滚
if(name.equals("error")) {
status.setRollbackOnly(); // 手动回滚事务
}
} catch (Exception e) {
// 捕获异常也可以手动回滚
status.setRollbackOnly();
}
return null; // 可返回操作结果,这里不需要
});
}
}
2. 使用 PlatformTransactionManager
@Service
public class UserService2 {
@Autowired
private PlatformTransactionManager txManager; // 事务管理器
@Autowired
private UserMapper userMapper; // 数据操作接口
/**
* 手动控制事务示例
* @param name 用户名
*/
public void registerUserManual(String name) {
// 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); // 隔离级别
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 传播行为
// 开启事务
TransactionStatus status = txManager.getTransaction(def);
try {
// 执行数据库操作
userMapper.insert(name);
// 模拟异常回滚
if(name.equals("error")) {
throw new RuntimeException("模拟异常");
}
// 提交事务
txManager.commit(status);
} catch (Exception e) {
// 回滚事务
txManager.rollback(status);
}
}
}
5. 总结
- 使用
@Autowired直接注入事务管理器和TransactionTemplate,无需写构造方法。 TransactionTemplate.execute()简洁易用,自动管理事务开启和提交。PlatformTransactionManager更灵活,可手动控制事务的提交和回滚。- 都可动态设置事务的隔离级别和传播行为。
| 使用方式 | 特点 |
|---|---|
TransactionTemplate | 简洁、易用,事务模板负责开启和提交事务,适合简单逻辑 |
PlatformTransactionManager | 灵活、可动态控制事务属性,适合复杂逻辑或动态事务需求 |
Spring 声明式事务 @Transactional
1. 基本概念
@Transactional 注解可以用于 类或方法,主要作用是:
- 在方法执行前自动开启事务
- 方法执行完成后自动提交事务
- 方法执行中如果出现异常(未捕获),自动回滚事务
推荐在 Service 层 使用,而不是 Controller 层。
@Service
@Transactional
public class UserService {
// 所有 public 方法默认都有事务
}
2. 基本使用
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional
public void registryUser(String name, String password) {
userInfoMapper.insert(name, password);
}
}
说明:
- 默认只对
RuntimeException或Error回滚 - 非运行时异常(Checked Exception)不会回滚
3. rollbackFor 与异常回滚
3.1 默认行为
@Transactional
public void registryUser(String name, String password) throws IOException {
userInfoMapper.insert(name, password);
throw new IOException(); // Checked Exception
}
- 默认不会回滚,因为
IOException不是 RuntimeException - 数据会被提交
3.2 指定回滚异常
@Transactional(rollbackFor = Exception.class)
public void registryUser(String name, String password) throws IOException {
userInfoMapper.insert(name, password);
throw new IOException(); // 数据会回滚
}
说明:
rollbackFor:指定出现哪些异常时事务回滚- 可以指定多个异常:
rollbackFor = {IOException.class, SQLException.class}
4. 事务隔离级别 isolation
控制事务对其他事务的可见性,Spring 中对应 MySQL 四种常用隔离级别:
| Spring 属性 | 含义 | MySQL 对应 |
|---|---|---|
| DEFAULT | 使用数据库默认隔离级别 | - |
| READ_UNCOMMITTED | 读未提交,可能出现脏读 | READ UNCOMMITTED |
| READ_COMMITTED | 读已提交,防脏读 | READ COMMITTED |
| REPEATABLE_READ | 可重复读,防脏读、不可重复读 | REPEATABLE READ(MySQL 默认) |
| SERIALIZABLE | 串行化,防幻读,性能低 | SERIALIZABLE |
@Transactional(isolation = Isolation.READ_COMMITTED)
public void registryUser(String name, String password) {
userInfoMapper.insert(name, password);
}
1. @Transactional(isolation = Isolation.DEFAULT)
-
@Transactional:开启事务。 -
isolation = Isolation.DEFAULT:使用数据库默认隔离级别。- 意思是:事务隔离级别由数据库本身决定(MySQL 默认
REPEATABLE_READ)。 - 优点是简单、兼容性好,不用额外指定。
- 缺点是对脏读、不可重复读等现象的控制依赖数据库默认设置。
- 意思是:事务隔离级别由数据库本身决定(MySQL 默认
2. @Transactional(isolation = Isolation.READ_UNCOMMITTED)
-
isolation = Isolation.READ_UNCOMMITTED:读未提交(Read Uncommitted)。-
意思是:当前事务可以读取其他事务未提交的数据。
-
可能出现 脏读(Dirty Read):
- 例如事务 A 插入一条用户记录还没提交,事务 B 读到了这条数据;如果 A 回滚,B 就读到了“错误数据”。
-
优点:性能最高,因为不加锁限制。
-
缺点:数据不可靠,一般不推荐在业务系统中使用。
-
3. @Transactional(isolation = Isolation.READ_COMMITTED)
-
@Transactional:表示这个方法开启事务,方法执行过程要么全部成功,要么失败回滚。 -
isolation = Isolation.READ_COMMITTED:读已提交(Read Committed)。-
意思是:当前事务只能读取其他事务已经提交的数据。
-
可以防止 脏读(Dirty Read):
- 脏读:一个事务读到了另一个事务未提交的数据,如果对方回滚,你读到的数据就“脏了”。
-
Spring 的
Isolation对应 MySQL 隔离级别:
READ_UNCOMMITTED→ 可能脏读READ_COMMITTED→ 防脏读REPEATABLE_READ→ 防脏读 + 防不可重复读(MySQL 默认)SERIALIZABLE→ 最严格,防幻读,性能低
4. @Transactional(isolation = Isolation.REPEATABLE_READ)
-
isolation = Isolation.REPEATABLE_READ:可重复读(Repeatable Read)。-
意思是:同一事务中多次读取同一条记录,结果是一致的。
-
防止脏读和 不可重复读(Non-Repeatable Read):
- 不可重复读:同一事务中,第一次读取到的数据,第二次读取可能被其他事务修改。
-
MySQL 默认隔离级别。
-
缺点:可能出现 幻读(Phantom Read),即一次查询发现新增或删除的数据行。
-
5. @Transactional(isolation = Isolation.SERIALIZABLE)
-
isolation = Isolation.SERIALIZABLE:串行化(Serializable)。- 意思是:事务完全串行执行,任何事务都会对其他事务“看不到的方式”执行。
- 可以防止 脏读、不可重复读和幻读,最严格的隔离级别。
- 优点:数据最安全。
- 缺点:性能低,容易锁表,吞吐量下降,一般只在特殊场景使用。
5. 事务传播机制 propagation
事务传播机制决定了一个方法被另一个方法调用时,事务如何处理。
| 类型 | 含义 |
|---|---|
| REQUIRED | 默认。如果存在事务,加入当前事务;否则新建事务 |
| REQUIRES_NEW | 总是新建事务,如果有当前事务,挂起当前事务 |
| NESTED | 嵌套事务,有保存点;父事务回滚,子事务也回滚;子事务回滚不影响父事务 |
| SUPPORTS | 如果存在事务,就加入事务;否则非事务方式执行 |
| MANDATORY | 必须在事务中执行,如果没有事务,抛异常 |
| NOT_SUPPORTED | 以非事务方式执行,如果有事务,挂起事务 |
| NEVER | 以非事务方式执行,如果有事务,抛异常 |
1. REQUIRED(默认传播)
@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample() {
userInfoMapper.insert("Alice", "123");
logService.requiredExample(); // 子方法共享同一事务
}
@Transactional(propagation = Propagation.REQUIRED)
public void requiredExampleChild() {
logInfoMapper.insertLog("REQUIRED: 写日志");
int a = 10 / 0; // 异常,整个事务回滚
}
说明:
- 父方法和子方法共享同一个事务。
- 如果子方法出现异常,整个事务都会回滚,包括父方法插入的数据。
- 场景适合业务逻辑需要整体成功或失败的情况。
2. REQUIRES_NEW(新建事务)
@Transactional(propagation = Propagation.REQUIRED)
public void requiresNewExample() {
userInfoMapper.insert("Bob", "123");
logService.requiresNewExample(); // 新事务失败,不影响父事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExampleChild() {
logInfoMapper.insertLog("REQUIRES_NEW: 写日志");
int a = 10 / 0; // 异常,只回滚子事务
}
说明:
- 父方法和子方法各自拥有独立事务。
- 子方法出现异常只回滚自己事务,不影响父方法事务。
- 场景适合日志记录或审计操作,即使失败也不影响主业务数据提交。
3. NESTED(嵌套事务)
@Transactional(propagation = Propagation.REQUIRED)
public void nestedExample() {
userInfoMapper.insert("Carol", "123");
logService.nestedExample(); // 嵌套事务
}
@Transactional(propagation = Propagation.NESTED)
public void nestedExampleChild() {
logInfoMapper.insertLog("NESTED: 写日志");
int a = 10 / 0; // 子事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
说明:
- 子事务在父事务中作为“嵌套事务”运行。
- 子事务回滚只影响自己,不影响父事务提交。
- 如果父事务出错,父事务回滚时子事务也会回滚。
- 场景适合部分操作失败可独立回滚,但父事务仍可继续。
4. SUPPORTS(存在事务就加入,否则非事务执行)
@Transactional(propagation = Propagation.REQUIRED)
public void supportsExample() {
userInfoMapper.insert("Eve", "123");
logService.supportsExample(); // 子方法随父事务执行
}
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExampleChild() {
logInfoMapper.insertLog("SUPPORTS: 写日志");
}
说明:
- 父事务存在 → 子方法参与事务;父事务不存在 → 子方法非事务执行。
- 场景适合可选事务操作,根据父事务存在与否决定是否参与。
5. MANDATORY(必须在事务中执行)
@Transactional(propagation = Propagation.REQUIRED)
public void mandatoryExample() {
userInfoMapper.insert("Frank", "123");
logService.mandatoryExample(); // 父事务存在,子方法执行正常
}
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExampleChild() {
logInfoMapper.insertLog("MANDATORY: 写日志");
}
说明:
- 子方法必须在事务中执行,否则会抛异常。
- 场景适合关键业务操作必须依赖事务,避免非事务调用。
6. NOT_SUPPORTED(以非事务方式执行)
@Transactional(propagation = Propagation.REQUIRED)
public void notSupportedExample() {
userInfoMapper.insert("Grace", "123");
logService.notSupportedExample(); // 子方法以非事务执行
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExampleChild() {
logInfoMapper.insertLog("NOT_SUPPORTED: 写日志");
int a = 10 / 0; // 异常不会回滚父事务
}
说明:
- 子方法不使用事务,如果父方法有事务,会被挂起。
- 子方法异常不会影响父事务(单独操作数据库)。
- 适合日志记录、统计、读操作等不希望被父事务回滚影响的场景。
7. NEVER(不允许事务)
@Transactional(propagation = Propagation.NEVER)
public void neverExample() {
userInfoMapper.insert("David", "123"); // 外部有事务会报异常
}
说明:
- 方法不允许事务存在,如果调用时外部已有事务,会抛异常。
- 场景适合非事务操作,如只做简单查询或日志记录,不希望被事务管理。
6. 其他常用属性
| 属性 | 含义 |
|---|---|
timeout | 超时时间,单位秒,超过则回滚 |
readOnly | 只读事务,优化性能 |
noRollbackFor | 指定哪些异常不回滚 |
示例:
@Transactional(timeout = 5, readOnly = true, noRollbackFor = NullPointerException.class)
public void readOnlyMethod() {
userInfoMapper.selectAll();
}
7. 总结
-
声明式事务最常用,在 Service 层添加
@Transactional -
异常控制事务回滚:
- 默认只回滚 RuntimeException / Error
rollbackFor可回滚任意异常- 捕获异常会导致事务提交 → 可用
setRollbackOnly()回滚
-
隔离级别控制脏读、不可重复读、幻读
-
传播机制控制方法间事务的加入、新建或嵌套
-
NESTED vs REQUIRED:嵌套事务可部分回滚

5104

被折叠的 条评论
为什么被折叠?



