百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

浅谈Spring事务中的7种传播特性,你了解多少?

bigegpt 2024-08-01 12:02 15 浏览

引言

什么是事务的传播特性?

简单来讲,就是当系统中存在两个事务方法时(我们暂称为方法A和方法B),如果方法B在方法A中被调用,那么将采用什么样的事务形式,就叫做事务的传播特性

比如,A方法调用了B方法(B方法必须使用事务注解),那么B事务可以是一个在A中嵌套的事务,或者B事务不使用事务,又或是使用与A事务相同的事务,这些均可以通过指定事务传播特性来实现

怎么配置事务传播特性?

首先使用org.springframework.transaction.annotation包下的@Transactional注解,在其中声明propagation属性即可(默认值为Propagation.REQUIRED):

 @Transactional(propagation = Propagation.REQUIRED)
复制代码

事务传播特性有哪几种?

目前,Spring在TransactionDefinition类中定义了以下7种传播特性,具体特性我们接下来会分析:

  • PROPAGATION_REQUIRED:如果不存在外层事务,就主动创建事务;否则使用外层事务
  • PROPAGATION_SUPPORTS:如果不存在外层事务,就不开启事务;否则使用外层事务
  • PROPAGATION_MANDATORY:如果不存在外层事务,就抛出异常;否则使用外层事务
  • PROPAGATION_REQUIRES_NEW:总是主动开启事务;如果存在外层事务,就将外层事务挂起
  • PROPAGATION_NOT_SUPPORTED:总是不开启事务;如果存在外层事务,就将外层事务挂起
  • PROPAGATION_NEVER:总是不开启事务;如果存在外层事务,则抛出异常
  • PROPAGATION_NESTED:如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务

为什么要指定事务传播特性?

有些人可能会觉得,为什么非要指定传播特性不可,我们所有方法执行都开启一个事务不可以吗?这里我暂时不做解释,看完下一个部分相信就有答案了

事务传播特性各自有什么特点?

在具体说明前,先来做一些准备工作

首先定义两张表user和note,user表有id和name两个数据列,note表有id和content两个数据列

然后新建springboot项目,创建对应的User/Note类,以及dao和service接口等等部分(为了方便演示,Service我直接创建的类,没有使用接口),就不再一一列出了

接着重点来了,我们先在UserService中定义insertUser方法:

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 // 插入用户之后,我们插入一条用户笔记
 noteService.insertNote(name + "'s note");
 }
复制代码

对应的NoteService中的insertNote方法如下:

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 }
复制代码

现在我们再定义一个测试方法,注意要把自动回滚关闭:

 @Test
 @Rollback(value = false)
 public void test() {
 userService.insertUser("hikari");
 }
复制代码

看一下现在的执行结果:

REQUIRED

如果不存在外层事务,就主动开启事务;否则使用外层事务

虽然该类型是默认的传播特性,不过我们还是手动指定一下,要记住的是,传播特性是作用于内层方法上的,所以我们加在外层方法上是无效的:

 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 }
复制代码

但是目前这两个方法没有任何干扰,所以我们手动制造点异常:

外层方法关闭事务,内层方法抛出异常

 // @Transactional(rollbackFor = Exception.class) // ←
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 }
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 
 throw new RuntimeException(); // ←
 }
复制代码

按照REQUIRED传播特性,如果外层方法没有使用事务,则内层方法会主动开启事务,运行结果如下:

我们可以发现外层方法没有使用事务(user表中有数据),而内层方法使用了事务(note表进行了回滚,所以无数据)外层方法抛出异常

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 throw new RuntimeException(); // ←
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 }
复制代码

运行结果:

两个表中均无数据,说明外层方法和内层方法使用的是一个事务,所以发生异常一起回滚

SUPPORTS

如果不存在外层事务,就不开启事务;否则使用外层事务

为了避免阅读疲劳,存在外层事务则使用同一个事务这个特性就不演示了,我们演示前一个特性:

外层方法关闭事务,内层方法抛出异常

// @Transactional(rollbackFor = Exception.class) // ←
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 throw new RuntimeException(); // ←
 }
复制代码

结果如下:

其中note表有数据,说明内层方法没有启用事务

MANDATORY

如果不存在外层事务,就抛出异常;否则使用外层事务

外层方法关闭事务

// @Transactional(rollbackFor = Exception.class) // ←
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 }
复制代码

运行结果:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
复制代码

说明必须要有外部事务的存在才能执行

与上面类似,“如果外层方法事务存在,则内层方法使用同一个事务” 这个特性在这里也不赘述了

REQUIRES_NEW

总是主动开启事务;如果存在外层事务,就将外层事务挂起

外层方法抛出异常

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 throw new RuntimeException(); // ←
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 }
复制代码

运行结果:

我们可以看到,外层方法的事务被回滚,而内层方法的事务并没有跟着一起回滚,所以它们使用的不是同一个事务,两个事务不会相互影响

但是有些人可能会觉得,如果内层方法抛出异常,外层方法的事务应该也不会回滚吧?很遗憾并不是这样的,不要被事务迷惑了,内层方法抛出异常(未被try-catch捕获),相当于外层方法抛出异常,所以外层方法的事务依然会回滚

