自学内容网 自学内容网

【JavaEE】Spring事务和事务的传播机制

一.回顾数据库事务

1.1什么是事务?

  • 事务是一组操作的集合, 是一个不可分割的操作. 事务会把所有的操作作为一个整体, 一起向数据库提交或者是撤销操作请求. 所以这组操作要么同时成功(同时完成就会提交), 要么同时失败(失败就会回滚).

  • 事务通常用于数据库管理系统中,以确保数据的完整性和一致性。在多个应用程序并发访问数据库时,事务能够防止这些操作互相干扰,从而避免数据错误。事务具有四个基本特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),简称ACID特性。

    • 原子性:事务作为一个整体,要么全部执行成功,要么全部不执行。如果其中一个操作失败,所有操作将回滚至事务开始前的状态。例如,在银行转账过程中,如果从一个账户扣款成功但另一个账户入账失败,则整个转账操作将回滚,确保不会出现部分完成的情况。
    • 一致性:事务必须使数据从一个一致的状态转换为另一个一致的状态。无论事务是否成功提交,数据库都不能处于不一致的状态。例如,在转账例子中,不论过程如何,两个账户的余额总和应保持不变。
    • 隔离性:一个事务的操作对其他并发事务是透明的,即一个事务在提交之前,其所做的修改对其他事务不可见。这通过并发控制的锁机制或多版本并发控制(MVCC)实现,防止脏读、不可重复读和幻读等问题。
    • 持久性:事务一旦提交,其对数据库的修改将永久保存下来,即使系统发生故障也不会影响已提交事务的结果。这通常通过重做日志(redo log)来保障。

1.2为什么需要事务?

  • 我们在进行程序开发时, 也会有事务的需求.
    • 比如转账操作:
      第一步:A 账户 -100 元.
      第二步:B 账户 +100 元.
      如果没有事务,第一步执行成功了, 第二步执行失败了, 那么A 账户的100 元就平白无故消失了. 如果使用事务就可以解决这个问题, 让这一组操作要么一起成功, 要么一起失败.
    • 比如秒杀系统,
      第一步: 下单成功
      第二步: 扣减库存
      下单成功后, 库存也需要同步减少. 如果下单成功, 库存扣减失败, 那么就会造成下单超出的情况. 所以就需要把这两步操作放在同一个事务中. 要么一起成功, 要么一起失败.

1.3 事务的操作

  • 事务的操作主要有三步:
    1. 开启事start transaction/ begin (一组操作前开启事务)
    2. 提交事务: commit (这组操作全部成功, 提交事务)
    3. 回滚事务: rollback (这组操作中间任何一个操作出现异常, 回滚事务)
-- 开启事务
start transaction;
-- 提交事务
commit;
-- 回滚事务
rollback;

1.4 MySQL事务隔离 级别

  • SQL标准定义了四种隔离级别,MySQL全都支持,这四种隔离级别分别是:

    • 1.读未提交(READ UNCOMMITTED): 读未提交也叫未提交读, 该隔离级别的事务可以看到其他事务中未提交的数据.

    它发生在一个事务读取了另一个尚未提交的事务所做的修改数据时。如果这个未提交的事务最终回滚,那么第一个事务读取的数据就是“脏”的,即从未在数据库中真正存在过的数据。

    • 2.读提交(READ COMMITTED): 读已提交也叫提交读, 该隔离级别的事务能读取到已经提交事务的数据.

    该种隔离级别不会出现脏读的情况,到那时由于在事务的执行过程中可以读取到其他事务提交的结果,所以在不同时间的相同SQL查询可能会得到不同的结果,这种现象叫做不可重复读.

    • 3.可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改(就是A事务如果修改数据, 那么B 事务读取不到A事务修改的那个数据), 也就是可以确保同一事物多次查询的结果是相同的, 但是如果A事务插入新插入数据或者删除满足查询条件的数据,B事务是可以感知到的,这也就可能造成幻读的问题. 可重复读是MySQL的默认事务隔离级别

    比如此隔离级别的事务A正在执行时, 另一个事务B成功的插入了某条数据(相当于B事务创建了一个新的变量X), 但因为它每次查询的结果都是一样的, 所以会导致查询不到这条数据, 自己(A事务)重复插入时(相当于A事务想再创建这个变量就创建不成功, 因为唯一约束的原因), 明明在事务中查询不到这条信息, 但自己就是插入不进去,这个现象叫做幻读

    • 4.串行化(SERIALIZABLE): 也叫做序列化, 事务最高的隔离级别. 它会强制事务排序, 使之不会发生冲突, 从而解决了脏读, 不可重复读和幻读的问题, 但因为执行的效率低, 所以真正使用的场景并不多.

