【掌握Spring事务管理】深入理解事务传播机制的秘密

您所在的位置:网站首页 spribg事务传播机制 【掌握Spring事务管理】深入理解事务传播机制的秘密

【掌握Spring事务管理】深入理解事务传播机制的秘密

2023-06-24 21:17| 来源: 网络整理| 查看: 265

🎉🎉🎉点进来你就是我的人了博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🤺🤺🤺

目录

1.Spring 中事务的实现方式

1.1 Spring 编程式事务 (了解)

1.2 Spring 声明式事务 ( @Transactional )

【异常情况一】(自动回滚成功)

【异常情况二】(自动回滚失效)

1.3 声明式事务的手动回滚 (解决自动回滚失效问题)

2. @Transactional的使用

2.1 @Transactional中的参数作用

2.2 @Transactional工作原理

3. 事务隔离级别

3.1 事务特性回顾

3.2 Spring 中设置事务隔离级别

3.2.1 MySQL 事务隔离级别有 4 种

3.2.2 Spring 事务隔离级别有 5 种

4. Spring事务传播机制

4.1 事务传播机制是什么

4.2 为什么需要事务传播机制

4.3 事务传播机制有哪些

4.4 事务传播的分类 

4.5 Spring事务传播机制使用场景演示

4.5.1 支持当前事务(REQUIRED)

 4.5.2 不支持当前事务(REQUIRES_NEW)

4.5.3 嵌套事务(NESTED)

嵌套事务为什么能实现部分事务的回滚 (面试题)

4.5.4 嵌套事务(NESTED)和加入事务(REQUIRED)有什么区别?

总结

1.Spring 中事务的实现方式

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

编程式事务 (了解)声明式事务

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

1.1 Spring 编程式事务 (了解)

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

开启事务 提交事务回滚事务

【代码实现】

//这是一个组合注解,它组合了@Controller和@ResponseBody //表明这个类是一个控制器,会处理HTTP请求,并且返回的数据会自动转换为JSON或其他格式 @RestController //这个注解表明这个控制器会处理以"/user"开头的URL @RequestMapping("/user") public class UserController { @Autowired private UserService userService; //这是一个业务逻辑服务类,用于处理与用户相关的业务逻辑 //编程式事务 //Spring的事务管理可以确保当在执行数据库操作时,如果出现错误,所有的操作都可以回滚,以保证数据的一致性。 @Autowired //transactionManager 负责管理数据库事务 private DataSourceTransactionManager transactionManager; @Autowired //transactionDefinition 它定义了事务的各种属性,比如隔离级别、传播行为、超时时间、是否为只读事务等。 private TransactionDefinition transactionDefinition; @RequestMapping("/del") public int del(Integer id) { if(id == null || id

REQUIRED和NESTED是最容易混淆的,因为它们都会参与到事务中,但差别在于REQUIRED是全局的,所有操作都是在同一个事务中,一起成功或失败,而NESTED是在当前事务中创建一个子事务,子事务可以独立于父事务进行回滚,这个子事务的回滚并不会影响到父事务。

4.5 Spring事务传播机制使用场景演示 4.5.1 支持当前事务(REQUIRED)

以下代码实现中,先开启事务先成功插⼊⼀条⽤户数据,然后再执⾏⽇志报错,⽽在⽇志报错时发⽣了异常,观察 propagation = Propagation.REQUIRED 的执⾏结果。

 准备工作:

UserController3:(Controller层主要负责处理用户请求并返回响应)

//这是一个组合注解,它组合了@Controller和@ResponseBody //表明这个类是一个控制器,会处理HTTP请求,并且返回的数据会自动转换为JSON或其他格式 @RestController //这个注解表明这个控制器会处理以"/user"开头的URL @RequestMapping("/user3") public class UserController3 { @Autowired private UserService userService; @RequestMapping("/add") @Transactional(propagation = Propagation.REQUIRED) public int add (String username, String password) { if(null == username || null == password || username.equals("") || password.equals("")) { return 0; } Userinfo user = new Userinfo(); user.setUsername(username); user.setPassword(password); int result = userService.add(user); // 用户添加操作 return result; } }

LogService: (添加日志)

@Service public class LogService { @Autowired private LogMapper logMapper; @Transactional(propagation = Propagation.REQUIRED) public int add(Log log) { int result = logMapper.add(log); System.out.println("添加日志结果: " + result); int num = 10 / 0; return result; } }

 UserService: (添加用户数据)

@Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private LogService logService; @Transactional(propagation = Propagation.REQUIRED) public int add(Userinfo userinfo) { // 给用户表添加用户信息 int addUserResult = userMapper.add(userinfo); System.out.println("添加用户结果: " + addUserResult); // 添加日志信息 Log log = new Log(); log.setMessage("添加用户信息"); logService.add(log); return addUserResult; } }

  代码调度执行流程图:

运行程序: 在浏览器页面输入url 127.0.0.1:8080/user3/add?username="小诗诗"&password="123" 发现报错了

 观察控制台: (我们可以发现日志和用户数据添加成功了)

 再观察数据库: (发现用户数据和日志都没有添加成功)

实际得出的结果是符合我们预期的 ,因为在我们的@Transactional注解中使用了默认的事务传播机制(REQUIRED),该机制表示如果当前存在事务,那么就在该事务中运行,否则,就开启一个新的事务。在这个级别中,参与者要么都提交成功,要么都回滚失败。这里由于最后添加日志的时候发生算术异常,导致添加日志这个操作发生了回滚,没有提交成功,进而导致全部提交失败  4.5.2 不支持当前事务(REQUIRES_NEW) Propagation.REQUIRES_NEW修饰的方法总是会在新的事务中运行。这个新的事务和原来的事务是相互独立的,也就是说,它们的提交和回滚不会互相影响。例如,假设有一个方法A,它在一个事务中运行,并且调用了一个使用Propagation.REQUIRES_NEW修饰的方法B。如果方法B成功完成并且提交了它的事务,但是方法A之后抛出了一个异常并且回滚了它的事务,那么方法B的结果不会被回滚。反之亦然,如果方法B抛出了一个异常并且回滚了它的事务,那么方法A可以选择捕获那个异常并且继续它的事务,而不会影响到方法A的事务。

代码演示: 整体代码与上面一致,我们只需要把@Transactional里面的参数Propagation.REQUIRED改成Propagation.REQUIRES_NEW即可

 同时将上面的添加日志的代码从抛出异常改成手动回滚,目的就是为了测试REQUIRES_NEW是否能让多个事务提交和回滚不受影响

此时运行程序: 在浏览器页面输入url 127.0.0.1:8080/user3/add?username="小诗诗"&password="123" 发现没有报错同时观察控制台: (我们可以发现日志和用户数据添加成功了)

再观察数据库: (发现用户数据添加成功,日志为空)

 此时的数据库的结果符合我们的预期,同时也证明 REQUIRES_NEW 它们的提交和回滚不会互相影响

4.5.3 嵌套事务(NESTED)

Propagation.NESTED可以启动一个“嵌套事务”,这是在一个已经存在的事务中启动的新事务。嵌套事务是一种特殊类型的子事务,它的提交是可以独立于外部事务的。但是,它的回滚会回滚所有从它开始到现在的所有操作,而不会影响到外部事务在它开始之前的操作。

此处不做代码演示了,只需要把上面代码里@Transactional里面的参数Propagation.REQUIRES_NEW改成Propagation.NESTED即可,得到的结果与上面一致.

嵌套事务为什么能实现部分事务的回滚 (面试题)

参考文档:  MySQL

嵌套事务之所以能实现部分事务的回滚,是因为当嵌套事务开始时,会在事务日志中设置一个保存点(Savepoint)。如果后续的操作出现错误需要回滚,只需要回滚到这个保存点,也就是嵌套事务开始的地方。这就相当于只回滚了嵌套事务的部分,而不会影响到外部事务在嵌套事务开始之前的操作。这种行为使得在一个大事务中执行一系列的任务时,如果某个任务失败,可以只回滚这个任务的操作,而不会影响到其他任务的结果。这在处理复杂的业务逻辑时是非常有用的。 4.5.4 嵌套事务(NESTED)和加入事务(REQUIRED)有什么区别? 嵌套事务,可以实现部分事务回滚,也就是说回滚时,一直往上找调用它的方法和事务回滚,而不会回滚嵌套之前的事务加入事务,相当我已经成了他的一部分,回滚时,整个一起回滚加入事务和嵌套事务的主要区别在于,当一个方法在运行过程中抛出了一个未捕获的异常并且需要回滚时,加入事务会回滚整个事务,而嵌套事务只会回滚从嵌套事务开始的部分。如果整个事务都执行成功,那么这两种传播行为的效果是一样的。 总结

当我们说"当前存在事务"时,我们通常是指调用当前方法的外部方法已经开启了一个事务。

当我们说一个方法"加入事务"时,我们通常是指这个方法并不自己单独开启一个新的事务,而是使用(或者说,加入到)调用它的外部方法已经开启的事务。这意味着这个方法的操作会成为外部事务的一部分,如果事务提交,那么这个方法的操作会被永久地应用到数据库;如果事务回滚,那么这个方法的操作也会被回滚。

这些概念是理解Spring的事务管理和事务传播行为的关键。

如果你觉得这篇文章有价值,或者你喜欢它,那么请点赞并分享给你的朋友。你的支持是我创作更多有用内容的动力,感谢你的阅读和支持。祝你有个美好的一天!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3