Spring 事务和事务的传播机制

article/2025/9/21 2:07:11

1.Spring 中事务的实现方式

Spring 中的操作主要分为两类:

  • 编程式事务 (了解)

  • 声明式事务

编程式事务就是手写代码操作事务, 而声明式事务是利用注解来自动开启和提交事务. 并且编程式事务用几乎不怎么用. 这就好比汽车的手动挡和自动挡, 如果有足够的的钱, 大部分人应该都会选择自动挡.
声明式事务也是如此, 它不仅好用, 还特别方便.

1.1 Spring 编程式事务 (了解)

编程式事务和 MySQL 中操作事务类似, 也是三个重要步骤:

  1. 开启事务

  1. 提交事务

  1. 回滚事务

【代码实现】

@RequestMapping("/user")
public class UserController1 {@Autowiredprivate UserService userService;@Autowired  // JDBC 事务管理器private DataSourceTransactionManager dataSourceTransactionManager;@Autowired  // 定义事务属性private TransactionDefinition transactionDefinition;@RequestMapping("/add")public int add(String username, String password) {// 非空校验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}// 事务 [得到并开启事务]TransactionStatus transactionStatus =dataSourceTransactionManager.getTransaction(transactionDefinition);int result = userService.add(username, password, null);System.out.println("添加影响行数: " + result);// 提交事务 or 回滚dataSourceTransactionManager.rollback(transactionStatus);//  dataSourceTransactionManager.commit(transactionStatus);return result;}
}
DataSourceTransactionManager 和 TransactionDefinition 是 SpringBoot 内置的两个对象.
DataSourceTransactionManager : 用来获取事务(开启事务)、提交或回滚事务.
TransactionDefinition : 它是事务的属性,在获取事务的时候需要将这个对象传递进去从而获得⼀个事务 TransactionStatus.

【测试编式事务】

上述代码的主要要业务逻辑就是基于 MyBatis实现了一个新增方法, 接下来我们测试一下编程式事务中的回滚操作是否生效.

  1. 在测试之前先查看一下数据库中的用户信息 (userinfo) :

  1. 启动程序后, 在浏览器输入: 127.0.0.1:8080/user/add?username=李华&password=123

3. 此时我们看见控制台显示添加数据成功, 那么要知道代码中的回滚是否生效, 需要查看数据库是否真正的把数据添加进去了.

4. 发现数据库中并没有添加数据, 说明回滚操作生效了. 而提交事务 commit 就和普通的添加操作差不多, 下来可以自己试一下.

1.2 Spring 声明式事务

声明式事务的实现相较于编程式事务来说, 就要简单太多了, 只需要在需要的方法上添加 @Transactional注解就可以实现了.

@Transactional 注解的作用:

当进入方法的时候, 它就会自动开启事务, 当方法结束后, 它就会自动提交事务. 说白了它就是 AOP 的一个环绕通知. 只要加了 @Transactional 注解的方法, 都有一个事务的 AOP , 这都是 Spring 帮我们封装好的.

@Transactional 注解的执行流程:

1. 方法执行之前, 先开启事务, 当方法成功执行完成之后会自动提交事务.
2. 如果方法在执行过程中发生了异常, 那么事务会自动回滚.

【代码实现】

    @RequestMapping("/add2")@Transactional // 声明式事务public int add2(String username, String password) {// 非空校验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}int result = userService.add(username, password, null);System.out.println("添加影响行数: " + result);return result;}

对于方法执行成功的情况就不测试了, 它和普通的插入数据没有多大区别, 重点在于理解 @Transactional注解的含义和作用即可.

【异常情况一】

    @RequestMapping("/add2")@Transactional // 声明式事务public int add2(String username, String password) {// 非空校验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}int result = userService.add(username, password, null);System.out.println("添加影响行数: " + result);int num = 10 / 0;return result;}

当我们写出 int num = 10 / 0; 这样一条语句的时候, 看看 @Transactional 是否会进行回滚操作:

启动程序, 浏览器访问 : 127.0.0.1:8080/user/add2?username=王五&password=123

此时程序已经报错了, 并且打印了添加成功语句, 是否真正添加成功, 还是说进行了回滚操作, 就要查询数据库:

发现数据库中并没有王五这条数据, 说明在发生异常的时候, @Transactional 注解帮我们做了回滚操作.