在这里插入图片描述

二.Spring中的事务实现.

  • 在Spring中事务的操作分为了两类:

    • 编程式事务( 通过手动写代码,完成事务的相关操作 )
    • 声明式事务( 利用注解自动开启和提交事务 )
  • 为了完成上述的操作的演示, 现在有如下需求:

    • 用户注册, 注册时在日志表中插入一条操作记录

2.1编程式事务

  • Spring 手动操作事务和上面 MySQL 操作事务类似, 有 3 个重要操作步骤:

    • 开启事务(获取事务)
    • 提交事务
    • 回滚事务
  • SpringBoot 内置了两个对象:

    1. DataSourceTransactionManager 事务管理器. 用来获取事务(开启事务), 提交或回滚事务
    2. TransactionDefinition 是事务的属性, 在获取事务的时候需要TransactionDefinition 传递进去从而获得一个事务 TransactionStatus

代码实例:

@RequestMapping("/user")
@RestController
public class UserInfoController {
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;
    @Autowired
    private UserService userService;

    @RequestMapping("/register")
    public Boolean register(String userName, String password) {
        //开启事务
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
        Integer result = userService.insertUser(userName, password);
        System.out.println("插入用户表, result " + result);
        //回滚事务
        transactionManager.rollback(transaction);
        //提交事务
        transactionManager.commit(transaction);
        return false;
    }
}

观察事务提交:
在这里插入图片描述
观察数据库的结果, 数据插入成功.

观察事务回滚:
在这里插入图片描述
观察数据库, 虽然程序返回"注册成功", 但数据库并没有新增数据.
以上代码虽然可以实现事务, 但操作也很繁琐, 因此在日常的开发过程中,使用声明式事务的情况会更多.

2.2 Spring声明式事务@Transactional

  • 声明式事务的实现很简单, 两步操作:
    1. 添加依赖
    2. 在需要事务的方法上添加 @Transactional 注解就可以实现了. 无需手动开启事务和提交事务, 进入方法时自动开启事务, 方法执行完会自动提交事务, 如果中途发生了没有处理的异常会自动回滚事务.

代码实现:

@Transactional
    @RequestMapping("/register")
    public Boolean register(String userName, String password) {
        Integer i = userService.insertUser(userName, password);
        System.out.println("插入用户表, result " + i);
        return true;
    }

修改程序, 使之出现异常:

@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionalController {
 @Autowired
 private UserService userService;
 @Transactional
 @RequestMapping("/registry")
 public String registry(String name,String password){
 //⽤户注册
 userService.registryUser(name,password);
 log.info("⽤⼾数据插⼊成功");
 //强制程序抛出异常
 int a = 10/0;
 return "注册成功";
 }
}

运行程序:
发现虽然日志显示数据插入成功, 但数据库却没有新增数据, 事务进行了回滚.
在这里插入图片描述

注:
我们一般会在业务逻辑层当中来控制事务, 因为在业务逻辑层当中, 一个业务功能可能会包含多个数据访问的操作. 在业务逻辑层来控制事务, 我们就可以将多个数据访问操作控制在一个事务范围内.上述代码在Controller中书写, 只是为了方便演示学习.

四. @Transactional详解.