NOT_SUPPORTED

总是不开启事务;如果存在外层事务,就将外层事务挂起

内层方法抛出异常

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 throw new RuntimeException(); // ←
 }
复制代码

运行结果:

user表中无数据,说明外层方法使用了事务,发生了回滚;而note表中有数据,说明没有回滚,没有启用事务

NEVER

总是不开启事务;如果存在外层事务,则抛出异常

外层方法开启事务

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 }
复制代码

运行结果:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
复制代码

和MANDATORY的特性正好相反,MANDATORY是当外层方法不存在事务抛出异常,而NEVER是当外层方法存在事务抛出异常

NESTED

如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务

外层方法抛出异常

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 noteService.insertNote(name + "'s note");
 throw new RuntimeException(); // ←
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 }
复制代码

运行结果:

org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
复制代码

有些人可能会遇到这样的错误,是因为jpa/hibernate是不支持“savepoint”特性的,而NESTED依赖于 “savepoint” 机制,可以在内层方法执行前创建一个“暂存点”,然后开启嵌套的子事务,如果子事务发生异常,会直接回滚到这个暂存点,而不会导致整体事务的回滚

不过没关系,我们可以使用jdbcTemplate,修改后的方法如下:

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userDao.insertUser(user);
 noteService.insertNote(name + "'s note");
 throw new RuntimeException();
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteDao.insertNote(note);
 }
复制代码

这里就不做测试了(其实是因为代码还是有bug,等调好了我会把执行结果放上来),执行结果是两表中均无数据,因为嵌套的子事务依然属于外层的事务,所以外层事务的回滚会连带着嵌套的子事务一起回滚

NESTED/REQUIRES_NEW和REQUIRED有什么区别?NESTED和REQUIRES_NEW有什么区别?

NESTED/REQUIRES_NEW和REQUIRED的区别

先来谈一下它们之间的相同点,如果外层方法不存在时,这两种方式均会创建一个新事务,这一点是一致的

不同点在于,如果外层方法存在事务时,REQUIRED会使用外层同一个事务,而NESTED会创建一个嵌套的子事务,这两种方式最重要的区别就在这里:如果内层方法抛出异常,当使用REQUIRED方式时,即使在外层方法捕获了该异常,也依然会导致外层事务回滚(因为使用的是同一个事务);而如果使用NESTED或REQUIRES_NEW的方式,只要在外层方法捕获了该异常,就不会导致外层事务回滚

 @Transactional(rollbackFor = Exception.class)
 public void insertUser(String name) {
 User user = new User(name);
 userRepository.save(user);
 try {
 noteService.insertNote(name + "'s note");
 } catch(Exception e) {
 e.printStackTrace();
 }
 }
 
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
 public void insertNote(String content) {
 Note note = new Note(content);
 noteRepository.save(note);
 throw new RuntimeException();
 }
复制代码

运行结果:

两张表均无数据,即使外层方法捕获了内层方法的异常,还是会导致整体回滚,因为使用的是同一个事务

NESTED和REQUIRES_NEW的区别

这两种方式都相当于开了一个新事务,但是它们之间最重要的区别就是,NESTED是一个嵌套的子事务,如果外层事务回滚,则这个子事务会被一起回滚,而REQUIRES_NEW的方法则不会

使用场景

不重要的项目/普通场景/懒得考虑那么多

什么都不填 / REQUIRED

内层方法与外层方法几乎没有关联,相当于独立执行

REQUIRES_NEW

内层方法依赖于外层方法,但是外层方法不希望被内层方法影响

在插入用户信息后向日志表中插入一条记录

NESTED

内层方法需要和外层方法的操作同步,发生异常时要么都不回滚,要么一起回滚

SUPPORTS

内层方法不启用事务,但是可以允许外层方法启用事务

NOT_SUPPORTED

内层方法不启用事务,也不允许外层方法启用事务

NEVER

内层方法需要使用外层方法的事务,不希望自己开启一个新事务

MANDATORY

小结

欢迎关注头条号:JAVA大飞哥

点击关注评论转发一波:私信小编发送“架构”(免费获取)

获取微服务、分布式、高并发、高可用,性能优化丶Mysql源码分析等等一些技术资料

最后,每一位读到这里的Java程序猿朋友们,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序猿的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!

相关推荐

当Frida来“敲”门(frida是什么)

0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

服务端性能测试实战3-性能测试脚本开发

前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

Linux和Windows下:Python Crypto模块安装方式区别

一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

Python 3 加密简介(python des加密解密)

Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

怎样从零开始编译一个魔兽世界开源服务端Windows

第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

附1-Conda部署安装及基本使用(conda安装教程)

Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

如何配置全世界最小的 MySQL 服务器

配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

如何使用Github Action来自动化编译PolarDB-PG数据库

随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

面向NDK开发者的Android 7.0变更(ndk android.mk)

订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

OpenSSH 安全漏洞,修补操作一手掌握

1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

Linux:lsof命令详解(linux lsof命令详解)

介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

幻隐说固态第一期:固态硬盘接口类别

前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

新品轰炸 影驰SSD多款产品登Computex

分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...