【异常情况二】

对于上述代码抛出异常后, @Transactional 注解帮我们进行回滚, 这一点很好理解, 那么如果我们将这个异常捕获了, @Transactional 注解是否还会进行回滚操作呢 >>

    @RequestMapping("/add2")@Transactional // 声明式事务public int add2(String username, String password) {// 非空校验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}int result = userService.add(username, password, null);System.out.println("添加影响行数: " + result);try {int num = 10 / 0;} catch (Exception e) {}return result;}

执行结果: 此时程序没有发生报错了.

为了验证是否进行回滚, 继续查询数据库

此时我们发现, @Transactional 注解并没有进行回滚操作, 而是提交了事务. 这是为什么 ??

因为当我们捕捉到异常的时候, Spring 框架会认为我们有能力处理, 所以就不会进行回滚, 而当发生异常我们不处理的时候, Spring 框架就会采取保守的做法, 他知道我们没有能力去处理这个异常, 所以就会帮我们回滚. 所以当出现异常的时候, 我们要根据这个异常是否被处理来判断最终是提交数据了, 还是进了回滚操作.

1.2.1 声明式事务的手动回滚

当第二种异常情况, 捕获异常之后, 事务并没有进行回滚, 我们是需要做出一些处理的. 既然程序发生了异常, 我们一般就需要进行回滚操作的. 对于这种捕获异常的情况,我们有两种方式进行回滚:

  • 将异常继续抛出.

  • 通过代码手动回滚事务.

【代码示例】- 将异常继续抛出

    @RequestMapping("/add2")@Transactional // 声明式事务public int add2(String username, String password) {// 非空校验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}int result = userService.add(username, password, null);System.out.println("添加影响行数: " + result);try {int num = 10 / 0;} catch (Exception e) {throw e; // 将异常继续抛出}return result;}

测试代码是否回滚,还是和前面一样的操作,就不赘述了. 代码的最终执行结果肯定是进行了回滚操作.

【代码示例】- 手动回滚事务

    @RequestMapping("/add2")@Transactional // 声明式事务public int add2(String username, String password) {// 非空校验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}int result = userService.add(username, password, null);System.out.println("添加影响行数: " + result);try {int num = 10 / 0;} catch (Exception e) {// throw e;System.out.println("程序发生异常: " + e.getMessage());// 手动回滚事务 [得到当前事务并设置回滚] - 通过事务的切面拿到当前事务, 再设置回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}
手动回滚事务 : 通过事务的 AOP 拿到当前的事务, 然后设置回滚.

这种方式来处理事务的回滚, 显得更加优雅, 更推荐使用.

1.2.2 @Transactional 的工作原理

@Transactional 是基于 AOP实现的, AOP又是基于动态代理实现的 (JDK, CGLIB), 它在开始执行业务之前, 会通过代理实现开启事务, 在执行成功之后再提交事务, 如果中途出现了异常, 就会回滚事务.

@Transactional 实现思路

切面会拦截所有加了 @Transactional 注解的方法, 于是切点就有了, 然后开启事务与提交事务/回滚事务之间相当于是一个环绕通知.

2.事务隔离级别

在学 MySQL 的时候, 我们就已经知道了事务有四大特性: (ACID)

原子性 (Atomicity),
持久性(Consistency),
一致性 (Isolation) ,
隔离性 (Durability);

具体的概念在这篇博客中已经做过说明. - MySQL 事务的四大特性.

这四种特性中只有隔离性是可以设置的, 那么为什么要设置事务的隔离级别呢 ??

-- 为了保障多个并发事务执行更可控, 更符合操作者的预期.

2.1 Spring 中设置事务的隔离级别

MySQL 中事务的隔离级别分为四种:

事务隔离级别

脏读

不可重复读

幻读

读未提交 (READ UNCOMMITTED)

读已提交 (READ COMMITTED)

×

可重复读 (REPEATABLE READ)

×

×

串行化 (SERIALIZABLE)

×

×

×

MySQL 默认事务的隔离级别 : 可重复读, 可通过命令 select @@global.tx_isolation,@@tx_isolation; 来进行查看.

1. 脏读 : 一个事务A,在执行的过程中,对数据进行了一系列修改,在提交到数据库之前(完成事务之前),另一个事务B,读取了对应的数据,此时这个B读到的数据都是一些临时的结果,后续可能马上就被A给改了,此时B的读取行为就是"脏读"!