4.1@Transactional的基本使用

  • @Transactional 可以用来修饰方法或类:

    • 修饰方法时: 只有修饰public 方法时才生效(修饰其他方法时不会报错, 也不生效)[推荐]
    • 修饰类时: 对 @Transactional 修饰的类中所有的 public 方法都生效
  • 方法/类被 @Transactional 注解修饰时, 在目标方法执行开始之前, 会自动开启事务, 方法执行结束之后, 自动提交事务. 如果在方法执行过程中, 出现异常, 且异常未被捕获, 就进行事务回滚操作. 如果异常被程序捕获, 方法就被认为是成功执行, 依然会提交事务

修改上述代码, 对异常进行捕获:

@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
 //⽤户注册
 userService.registryUser(name,password);
 log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
 try {
 //强制程序抛出异常
 int a = 10/0;
 }catch (Exception e){
 e.printStackTrace();
 }
 return "注册成功";
}

运行程序, 发现虽然程序出错了, 但是由于异常被捕获了, 所以事务依然得到了提交.
如果需要事务进行回滚, 有以下两种方式:

  1. 方法一: 重新抛出异常
@Transactional
    @RequestMapping("/register2")
    public Boolean register2(String userName, String password) {
        Integer i = userService.insertUser(userName, password);
        System.out.println("插入用户表, result " + i);
        try {
            int a = 10/0;
        } catch (Exception e) {
            throw e;
        }
        return false;
    }
  1. 方法二: 手动回滚事务
    使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务, 并
    使用 setRollbackOnly 设置 setRollbackOnly
@Transactional
    @RequestMapping("/register3")
    public Boolean register3(String userName, String password) {
        Integer i = userService.insertUser(userName, password);
        System.out.println("插入用户表, result " + i);
        try {
            int a = 10/0;
        } catch (Exception e) {
            //发现异常,也进行了处理, 手动设置回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return false;

    }
  • 通过上面的代码, 我们学习了 @Transactional 的基本使用. 接下来将演示 @Transactional注解的使用细节.
  • 主要学习讲解 @Transactional 注解当中的三个常见属性:
    1. rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
    2. Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT
    3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED

4.2 rollbackFor属性

  • @Transactional 默认只在遇到运行时异常和Error时才会回滚, 非运行时异常不回滚. 即Exception的子类中, 除了RuntimeException及其子类.
@Transactional
@RequestMapping("/r2")
public String r2(String name,String password) throws IOException {
 //⽤户注册
 userService.registryUser(name,password);
 log.info("⽤⼾数据插⼊成功");
 if (true){
 throw new IOException();
 }
 return "r2";
}

运行程序

当前表中数据:在这里插入图片描述
在这里插入图片描述

发现虽然程序抛出了异常, 但是事务依然进行了提交:

运行后表中数据:在这里插入图片描述

  • 如果我们需要所有异常都回滚, 需要来配置 @Transactional 注解当中的 rollbackFor 属性, 通过 rollbackFor 这个属性指定出现何种异常类型时事务进行回滚.
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name,String password) throws IOException {
 //⽤⼾注册
 userService.registryUser(name,password);
 log.info("⽤⼾数据插⼊成功");
 if (true){
 throw new IOException();
 }
 return "r2";
}

此时事务会进行回滚.

  • 结论:
    • 在Spring的事务管理中,默认只在遇到运行时异常RuntimeException和Error时才会回滚.
    • 如果需要回滚指定类型的异常, 可以通过rollbackFor属性来指定.

4.3 Spring 事务隔离级别

  • Spring 中事务隔离级别有5 种(在上面针对MySQL的事务隔离级别进行了细致的描述,这里便不再过多的叙述了):

    1. Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.
    2. Isolation.READ_UNCOMMITTED : 读未提交, 对应SQL标准中 READ UNCOMMITTED
    3. Isolation.READ_COMMITTED : 读已提交,对应SQL标准中 READ COMMITTED
    4. Isolation.REPEATABLE_READ : 可重复读, 对应SQL标准中 REPEATABLE READ
    5. Isolation.SERIALIZABLE : 串行化, 对应SQL标准中 SERIALIZABLE
  • 设置Spring事务的隔离级别:

@Transactional(isolation = Isolation.DEFAULT)
    @RequestMapping("/register6")
    public Boolean register6(String userName, String password) throws IOException {
        Integer i = userService.insertUser(userName, password);
        System.out.println("插入用户表, result " + i);
        if(true){
            throw new IOException();
        }
        return false;
    }

五.Spring事务的传播机制.

5.1 Spring的七种事务传播行为

  • 事务的传播机制是指: 在多个事务方法相互调用的情况下,事务如何在方法之间进行传播和管理。具体来说,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行,是由其事务传播行为决定的。
  • Spring提供了七种事务传播行为,以适应不同的应用场景和需求。以下是对这七种传播行为的详细解释:
      1. REQUIRED
        • 定义:默认的事务传播级别. 如果当前存在事务,则加入该事务;如果不存在事务,则创建新事务。
        • 场景:这是最常用的传播行为,适用于大部分需要事务管理的情况。
        • 举例:如果方法A调用方法B,且两者都使用PROPAGATION_REQUIRED,它们将共享同一个事务上下文,其中任何一个方法的回滚都会影响另一个方法。
      1. SUPPORTS
        • 定义:如果当前存在事务,则加入该事务;如果不存在事务,则以非事务方式执行。
        • 场景:适用于那些可能不需要事务支持的操作,例如某些仅查询数据库的方法。
        • 举例:如果方法A在事务中调用方法B,且方法B使用PROPAGATION_SUPPORTS,那么B会加入到A的事务中。如果直接调用B(不在事务中),则B以非事务方式执行。
      1. MANDATORY
        • 定义:如果当前存在事务,则加入该事务;如果不存在事务,则抛出异常。
        • 场景:适用于绝对必须运行在事务中的方法,通常用于确保安全或数据完整性的操作。
        • 举例:如果方法A在事务中调用方法B,且B使用PROPAGATION_MANDATORY,B会加入到A的事务中。如果直接调用B(不在事务中),则会抛出异常。
      1. REQUIRES_NEW
        • 定义:创建新事务,无论当前是否存在事务。如果存在事务,则将当前事务挂起。
        • 场景:用于那些需要独立于其他事务操作的方法,以确保该方法的执行不会影响其他操作。
        • 举例:如果方法A调用方法B,且B使用PROPAGATION_REQUIRES_NEW,那么不管A是否有事务,B都会创建一个新的事务并执行。
      1. NOT_SUPPORTED
        • 定义:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
        • 场景:用于那些不需要事务管理的情况,例如某些仅读取静态数据的方法。
        • 举例:如果方法A在事务中调用方法B,且B使用PROPAGATION_NOT_SUPPORTED,那么B将以非事务方式执行,并且A的事务会被挂起,直到B执行完毕。
      1. NEVER
        • 定义:以非事务方式执行操作,如果当前存在事务,则抛出异常。
        • 场景:用于那些绝不应该在事务中执行的操作,这些操作可能涉及对事务安全性要求高的资源。
        • 举例:如果方法A在事务中调用方法B,且B使用PROPAGATION_NEVER,则会抛出异常。
      1. NESTED
        • 定义:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建新事务。
        • 场景:适用于需要在其他事务内部进行进一步原子操作的情况。
        • 举例:如果方法A调用方法B,且B使用PROPAGATION_NESTED,那么B会在A的事务内部创建一个子事务。这个子事务可以独立提交或回滚,不会影响到外部的事务A。

在这里插入图片描述

举个例子:(一对新人要结婚了, 关于是否需要房子)

  1. Propagation.REQUIRED : 需要有房子. 如果你有房, 我们就一起住, 如果你没房, 我们就一起买房. (如果当前存在事务, 则加入该事务. 如果当前没有事务, 则创建一个新的事务)
  2. Propagation.SUPPORTS : 可以有房. 如果你有房子, 那就一起住. 如果没房, 那就租房. (如果当前存在事务, 则加入该事务. 如果当前没有事务, 则以非事务的方式继续运行)
  3. Propagation.MANDATORY : 必须有房子. 要求必须有房, 如果没房就不结婚. (如果当前存在事务, 则加入该事务. 如果当前没有事务, 则抛出异常)
  4. Propagation.REQUIRES_NEW : 必须买新房. 不管你有没有房, 必须要两个人一起买房. 即使有房也不住. (创建一个新的事务. 如果当前存在事务, 则把当前事务挂起)
  5. Propagation.NOT_SUPPORTED : 不需要房. 不管你有没有房, 我都不住, 必须租房.(以非事务方式运行, 如果当前存在事务, 则把当前事务挂起)
  6. Propagation.NEVER : 不能有房⼦. (以非事务方式运行, 如果当前存在事务, 则抛出异常)
  7. Propagation.NESTED : 如果你没房, 就一起买房. 如果你有房, 我们就以房子为根据地,做点下生意. (如果如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行. 如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED )

5.2 部分传播行为的代码演示

5.2.1 REQUIRED(加入事务)

代码实现:

  • 用户注册, 插入一条数据
  • 记录操作日志, 插入一条数据(出现异常)
@RequestMapping("/user2")
@RestController
public class UserInfoController2 {
    @Autowired
    private UserService userService;
    @Autowired
    private LogService logService;

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    @RequestMapping("/register")
    public Boolean register(String userName, String password) {
        Integer result = userService.insertUser(userName, password);
        System.out.println("插入用户表, result " + result);
        //插入日志表
        Integer logResult = logService.insert(userName,"用户注册");
        return true;

    }

}
@Service
public class LogService {
    @Autowired
    private LoginInfoMapper loginInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insert(String userName, String op) {
        Integer i = loginInfoMapper.insertLog(userName, op);
        int a = 10/0;
        return i;
    }
}
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insertUser(String userName, String password) {
        int i = userInfoMapper.insertUserInfo1(userName, password);
        return i;
    }
}

经过测试可以得出以下结论:

  • 如果Controller层加上事务(即当前存在事务), 那么LogService和UserService的事务都会使用Controller的事务, 本质上就是把这三个事务看成是一个整体,要么都提交,要么都回滚如果他们中的任何一个方法(即insert,insertUser,register) 发生异常,所有的方法都不会执行成功, 都会进行回滚;如果所有相关的调用方法都没有什么问题,那么就会提交事务,三个方法都会执行成功
  • 如果Controller层没有加上事务(即当前不存在事务), 那么LogService和UserService使用各自的事务, 各自的运行结果之间互不影响.

5.2.2REQUIRES_NEW(新建事务)

  • 将上述UserService 和LogService 中相关方法事务传播机制改为Propagation.REQUIRES_NEW
  • 创建新事务,无论当前是否存在事务。如果存在事务,则将当前事务挂起。
@Service
public class LogService {
    @Autowired
    private LoginInfoMapper loginInfoMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer insert(String userName, String op) {
        Integer i = loginInfoMapper.insertLog(userName, op);
        int a = 10/0;
        return i;
    }
}
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer insertUser(String userName, String password) {
        int i = userInfoMapper.insertUserInfo1(userName, password);
        return i;
    }
}

经过测试可以得出以下结论:

  • 无论Controller层是否存在事务, LogService 和UserService 中的事务都会新建一个自己的事务, 运行结果之间互不干扰. LogService 方法中的事务不影响 UserService 中的事务. 本质上就是各是各的谁也不管谁
  • 当我们不希望事务之间相互影响时, 可以使用该传播行为.

5.2.3 NEVER (不支持当前事务, 抛异常)

  • 修改UserService 中对应方法的事务传播机制为 Propagation.NEVER
  • 以非事务方式执行操作,如果当前存在事务,则抛出异常。
@Service
public class LogService {
    @Autowired
    private LoginInfoMapper loginInfoMapper;
    @Transactional(propagation = Propagation.NEVER)
    public Integer insert(String userName, String op) {
        Integer i = loginInfoMapper.insertLog(userName, op);
        int a = 10/0;
        return i;
    }
}
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.NEVER)
    public Integer insertUser(String userName, String password) {
        int i = userInfoMapper.insertUserInfo1(userName, password);
        return i;
    }
}

经过测试可以得出以下结论:

  • 程序执行报错, 没有数据插入 在这里插入图片描述

