文章目录
- Ⅰ. Spring中事务的实现
- 一、编程式事务(了解)
- 二、声明式事务:`@Transactional`
- @Transactional 的作用
- Ⅱ. @Transactional 详解
- 一、异常回滚属性 `rollbackFor`
- 二、Spring事务隔离级别 `Isolation`
- 三、Spring事务传播机制 `Propagation`
- 1. 什么是事务传播机制
- 2. 事务的传播机制有哪些
- 💥`nested` 和 `required` 的区别
- Ⅲ. Spring 事务的失效问题
- 一、调用方式不对(自调用)💥
- 二、方法不是 `public`💥
- 三、异常没抛出💥
- 四、数据库引擎不支持事务
- 五、多线程调用💥
- 六、配置问题
- 七、手动设置了传播属性
- ✅ 快速排查清单
Ⅰ. Spring中事务的实现
Spring中的事务操作分为两类:
- 编程式事务(手动实现事务)
- 声明式事务(利用注解自动开启事务)
一、编程式事务(了解)
Spring 手动操作事务和 MySQL 操作事务类似,有三个重要操作步骤:
- 开启事务(获取事务)
- 提交事务
- 回滚事务
SpringBoot 内置了两个对象:
DataSourceTransactionManager:事务管理器,用来获取事务(开启事务),提交或回滚事务的TransactionDefinition:事务的属性,在获取事务的时候需要将TransactionDefinition传递进去从而获得一个事务TransactionStatus
代码实现:
importcom.example.demo.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.jdbc.datasource.DataSourceTransactionManager;importorg.springframework.transaction.TransactionDefinition;importorg.springframework.transaction.TransactionStatus;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;@RequestMapping("/user")@RestControllerpublicclassUserController{// 注入事务管理器@AutowiredprivateDataSourceTransactionManagerdataSourceTransactionManager;// 注入事务属性@AutowiredprivateTransactionDefinitiontransactionDefinition;@AutowiredprivateUserServiceuserService;@RequestMapping("/registry")publicStringregistry(Stringname,Stringpassword){// 开启事务TransactionStatustransactionStatus=dataSourceTransactionManager.getTransaction(transactionDefinition);// 用户注册userService.registryUser(name,password);// 提交事务dataSourceTransactionManager.commit(transactionStatus);// 回滚事务//dataSourceTransactionManager.rollback(transactionStatus);return"注册成功";}}二、声明式事务:@Transactional
添加依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId></dependency>在需要事务的方法上添加
@Transactional注解就可以实现了。无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务。@RequestMapping("/trans")@RestControllerpublicclassTransactionalController{@AutowiredprivateUserServiceuserService;@Transactional@RequestMapping("/registry")publicStringregistry(Stringname,Stringpassword){// 用户注册userService.registryUser(name,password);return"注册成功";}}
@Transactional 的作用
- 修饰方法时:只有修饰
public方法时才生效(修饰其他方法时不会报错,也不生效)[推荐] - 修饰类时:对
@Transactional修饰的类中所有的public方法都生效。
💥注意事项:
- 如果在方法执行过程中,出现异常,且异常未被捕获,就进行事务回滚操作。
- 如果异常被程序捕获,方法就被认为是成功执行,依然会提交事务。
下面对异常进行捕获:
@Transactional@RequestMapping("/registry")publicStringregistry(Stringname,Stringpassword){userService.registryUser(name,password);// 模拟用户注册功能log.info("用户数据插入成功");// 对异常进行捕获try{// 强制程序抛出异常inta=10/0;}catch(Exceptione){e.printStackTrace();}return"注册成功";}运行程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。
如果需要事务进行回滚,有以下两种方式:
重新抛出异常
@Transactional@RequestMapping("/registry")publicStringregistry(Stringname,Stringpassword){userService.registryUser(name,password);log.info("用户数据插入成功");// 对异常进行捕获try{inta=10/0;}catch(Exceptione){// 将异常重新抛出去throwe;}return"注册成功";}手动回滚事务:使用
TransactionAspectSupport.currentTransactionStatus()得到当前的事务,并使用setRollbackOnly设置回滚。@Transactional@RequestMapping("/registry")publicStringregistry(Stringname,Stringpassword){userService.registryUser(name,password);log.info("用户数据插入成功");// 对异常进行捕获try{inta=10/0;}catch(Exceptione){// 手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return"注册成功";}
Ⅱ. @Transactional 详解
这里主要介绍注解中三个常见属性:
rollbackFor:异常回滚属性。指定能够触发事务回滚的异常类型。可以指定多个异常类型Isolation:事务的隔离级别。默认值为Isolation.DEFAULTPropagation:事务的传播机制。默认值为Propagation.REQUIRED
一、异常回滚属性rollbackFor
@Transactional默认只在遇到RuntimeException和Error时才会回滚,而非运行时异常不回滚。
如果我们需要所有异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过这个属性指定出现何种异常类型时事务进行回滚。
// Controller 层@RequestMapping("/r2")publicStringr2(Stringname,Stringpassword)throwsIOException{userService.registryUserWithTx(name,password);return"r2";}// Service 层@Transactional(rollbackFor=Exception.class)publicvoidregistryUserWithTx(Stringname,Stringpassword)throwsIOException{userRepository.save(newUser(name,password));log.info("用户数据插入成功");if(true){thrownewIOException();// 模拟异常}}发现虽然程序抛出了异常,但是事务依然进行了回滚!
二、Spring事务隔离级别Isolation
| 隔离级别 | 含义 |
|---|---|
| Isolation.DEFAULT | 以连接的数据库的事务隔离级别为主 |
| Isolation.READ_UNCOMMITTED | 读未提交 |
| Isolation.READ_COMMITTED | 读已提交 |
| Isolation.REPEATABLE_READ | 可重复读 |
| Isolation.SERIALIZABLE | 串行化 |
它的实现如下所示:
publicenumIsolation{DEFAULT(-1),READ_UNCOMMITTED(1),READ_COMMITTED(2),REPEATABLE_READ(4),SERIALIZABLE(8);privatefinalintvalue;privateIsolation(intvalue){this.value=value;}publicintvalue(){returnthis.value;}}Spring 中事务隔离级别可以通过@Transactional中的isolation属性进行设置:
@Transactional(isolation=Isolation.READ_COMMITTED)@RequestMapping("/r3")publicStringr3(Stringname,Stringpassword)throwsIOException{//... 代码省略return"r3";}三、Spring事务传播机制Propagation
1. 什么是事务传播机制
事务传播机制就是:多个事务方法存在调用关系时,事务是如何在这些方法间进行传播的。
比如有两个方法A,B都被@Transactional修饰,A方法调用B方法
A方法运行时,会开启一个事务。当A调用B时,B方法本身也有事务,此时B方法运行时,是加入A的事务呢,还是创建一个新的事务呢?
这就涉及到了事务的传播机制。
事务隔离级别解决的是多个事务同时调用一个数据库的问题。
事务传播机制解决的是一个事务在多个方法之间传递的问题。
2. 事务的传播机制有哪些
@Transactional注解支持事务传播机制的设置,通过propagation属性来指定传播行为。
| 传播机制 | 作用 |
|---|---|
Propagation.REQUIRED | 默认的事务传播级别。如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务。 |
Propagation.SUPPORTS | 如果当前存在事务,则加入该事务。如果当前没有事务,则以非事务的方式继续运行。 |
Propagation.MANDATORY | 如果当前存在事务,则加入该事务。如果当前没有事务,则抛出异常。 |
Propagation.REQUIRES_NEW | 如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。 |
Propagation.NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起,不使用它。 |
Propagation.NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常。 |
Propagation.NESTED | 如果当前存在事务,则创建一个事务作为当前事务的子事务来运行。如果当前没有事务,则创建一个新的事务。 |
实现类如下所示:
publicenumPropagation{REQUIRED(0),SUPPORTS(1),MANDATORY(2),REQUIRES_NEW(3),NOT_SUPPORTED(4),NEVER(5),NESTED(6);privatefinalintvalue;privatePropagation(intvalue){this.value=value;}publicintvalue(){returnthis.value;}}💥nested和required的区别
- 整个事务如果全部执行成功,二者的结果是一样的。
- 如果事务部分执行成功
REQUIRED加入事务会导致整个事务全部回滚。NESTED嵌套事务可以实现局部回滚,不会影响上一个方法中执行的结果。
嵌套事务之所以能够实现部分事务的回滚,是因为事务中有一个保存点(savepoint)的概念,嵌套事务进入之后相当于新建了一个保存点,而滚回时只回滚到当前保存点。
REQUIRED是加入到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚,这就是嵌套事务和加入事务的区别!
Ⅲ. Spring 事务的失效问题
一、调用方式不对(自调用)💥
- 场景:在同一个类里,一个方法调用另一个加了
@Transactional的方法。 - 原因:Spring 事务是通过代理实现的,内部方法调用不会走代理,事务不会生效。
- 解决:把需要事务的方法放到另一个类里,或通过 AopContext 获取代理调用。
- 具体可以参考《黑马点评》中的优惠券一人一单问题。
二、方法不是public💥
- Spring 默认只对
public方法生效。 - 如果
@Transactional标注在private、protected或default方法上,事务不会生效。
三、异常没抛出💥
默认回滚规则:Spring 只会对运行时异常(RuntimeException 或其子类)回滚。
如果你捕获异常后没抛出,事务不会回滚。
如果抛的是受检异常(如
IOException),默认也不会回滚。解决:
重新抛出运行时异常。
或在注解里指定
rollbackFor:@Transactional(rollbackFor=Exception.class)
四、数据库引擎不支持事务
- MySQL 如果表引擎是 MyISAM,不支持事务,事务自然无效。
- 必须用 InnoDB 才能支持。
五、多线程调用💥
- 如果事务方法里开了新线程,线程里的操作不受当前事务控制。
- 解决:避免在事务方法里直接用多线程,或者手动管理事务。
六、配置问题
@EnableTransactionManagement没配置。- 事务管理器没有正确注入。
七、手动设置了传播属性
- 如果事务传播属性设置不当(如
PROPAGATION_NEVER),也会导致失效。
✅ 快速排查清单
- 方法必须是
public - 确保是代理调用(不是同类内调用)
- 确保异常能抛出,没被吞掉
- 数据库表必须支持事务
- 确认 Spring 的事务配置启用