2. 不可重复读 : 事务A提交了事务之后,事务B才开始读(读的时候加了锁),然后B在执行的过程中,A再次开启了事务, 修改了 B 读取的数据,此时B执行中,就导致两次读取操作结果可能就不一致!(侧重于修改)

3. 幻读 : 事务B读取过程中,事务A进行了更新操作 ( 新增/删除/修改),没有直接影响B正在读取的数据,但是影响到了B读取的结果集,事务B两次读取到的结果集不一样,这个就是幻读!幻读相当于不可重复读的特殊情况。(侧重于新增和删除)

2.1.1 Spring 中事务的隔离级别

Spring 中事务的隔离级别有五个, MySQL 中的四个加上 DEFAULT 级别;

Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.(以数据库的全局事务隔离级别为主)

在 Spring 中如何设置事务的隔离级别>>>

@RequestMapping("/add")
@Transactional(isolation = Isolation.DEFAULT)
public int add(String username, String password) {// 业务逻辑
}

3. Spring 事务的传播机制

什么是事务的传播机制 ??

在回答这个问题前, 先给大家举个例子 >>

1. 抛开事务的传播机制不说, 我们的业务就像 UU 跑腿一样, 你在你家附近买了一样东西, 然后让 UU跑腿的人送到你的手里, 如果送到了就没事 (commit), 如果没送到, 就可以找到对应跑腿的人 (或店家) 进行相应的赔偿 (rollback).
2. 可是实际生活中, 在网上买东西的人相对来说要多一些, 如果你在深圳, 但是在北京的一家网店上买了东西, 这时候就不可能叫 UU跑腿来送到你手里了. 那么快递一般都要经过很多个运输点, 这多个运输点对应着多批人, 如果你的快递在中途被弄丢了, 他们应该要怎样赔偿, 由谁来赔偿, 这就需要牵扯到了多个事务之间的传播机制了.

事务的隔离级别 : 解决的是多个事务同时调用数据库的问题!

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

上述例子将的就是上图中多个方法调用的时候, 发生异常应该要怎么去处理, 这就是传播机制的意义所在!

3.1 事务传播机制的级别

事务传播机制的级别分为 7 种:

1. Propagation.REQUIRED : 默认的事务传播级别, 它表示如果当前存在事务,则加入该事务; 如果当前没有事务, 则创建⼀个新的事务.
2. Propagation.SUPPORTS : 如果当前存在事务, 则加入该事务;如果当前没有事务,则以非事务的
方式继续运行.
3. Propagation.MANDATORY : (mandatory:强制性) 如果当前存在事务,则加入该事务; 如果当
前没有事务,则抛出异常.
4. Propagation.REQUIRES_NEW:表示创建一个新的事务, 如果当前存在事务, 则把当前事务挂
起. 也就是说不管外部方法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部方法会新开
启自己的事务, 且开启的事务相互独立, 互不干扰.
5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起.
6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常.
7. Propagation.NESTED:如果当前存在事务,则 创建⼀个事务作为当前事务的嵌套事务来运行;如
果当前没有事务,则该取值等价于 Propagation.REQUIRED

这 7 中事务传播级别又可以分为三大类:

如果对于这三类事务传播机制, 不太理解的话, 下面把事务比做房子, 举一个生活中的例子 :

😁😁😁😁😁😁😁😁😁

  1. 支持当前事务 (普通伴侣)

  • REQUIRED (需要有) : 有房子就一起住, 没房子就一起赚钱买房子. (愿意陪你吃苦, 但一定要有房子)

  • SUPPORTS (可以有) : 有房子就一起住, 没房子就租房子住. (随缘的, 没房子也无所谓)

  • MANDATORY (强制有) : 有房子一起住, 没房子就分手. (不愿陪你吃苦)

  1. 不支持当前事务 (强势型伴侣)

  • REQUIRES_NEW : 不要你的房子, 咱们必须一起赚钱买房子. (看不上你的房子, 必须买新房子)

  • NOT_SUPPORTED : 不要你的房子, 咱们必须一起租房子. (不住你的房子, 必须租房子)

  • NEVER : 必须一起租房子, 你要有房子就分手. (看不上你的房子, 还得陪你环房贷)

  1. 嵌套事务 (懂事型伴侣)

  • NESTED : 有房子就以房子为根据地做点小生意, 赚钱了就继续发展, 赔钱至少还有房子; 如果没房子就一起赚钱买房子. (无风险创业, 保本懂事型伴侣)

