Spring 事务详解:从基础到传播机制的实践指南
文章目录
- 📕1. 事务回顾
- 📕2. Spring 中事务的实现
- ✏️2.1 Spring 编程式事务(了解)
- ✏️2.2 Spring声明式事务
- 📕3. @Transactional 详解
- ✏️3.1 rollbackFor
- ✏️3.2 事务隔离级别
- 🔖3.2.1 MySQL 事务隔离级别(回顾)
- 🔖3.2.2 Spring 事务隔离级别
- 📕4. Spring 事务传播机制
- ✏️4.1 什么是事务传播机制
- ✏️4.2 事务的传播机制有哪些
特此注明 :
Designed By :长安城没有风
Version:1.0
Time:2025.08.09
Location:辽宁 · 阜新
📕1. 事务回顾
什么是事务?
事务是一组操作的集合,是一个不可分割的操作。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求,所以这组操作要么同时成功,要么同时失败。
为什么需要事务?
⽐如转账操作 :
第⼀步:A账⼾-100元
第⼆步:B账⼾+100元
如果没有事务,第⼀步执⾏成功了,第⼆步执⾏失败了,那么A账⼾的100元就平⽩⽆故消失了,如果使⽤事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败。
比如秒杀系统:
第⼀步:下单成功
第⼆步:扣减库存
下单成功后,库存也需要同步减少,如果下单成功,库存扣减失败,那么就会造成下单超出的情况。所以就需要把这两步操作放在同⼀个事务中,要么⼀起成功,要么⼀起失败。(理解事务概念为主,实际企业开发中,并不是简单的通过事务来处理。)
事务的操作
事务的操作主要有三步:
- 开启事务:starttransaction/begin(⼀组操作前开启事务)
- 提交事务:commit(这组操作全部成功,提交事务)
- 回滚事务:rollback(这组操作中间任何⼀个操作出现异常,回滚事务)
📕2. Spring 中事务的实现
✏️2.1 Spring 编程式事务(了解)
Spring 编程式事务依赖以下核心接口:
- DataSourceTransactionManager : 事务管理器,专门用于处理基于 DataSource 的事务(如 JDBC、MyBatis 等持久层框架)。它实现了 PlatformTransactionManager 接口,核心功能是获取事务(开启事务)、提交事务、回滚事务,是编程式事务和声明式事务的底层核心组件。
- TransactionDefinition:定义事务属性(隔离级别、传播行为、超时时间、是否只读等)。
- TransactionStatus:表示当前事务的状态(是否活跃、是否已完成、是否需要回滚等)。
package xulong.com.captcha.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;@RestController
@RequestMapping("/user")
public class UserController {//事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//定义事务属性@Autowiredprivate TransactionDefinition transactionDefinition;@Autowiredprivate UserMybatisPlusMapper userMybatisPlusMapper;@PostMapping("/register")public UserInfo register(@RequestBody UserInfo userInfo) {//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);try {//执行数据库操作userMybatisPlusMapper.insert(userInfo);//提交事务dataSourceTransactionManager.commit(transactionStatus);} catch (Exception e) {//回滚事务dataSourceTransactionManager.rollback(transactionStatus);}return userInfo;}
}
以上代码虽然可以实现事务,但是操作十分繁琐,各位了解即可,下面我们介绍声明式事务。
✏️2.2 Spring声明式事务
在需要事务的方法上添加 @Transactional 注解就可以实现了,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务。如果异常被程序捕获,⽅法就被认为是成功执⾏,依然会提交事务。
package xulong.com.captcha.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserMybatisPlusMapper userMybatisPlusMapper;@PostMapping("/register")@Transactionalpublic UserInfo register(@RequestBody UserInfo userInfo) {userMybatisPlusMapper.insert(userInfo);return userInfo;}
}
@Transactional 可以用来修饰方法或类 :
- 修饰⽅法时:只有修饰public⽅法时才⽣效(修饰其他⽅法时不会报错,也不⽣效)。
- 修饰类时:对 @Transactional 修饰的类中所有的public⽅法都⽣效。
运⾏程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。如果需要事务进⾏回滚,有以下两种⽅式 :
重新抛出异常
package xulong.com.captcha.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserMybatisPlusMapper userMybatisPlusMapper;@PostMapping("/register")@Transactionalpublic UserInfo register(@RequestBody UserInfo userInfo) {userMybatisPlusMapper.insert(userInfo);try{int i = 1/0;}catch (Exception e){throw new RuntimeException("注册失败");}return userInfo;}
}
手动回滚事务
使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并设置setRollbackOnly。
package xulong.com.captcha.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserMybatisPlusMapper userMybatisPlusMapper;@PostMapping("/register")@Transactionalpublic UserInfo register(@RequestBody UserInfo userInfo) {userMybatisPlusMapper.insert(userInfo);try{int i = 1/0;}catch (Exception e){TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return userInfo;}
}
📕3. @Transactional 详解
✏️3.1 rollbackFor
@Transactional 默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚。即 Exception的子类中,除了RuntimeException及其子类。
如果我们需要所有异常都回滚,需要来配置@Transactional 注解当中的rollbackFor 属性,通过rollbackFor 这个属性指定出现何种异常类型时事务进行回滚。
package xulong.com.captcha.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserMybatisPlusMapper userMybatisPlusMapper;@PostMapping("/register")@Transactional(rollbackFor = Exception.class)public UserInfo register(@RequestBody UserInfo userInfo) {userMybatisPlusMapper.insert(userInfo);try{int i = 1/0;}catch (Exception e){TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return userInfo;}
}
结论:
- 在Spring的事务管理中,默认只在遇到运行时异常RuntimeException和Error时才会回滚。
- 如果需要回滚指定类型的异常,可以通过rollbackFor属性来指定。
✏️3.2 事务隔离级别
🔖3.2.1 MySQL 事务隔离级别(回顾)
SQL 标准定义了四种隔离级别, MySQL 全都支持. 这四种隔离级别分别是:
- 读未提交(READ UNCOMMITTED) : 读未提交,也叫未提交读。该隔离级别的事务可以看到其他事务中未提交的数据。
因为其他事务未提交的数据可能会发生回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据,这个问题称之为脏读。
- 读已提交(READ COMMITTED) : 读已提交,也叫提交读。该隔离级别的事务能读取到已经提交事务的数据。
该隔离级别不会有脏读的问题,但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询可能会得到不同的结果,这种现象叫做不可重复读。
- 可重复读(REPEATABLE READ) : 事务不会读到其他事务对已有数据的修改,即使其他事务已提交。也就可以确保同一事务多次查询的结果一致,但是其他事务新插入的数据,是可以感知到的。这也就引发了幻读问题。可重复读,是 MySQL 的默认事务隔离级别。
比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是 一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这个现象叫幻读。
- 串行化(SERIALIZABLE) : 序列化,事务最高隔离级别。它会强制事务排序,使之不会发生冲突,从而解决了脏读,不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。
🔖3.2.2 Spring 事务隔离级别
Spring 中事务隔离级别有5 种:
- Isolation.DEFAULT :以连接的数据库的事务隔离级别为主。
- Isolation.READ_UNCOMMITTED :读未提交,对应SQL标准中 READ UNCOMMITTED
- Isolation.READ_COMMITTED :读已提交,对应SQL标准中 READ COMMITTED
- Isolation.REPEATABLE_READ :可重复读,对应SQL标准中 REPEATABLE READ
- Isolation.SERIALIZABLE :串⾏化,对应SQL标准中 SERIALIZABLE
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserMybatisPlusMapper userMybatisPlusMapper;@PostMapping("/register")@Transactional(rollbackFor = Exception.class,isolation = Isolation.REPEATABLE_READ)public UserInfo register(@RequestBody UserInfo userInfo) {return userInfo;}
}
事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题
📕4. Spring 事务传播机制
✏️4.1 什么是事务传播机制
事务传播机制就是 : 多个事务方法存在调用关系时,事务是如何在这些方法间进行传播的。
举例说明:比如有两个方法A, B都被@Transactional 修饰,A方法调用B方法。A方法运行时,会开启一个事务。当A调用B时,B方法本身也有事务,此时B方法运行时,是加入A的事务,还是创建一个新的事务呢?这个就涉及到了事务的传播机制。
⽐如公司流程管理,执⾏任务之前,需要先写执⾏⽂档,任务执⾏结束,再写总结汇报。
此时A部⻔有⼀项⼯作,需要B部⻔的⽀援,此时B部⻔是直接使⽤A部⻔的⽂档,还是新建⼀个⽂档呢?
事务传播机制解决的是一个事务在多个节点(方法)中传递的问题
✏️4.2 事务的传播机制有哪些
@Transactional 注解支持事务传播机制的设置,通过 propagation 属性来指定传播行为。
Spring 事务传播机制有以下 7 种 :
- Propagation.REQUIRED :默认的事务传播级别。如果当前存在事务,则加⼊该事务。如果当前没有事务,则创建⼀个新的事务。
- Propagation.SUPPORTS :如果当前存在事务,则加⼊该事务。如果当前没有事务,则以⾮事务的⽅式继续运⾏。
- Propagation.MANDATORY : 强制性。如果当前存在事务,则加⼊该事务,如果当前没有事务,则抛出异常。
- Propagation.REQUIRES_NEW :创建⼀个新的事务。如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
- Propagation.NOT_SUPPORTED :以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起(不⽤)。
- Propagation.NEVER :以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
- Propagation.NESTED : 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
比如一对新人要结婚了, 关于是否需要房子
- Propagation.REQUIRED :需要有房⼦。如果你有房,我们就⼀起住,如果你没房,我们就⼀起买房。(如果当前存在事务,则加⼊该事务。如果当前没有事务,则创建⼀个新的事务。)
- Propagation.SUPPORTS :可以有房⼦。如果你有房,那就⼀起住。如果没房,那就租房。(如果当前存在事务,则加⼊该事务。如果当前没有事务,则以⾮事务的⽅式继续运⾏。)
- Propagation.MANDATORY :必须有房⼦。要求必须有房,如果没房就不结婚。(如果当前存在事务,则加⼊该事务。如果当前没有事务,则抛出异常。)
- Propagation.REQUIRES_NEW :必须买新房,不管你有没有房,必须要两个⼈⼀起买房。即使有房也不住。(创建⼀个新的事务。如果当前存在事务,则把当前事务挂起。)
- Propagation.NOT_SUPPORTED :不需要房。不管你有没有房,我都不住,必须租房。(以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。)
- Propagation.NEVER :不能有房⼦。(以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。)
- Propagation.NESTED :如果你没房,就⼀起买房。如果你有房,我们就以房⼦为根据地,做点⽣意。(如果如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏。如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED )