
目录
[@Transactional 详解](#@Transactional 详解)
什么是事务
事务是一组操作的集合,是一个不可分割的操作。事务会把所有操作都视为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。
在前面数据库的学习中,对事务也是有一定的了解。
事务的特性(ACID):
- 原子性(Atomicity):事务中的所有操作必须作为一个不可分割的整体来执行,事务中的操作要么全部失败,要么全部成功。
- 一致性(Consistency):事务在完成时,必须使所有的数据都保持一致状态。事务可以保证数据库数据的一致性,避免了各种情况而导致数据库内容不一致。
- 隔离性(Isolation):事务处理过程中的中间状态对其他事务时透明的。事务隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
- 持续性(Durability):事务一旦提交,其结果就会被永久保存,即使发生系统故障也不会丢失。
事务的使用
事务的操作主要分为三步:
- 开启事务:start transaction/begin(一组操作前开启事务)
- 提交事务:commit(这组操作全部成功,提交事务)
- 回滚事务:rollback(这组操作中间任何一个操作出现异常,回滚事务)
Spring中事务的实现
那么在Spring中事务是如何实现的?
Spring中事务的操作分为两类:
- 编程式事务(手动写代码操作事务)
- 声明式事务(利用注解自动开启和提交事务)
事务的使用主要是通过操作数据库来体现的,我们先准备两张表:
sql
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARSET = utf8mb4;
USE trans_test;
-- 用户表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info(
`id` INT NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(128) NOT NULL,
`password` VARCHAR(128) NOT NULL,
`create_time` DATETIME DEFAULT NOW(),
`update_time` DATETIME DEFAULT NOW() ON UPDATE NOW(),
PRIMARY KEY (`id`)
)ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '用户表';
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`user_name` VARCHAR(128) NOT NULL,
`op` VARCHAR(256) NOT NULL,
`create_time` DATETIME DEFAULT NOW(),
`update_time` DATETIME DEFAULT NOW() on UPDATE NOW()
)DEFAULT CHARSET = utf8mb4;
需要配置一下Mybatis:
XML
spring.application.name: transtests
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false
username: root
password: 'root'
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/**Mapper.xml
为对应的表创建类:
java
package com.example.demo.model;
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String userName;
private Date createTime;
private Date updateTime;
}
java
package com.example.demo.model;
import lombok.Data;
import java.util.Date;
@Data
public class LogInfo {
private Integer id;
private String userName;
private String op;
private Date createTime;
private Date updateTime;
}
mapper层:
java
package com.example.demo.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(user_name,password) values(#{userName},#{password})")
Integer insert(String userName,String password);
}
java
package com.example.demo.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(user_name,op) values(#{userName},#{op})")
Integer insert(String userName,String op);
}
service层:
java
package com.example.demo.service;
import com.example.demo.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
public void registryUser(String userName,String password){
userInfoMapper.insert(userName,password);
}
}
java
package com.example.demo.service;
import com.example.demo.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
public void insertLogInfo(String userName,String op){
logInfoMapper.insert(userName,op);
}
}
controller层:
java
package com.example.demo.controller;
import com.example.demo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
@RequestMapping("/registry")
public String registry(String name, String password){
userInfoService.registryUser(name, password);
return "注册成功";
}
}
java
package com.example.demo.controller;
import com.example.demo.service.LogInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/log")
public class LogInfoController {
@Autowired
private LogInfoService logInfoService;
@RequestMapping("/insert")
public String insert(String userName, String op){
logInfoService.insertLogInfo(userName, op);
return "记录成功";
}
}
Spring编程式事务
在前面,我们已经知道了Spring事务有两种实现方式:
编程式事务和声明式事务。
我们先来了解一下编程式事务。
Spring编程式操作事务有三个重要步骤:
- 开启事务(获取事务)
- 提交事务
- 回滚事务
在Spring中如何获取事务呢?
我们需要使用到Spring中两个内置的对象:
- DataSourceTransactionManager :事务管理器。用来获取事务(开启事务),提交或回滚事务
- TransactionDefinition :事务的属性,在获取事务的时候需要将 TransactionDefinition 专递进去从而获取一个事务 TransactionStatus 。
java
package com.example.demo.controller;
import com.example.demo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserInfoController {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserInfoService userInfoService;
@RequestMapping("/registry")
public String registry(String name, String password){
//开启事务
TransactionStatus transactionStatus=dataSourceTransactionManager.getTransaction(transactionDefinition);
userInfoService.registryUser(name, password);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
return "注册成功";
}
}
我们来访问下这个接口:

查看数据表:

这个是事务提交成功的日志:

那么如果我们对事务进行回滚,日志会发生什么变化:
sql
//回滚事务
dataSourceTransactionManager.rollback(transactionStatus);

可以看到,能够注册成功,但数据表中有这条数据?

可以看到,并没有添加成功,就进行打开和关闭sqlsession的操作,没有commit。
我们可以看到,如果用编程式事务,操作是很繁琐的。
所以我们就需要来学习一下声明式事务。
声明式事务
声明式事务的实现很简单,我们只要加上注解 @Transactional 即可,无需手动打开和提交事务,在进入方法后,如果发生了未处理的异常,就会自动回滚事务。
事务管理其实也是AOP的一种实现。
首先我们需要添加依赖:
sql
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
接下来我们就可以来使用了。
sql
package com.example.demo.controller;
import com.example.demo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password){
userInfoService.registryUser(name, password);
return "注册成功";
}
}
我们来调用一下:


在数据表中查看是否有插入数据:
可以看到,能够正常的执行事务。
那么如果其中有异常的话,会怎么样?
sql
int i = 1/0;

可以看到,并没有提交,我们看下数据表:

数据表中并没有我们插入的数据。这说明在发生运行时异常的时候,就会将事务进行回滚。
@Transactional注解作用
@Transactional注解不仅能够修饰方法,还能够修饰类:
- 修饰方法 :只有修饰public方法的时候才会生效(修饰其他权限的方法的时候不会报错,但也不会生效)
- 修饰类 :对 @Transactional 修饰的类中的所有public方法都生效
当方法/类被 @Transactional 注解修饰时,在目标方法执行前就会自动开启事务,方法执行结束之后,自动提交事务 。如果在方法执行过程中,出现异常,且异常未被捕获,就会进行事务回滚操作。如果异常被程序捕获,方法就被认为是执行成功,依然会提交事务。
我们通过例子来讲解:
sql
try{
int i = 1/0;
}catch (Exception e){
e.printStackTrace();
}



可以看到,在程序中捕获异常后,如果没有抛出,就会认为是执行成功,就会提交事务。
那么为了防止这种情况,我们在程序捕获后将异常抛出。
sql
try{
int i = 1/0;
}catch (Exception e){
throw new RuntimeException("注册失败");
}

可以看到,将异常抛出后,事务就不会提交了。
此外还有一种方法,就是手动进行回滚:
sql
try{
int i = 1/0;
}catch (Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
@Transactional 详解
既然我们已经知道了@Transactional注解的基本使用,那么我们就接着深入学习一下@Transactional注解。

可以看到,@Transactional注解的属性有很多个,但我们只讲其中三个关键的:
rollbackFor :异常回滚属性 。指定能够触发事务回滚的异常类型 ,默认可以发生事务回滚的异常类型是 RuntimeException 及其子类以及 Error ,可以指定多个异常类型
Isolation :事务的隔离级别 。默认值为 Isolation.DEFAULT
propagation :事务的传播机制 。默认值为 Propagation.REQUIRED
rollbackFor
@Transactional 默认只会在发生运行时异常和Error 的时候才会发生事务的回滚操作。

假如我们抛出的异常是非运行时异常:
sql
if(true){
throw new IOException();
}

可以看到,当抛出的异常是非运行异常时,即时将异常抛出,也不会发生事务的回滚。
当如果我们想要指定,发生非运行异常的时候也能进行事务回滚 ,就需要配置 @Transactional 注解中的 rollbackFor 属性:
sql
@Transactional(rollbackFor = Exception.class)


可以看到,在我们指定发生回滚的异常后,非运行异常也能触发回滚操作。
事务的隔离级别
在mysql时,我们知道事务有四种隔离级别:
- 读未提交:也叫未提交读。该隔离级别的事务可以看到其他事务中未提交的数据。会出现脏读、不可重复读、幻读问题。
- 读已提交:也叫提交读。该隔离级别的事务能够读取到已经提交事务的数据。会出现不可重复读、脏读问题。
- 可重复读:事务不会读到凄然事务对已有数据的修改,即时其他事务已提交,也可以确保同一事物多次查询的结构一致,但是其他事务新插入的数据,是可以感知到的,这就引发了幻读问题。(可重复读是MySQL的默认事务隔离机制)
- 串行化:也叫序列化。最高的事务隔离级别,通过强制事务串行执行,避免了脏读、不可重复读和幻读问题。但这种级别的性能开销较大,因为事务必须串行执行。
脏读指的是一个事务读取了另一个事务尚未提交的数据。
假如有现在有一个银行账户表,有两个账户1,1有1000元,账户2有2000元。现在事务1和事务2,事务1将账户1的余额从1000改为1500,此时事务2开始查询账户1,读取到1500(这就是脏数据),事务1执行回滚,恢复账户1的余额1000元。事务2再次查询,就会发现此时账户1的余额是1000,而不是1500。简单来说:事务2读取了事务1未提交的数据,而事务1回滚了,导致事务2读到的数据是无效的。
不可重复读指的是一个事务在两次读取同一数据时,由于其他事务的修改,导致读取的结果不一致。
假如现在有一个员工表,员工1的工资是5000元,有两个事务1和2,事务1查询员工1的工资,读到5000元,此时事务2把员工1的工资从5000改为6000,并提交事务,事务1再次查询员工1的工资,就会发现变成了6000。简单来说:事务1在两次查询中读取到了不同的数据,这是因为事务2修改了数据并提交。这种现象就称为不可重复读。
幻读指的是一个事务在两次查询中,由于其他事务的插入或者删除操作,导致查询结果的行数不同。
假如现在有一个订单表,订单1的状态为待支付,此时有两个事务1和2,事务1查询所有待支付的订单,发现就只有1条,事务2此时插入一条新的"待支付"的数据并提交。事务1再次查询,就会发现"待支付"的数据有2条。这就是幻读。
|------------|--------|-----------|--------|
| 事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
| 读未提交 | √ | √ | √ |
| 读已提交 | × | √ | √ |
| 可重复读 | × | × | √ |
| 串行化 | × | × | × |
随着隔离级别的提高,效率也会变低。
Spring事务隔离级别
在Spring中隔离级别对应的有5种:
- Isolation.DEFAULT :以连接的数据库事务隔离级别为主;
- Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准的 READ UNCOMMIT;
- Isolation.READ_COMMITTED:读已提交,对应SQL标准的 READ COMMITTED;
- Isolation.REPEATABLE_READ:可重复读,对应SQL标准中的REPEATABLE READ;
- Isolation.SERIALIZABLE:串行化,对应SQL标准的SERIALIZABLE.

Spring中隔离级别的配置需要配置 @Transactional 注解的 isolation属性:
sql
//rollbackFor=Exception.class 表示遇到异常回滚 isolation = Isolation.REPEATABLE_READ 表示隔离级别为可重复读
@Transactional(rollbackFor = Exception.class,isolation = Isolation.REPEATABLE_READ)
Spring事务传播机制
什么是事务传播机制?
Spring 事务传播机制(Transaction Propagation)定义了在多个事务方法相互调用时,事务应该如何传播或行为 。简单来说,它决定了当一个事务方法调用另一个事务方法时,事务是继续使用当前的事务,还是创建一个新的事务,或者以非事务的方式运行。
我们前面学的事务隔离级别主要解决的是多个事务同时调用同一个数据库的问题

而事务传播机制主要解决的是一个事务在多个节点(方法)中传递的问题

事务传播机制种类
spring中事务传播机制有7种:
-
Propagation.REQUIRED:默认的事务传播机制。如果当前存在事务,则加入该事务;如果没有事务,则创建一个新事务
-
Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果不存在事务,则以非事务的方式继续运行
-
Propagation.MANDATORY:强制性。如果当前存在事务,则加入该事务,如果不存在,则抛出异常
-
Propagation.REQUIRES_NEW:创建一个新的事务。如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法都是新开始自己的事务,且开启的事务相互独立,互不干扰
-
Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起(不用)
-
Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
-
Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 Propagation.REQUIRED

这几个事务隔离机制,我们可以用通俗一点的说法来理解,以一对新人要结婚为例。
- REQUIRED :结婚需要有房子,如果你有房子,我们就一起住,如果你没有房,我们就一起买房。(如果当前存在事务,就加入该事务,如果当前没有事务,则创建一个新的事务)
- SUPPORTS :可以有房子,如果你有房子,那就一起住,如果没有房,就租房(如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务的方式继续运行)
- MANDATORY :必须要有房子,要求必须有房,如果没有房就不结婚。(如果当前存在事务,就加入该事务,如果当前没有事务,就抛出异常)
- REQUIRES_NEW :必须买新房,不管你有没有房,必须两个人一起买房,即使有房也不住。(创建一个新的事务,如果当前存在事务,则把当前事务挂起)
- NOT_SUPPORTED :不需要房子。不管你有没有房,我都不住,必须租房。(以非事务方式运行,如果当前存在事务,则把当前事务挂起)
- NEVER :不能有房子。(以非事务方式运行,如果当前存在事务,则抛出异常)
- NESTED:如果你没有房,就一起买房,如果你有房子,我们就以房子为根据地,做点下生意。(如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务,则该取值等价于REQUIRED)
这里我们只演示以上两种:
- REQUIRED(默认值)
- REQUIRES_NEW
sql
package com.example.demo.controller;
import com.example.demo.service.LogInfoService;
import com.example.demo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
@RequestMapping("/user")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
@Autowired
private LogInfoService logInfoService;
//rollbackFor=Exception.class 表示遇到异常回滚 isolation = Isolation.REPEATABLE_READ 表示隔离级别为可重复读
@Transactional(rollbackFor = Exception.class,isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED)
@RequestMapping("/registry")
public String registry(String name, String password) {
userInfoService.registryUser(name, password);
logInfoService.insertLogInfo(name, "注册");
return "注册成功";
}
}
sql
package com.example.demo.service;
import com.example.demo.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String userName,String password){
userInfoMapper.insert(userName,password);
}
}
sql
package com.example.demo.service;
import com.example.demo.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertLogInfo(String userName,String op){
logInfoMapper.insert(userName,op);
}
}
我们运行一下:

可以看到这里插入两个表的时候用的是同一个事务。
我们来制造一个异常:
sql
if(true){
throw new IOException();
}

通过观察日志以及数据表,我们可以看到,如果发生异常,事务都会进行回滚。
我们使用REQUIRES_NEW隔离级别:
需要将service层和controller层都改为这个隔离级别
sql
@Transactional(rollbackFor = Exception.class,isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW)
再来试上面的那个异常:



可以看到,这里虽然报了异常,但是数据还是会插入,说明使用 REQUIRES_NEW 传播机制,事务都是相互独立的,互不影响。
这样的想的话,其他几个隔离级别我们都能够理解。
如果是 NEVER,那么只要存在事务就会抛出异常。
NESTED(嵌套事务)
我们将上面的service层改为NESTED传播机制。
sql
package com.example.demo.service;
import com.example.demo.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLogInfo(String userName,String op){
logInfoMapper.insert(userName,op);
}
}
sql
package com.example.demo.service;
import com.example.demo.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void registryUser(String userName,String password){
userInfoMapper.insert(userName,password);
}
}
sql
package com.example.demo.controller;
import com.example.demo.service.LogInfoService;
import com.example.demo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
@RequestMapping("/user")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
@Autowired
private LogInfoService logInfoService;
//rollbackFor=Exception.class 表示遇到异常回滚 isolation = Isolation.REPEATABLE_READ 表示隔离级别为可重复读
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) throws IOException {
userInfoService.registryUser(name, password);
logInfoService.insertLogInfo(name, "注册");
return "注册成功";
}
}
测试一下:

可以看到使用数据表插入共用一个事务。
如果其中有一个出现异常会怎么样?
我们来试下,在其中一个插入异常:
sql
package com.example.demo.service;
import com.example.demo.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLogInfo(String userName,String op){
logInfoMapper.insert(userName,op);
if(true){
throw new RuntimeException("异常");
}
}
}

可以看到,如果其中一个事务出现了异常,那么所有的事务都会回滚。
这不是跟REQUIRED传播机制一样?这样看确实,但是还是有差别的。
如果我们将出现异常的事务单独回滚:
sql
package com.example.demo.service;
import com.example.demo.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogInfoService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLogInfo(String userName,String op){
logInfoMapper.insert(userName,op);
if(true){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
再试一次:


可以看到,使用NESTED隔离机制,如果对出现异常的事务单独进行回滚操作,对其他事务操作并不会有影响。
那么我们改成REQUIRED 试试:

可以看到,整个事务都进行了回滚。
REQUIRED 和 NESTED 传播机制的区别
- 如果事务全部执行成功,二者的结果是一样的;
- 如果事务一部分执行成功, REQUIRED 加入事务会导致整个事务回滚,NESTED 嵌套事务可以实现局部回滚,不会影响上一个方法的执行结果。

以上就是本篇所有内容~
若有不足,欢迎指正~