对于上述3 类事务传播机制, 主要就是 REQUIRED (默认级别) NESTED (嵌套事务) 不好区分>>

1. REQUIRED (默认级别) : 一荣俱荣, 一损俱损. 如果当前有事务, 执行过程中, 如果抛出异常, 那么就一起回滚, 如果否则一起提交.
2. NESTED (嵌套事务) : 如果当前有事务, 创建一个事务作为当前的嵌套事务来执行, 相当于在当前事务这里有一个保存点, 如果执行过程中嵌套事务抛出异常, 就回滚到保存点, 只回滚嵌套事务(局部回滚), 不会影响上一个方法中执行的结果.

【代码实现】 针对默认级别和嵌套事务的一个代码实现 >>

下面的代码针对 add 方法和 save 方法做了一个事务默认传播级别的测验, 两个方法都是添加方法, 如果途中没有抛异常, 数据库库就会新增两条数据, 否则一条也不新增.

Controller :

    @RequestMapping("/add2")@Transactional(propagation = Propagation.REQUIRED)public int add2(String username, String password) {// 非空校验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}int result = userService.add(username, password, null);System.out.println("添加影响行数: " + result);int result2 = userService.save(username, password, null);System.out.println("添加影响行数: " + result2);return result;}

Service :

save 方法中有一个除 0 异常 >>

    @Transactional(propagation = Propagation.REQUIRED)public int add(String username, String password, String photo) {return userMapper.add(username, password, photo);}@Transactional(propagation = Propagation.REQUIRED)public int save(String username, String password, String photo) {try {int result = 10 / 0;} catch (Exception e) {System.out.println("ex: " + e.getMessage());TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return userMapper.add(username, password, photo);}

数据库当前信息 :

浏览器访问 add2 方法, 并传入参数, username=老六&password=123:

可以看到控制台打印了两次添加影响行数 : 1, 且出现一个除 0 异常, 那么是否真正插入到数据库中了, 需要查看一下数据库>>

发现并没有, 和上次查询的结果还是一样的. 所以符合我们的预期. (查看细致过程可以打断点进行调试)

【测试NESTED】

还是上述两个方法, 只不过把 Service 种的 save 方法的事务传播级别改为 NESTED.

浏览器访问 add2 方法, 并传入参数 usename=老六&passowrd=123

此时控制台依然打印了两次添加影响行数 : 1, 查询数据库验证插入情况 :

发现 add 方法的新增成功了, 而 save 方法的的新增回滚了, 也就是回滚到保存点, 这也符合我们的预期. (细致过程可以通过打断点的方式进行调试查看).


本篇文章就到这里了, 谢谢观看!!


http://chatgpt.dhexx.cn/article/eYh9sWFd.shtml

相关文章

【Spring Boot】事务和事务传播机制

文章目录 1. 事务简单介绍2. Spring 中事务的实现2.1 Spring 手动操作事务2.2 Spring 声明式事务 3. Transactional 注解介绍3.1 Transactional 作用范围3.2 Transactional 参数说明3.3 Transactional 出现异常注意事项3.4 Transactional 工作原理 4. 事务隔离级别4.1 事务特性…

SpringBoot事务传播机制

Spring的事务传播机制:是指规定当程序中出现了多个方法出现了嵌套调用的时候,事务是如何进行传递的 支持当前事务:要有房子的 不支持当前事务:不许要有房子的 嵌套事务 1)定义:咱们之前所说的事务,都是针对一个方法的,咱们的Spring事务传播机…

Spring的事务传播机制(通俗易懂)

概述 Spring的事务传播机制有7种,在枚举Propagation中有定义。 1.REQUIRED PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的默认设置。 Tra…

Spring中的事务传播机制

目录 前言 1、Spring(Spring Boot)实现事务 1.1、通过代码的方式手动实现事务 1.2、通过注解Transactional的方式实现声明式事务 1.2.1、实现: 1.2.2、程序中有try-catch时,程序发生异常事务不会回滚 解决方案一:将异常抛出去 …

事务的7种传播机制和演示

文章目录 一、事务的传播机制1.1、nested 事务的几点说明: 二、示例2.1、前言:2.2、准备测试方法1)创建beans.xml,开启事务2)创建实体类和表(表创建读者可自定义创建)3)创建service接…