5.2.4 NESTED(嵌套事务)

  • 将上述UserService 和LogService 中相关方法事务传播机制改为Propagation.NESTED
  • 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建新事务。
@Service
public class LogService {
    @Autowired
    private LoginInfoMapper loginInfoMapper;
    @Transactional(propagation = Propagation.NESTED)
    public Integer insert(String userName, String op) {
        Integer i = loginInfoMapper.insertLog(userName, op);
        int a = 10/0;
        return i;
    }
}
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.NESTED)
    public Integer insertUser(String userName, String password) {
        int i = userInfoMapper.insertUserInfo1(userName, password);
        return i;
    }
}

运行程序, 发现没有任何数据插入
流程描述:

  1. Controller 中p1 方法开始事务
  2. UserService 用户注册, 插入一条数据 (嵌套p1事务)
  3. LogService 记录操作日志, 插入一条数据(出现异常, 行执失败) (嵌套p1事务, 回滚当前事务, 数据添加失败)
  4. 由于是嵌套事务, LogService 出现异常之后, 往上找调用它的方法和事务, 所以用户注册也失败了.
  5. 最终结果是两个数据都没有添加

p1事务可以认为是父事务, 嵌套事务是子事务. 父事务出现异常, 子事务也会回滚, 子事务出现异常, 如果不进行处理, 也会导致父事务回滚.

NESTED和REQUIRED 有什么区别?
  • 我们在 LogService 进行当前事务回滚, 修改 LogService 代码如下
@Service
public class LogService {
 @Autowired
 private LogInfoMapper logInfoMapper;
 @Transactional(propagation = Propagation.NESTED)
 public void insertLog(String name,String op){
 try {
 int a=10/0;
 }catch (Exception e){
 //回滚当前事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
 }
 //记录⽤⼾操作
 logInfoMapper.insertLog(name,"⽤⼾注册");
}
}

重新运行程序, 发现用户表数据添加成功, 日志表添加失败.LogService 中的事务已经回滚, 但是嵌套事务不会回滚嵌套之前的事务, 也就是说嵌套事务可以实现部分事务回滚

  • 对比REQUIRED
    把 NESTED 传播机制改为 REQUIRED, 修改代码如下:
@Service
public class UserService {

 @Autowired
 private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
 public void registryUser(String name,String password){
 //插⼊⽤⼾信息
 userInfoMapper.insert(name,password);
 }
}
@Service
public class LogService {
 @Autowired
 private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
 public void insertLog(String name,String op){
 try {
 int a=10/0;
 } catch (Exception e){
 //回滚当前事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
 }
 //记录⽤⼾操作
 logInfoMapper.insertLog(name,"⽤⼾注册");
 }
}

重新运行程序, 发现用户表和日志表的数据添加都失败了.REQUIRED 如果回滚就是回滚所有事务, 不能实现部分事务的回滚. (因为属于同一个事务)

  • 总结NESTED和REQUIRED区别:
    • 整个事务如果全部执行成功, 二者的结果是一样的.
    • 如果事务一部分执行成功, REQUIRED加入事务会导致整个事务全部回滚. NESTED嵌套事务可以实现局部回滚, 不会影响上一个方法中执行的结果
    • 嵌套事务之所以能够实现部分事务的回滚, 是因为事务中有一个保存点(savepoint)的概念, 嵌套事务进入之后相当于新建了一个保存点, 而滚回时只回滚到当前保存点.
    • 资料参考: https://dev.mysql.com/doc/refman/5.7/en/savepoint.html

六.总结.

  1. Spring中使用事务, 有两种方式: 编程式事务(手动操作)和声明式事务. 其中声明式事务使⽤用较多,在方法上添加 @Transactional 就可以实现了
  2. 通过 @Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离级
    别. Spring 中的事务隔离级别有 5 种
  3. 通过 @Transactional(propagation = Propagation.REQUIRED) 设置事务的传播机制, Spring 中的事务传播级别有 7 种, 重点关注 REQUIRED (默认值) 和 REQUIRES_NEW

原文地址:https://blog.csdn.net/m0_74105656/article/details/140448774

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!