事务传播行为

原文作者:https://blog.csdn.net/soonfly/article/details/70305683 事务传播行为 什么叫事务传播行为?听起来挺高端的,其实很简单。 即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。 事务传…

[事务] 事务的传播机制

前言: Spring的事务,也就是数据库的事务操作,符合ACID标准,也具有标准的事务隔离级别。 但是Spring事务有自己的特点,也就是事务传播机制。 所谓事务传播机制,也就是在事务在多个方法的调用中是如何传递的&…

事物的传播机制

目录 1、事务的传播机制 2、测试 2.1、准备测试方法 2.2、事务传播机制的测试 2.2.1、REQUIRED 2.2.2、NOT_SUPPORTED 2.2.3、REQUIRES_NEW 2.2.4、MANDATORY 2.2.5、NEVER 2.2.6、SUPPORTS 2.2.7、NESTED 事务传播机制:就是事务在多个方法的调用中是如何…

Spring事务传播机制

目录 一、事务在Spring中是如何运作的 1.1 开启事务(DataSourceTransactionManager.doBegin) 二、Spring的事务传播机制 2.1 子事务的传播机制为REQUIRED 2.2 子事务的传播机制为REQUIRES_NEW 2.3 子事务的传播机制为NESTED 当我们在使用Spring所提供的事务功能时&#x…

Spring事务传播的7种机制

Spring 事务传播机制包含以下 7 种: 1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果 当前没有事务,则创建一个新的事务。 2. Propagation.SUPPORTS:如果…

事务的传播机制

目录 1.形象说明: 2.代码演示: 2.1 REQUIRED 2.1.1 验证共用一个事务 2.1.2 验证当前没有事务,就新建一个事务 2.2 SUPPORTS 2.2.1 支持使用当前事务 2.2.2 如果当前事务不存在,则不使用事务 2.3 MANDATORY 2.3.1 支持…

Spring事务传播机制详解

前言: Spring的事务,也就是数据库的事务操作,符合ACID标准,也具有标准的事务隔离级别。 但是Spring事务有自己的特点,也就是事务传播机制。 所谓事务传播机制,也就是在事务在多个方法的调用中是如何传递的&…

反射原理详谈

什么是反射? 反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言…

Java反射的作用与原理

Java反射的作用与原理 定义 反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。在Java中,只要给定类…

彻底搞懂java反射技术及其原理

概述:反射是java中最强大的技术之一,很多高级框架都用到了反射技术,面试中也是经常问的点,所以搞懂反射非常重要! 文章目录 1.反射是什么?2.反射的底层原理3.三种方式获取Class对象4.反射的优缺点5.反射的应用场景6.反射的常用API 1.反射是什么? java反射机制指…

java反射原理-重要

一,反射是什么(反射是框架设计的灵魂) 1,JAVA反射机制是在运行状态中 对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意一个方法和属性; …

java 反射机制原理 简述

什么是反射机制? 1、在运行状态中,对于任意一个类,都能够知道这个类的属性和方法。 2、对于任意一个对象,都能够调用它的任何方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为JAVA的反射。 反射的作用 1、在运行…

java反射如何实现的_Java反射实现原理

Java反射应用十分广泛,例如spring的核心功能控制反转IOC就是通过反射来实现的,本文主要研究一下发射方法调用的实现方式和反射对性能的影响。 如下为Method类中invoke方法,可以看出该方法实际是将反射方法的调用委派给MethodAccessor&#xf…

Java反射原理与使用

当类加载器将类加载进jvm之后,jvm会创建每一个类的元数据对象(Class),这个元数据对象(Class)记录着这类的所有信息,java语言允许通过元数据对象动态的创建对象实例,这种机制就称为java的反射机制,基本上所有框架的底层都用到了反射机制,spring、mybatis、servlet都用到了 1.如…

Java反射原理简析

Java的反射机制允许我们动态的调用某个对象的方法/构造函数,获取某个对象的属性等,而无需在编码时确定调用的对象。这种机制在我们常用的框架中也非常常见。 1.原理简介 类actionClass Class.forName(“ MyClass”); 对象actio…