开源一夏 | Spring 事务传播机制
Spring 事务传播类型
本文主要是讲述 Spring 事务传播机制,也就是大家开发过程中多多少少会遇到的父方法调用子方法,或者 A 方法调用 B 方法时事务的传播机制问题,但是在开始讲 Spring 事务传播机制之前需要先讲一下 Spring 事务的几种传播类型,如图
REQUIRED
REQUIRED 表示如果当前没有事务,就创建一个事务,如果已经存在一个事务,就加入该事务,是 Spring 默认的事务传播类型
也就是说如果外部不存在事务,就开启新的事务,如果外部存在事务,就加入该事务中,如果调用者发生异常,那么调用者和被调用者的事务都回滚。
SUPPORTS
SUPPORTS 表示支持当前事务,如果当前没有事务,就以非事务的方式执行,也就是说外部不存在事务,不会开启新事务,外部存在事务时就加入该事务。
MANDATORY
MANDATORY 表示支持当前事务,但是跟 SUPPORTS 不同的是这种事务传播类型具备强制性,当前操作必须存在事务,如果不存在,则抛出异常。
REQUIRES_NEW
REQUIRES_NEW 表示如果当前存在事务,则把当前事务挂起,重新创建新的事务并执行,知道新的事务提交或回滚,才会恢复执行原来的事务。这种事务传播类型新创建的事务和被挂起的事务没有任何关系,他们是两个相互独立的事务,外部事务失败后回滚,不会回滚内部事务的执行结果,内部事务执行失败抛出异常,被外部事务捕获时,外部事务可以不处理内部事务的回滚操作。
NOT_SUPPORTED
NOT_SUPPORTED 表示以非事务方式执行,如果当前操作在一个事务中,就把当前事务挂起,以非事务方式运行,直到操作完成再恢复事务执行。
NEVER
NEVER 表示以非事务的方式执行,如果当前操作存在事务,则抛出异常。
NESTED
NESTED 表示如果当前方法有一个事务正在运行,则这个方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务进行提交或回滚,也就是说如果封装事务存在,并且外层事务异常回滚,那么内层事务必须回滚,如果内层事务回滚,不影响外层事务的提交和回滚,当封装事务不存在时,按照 REQUIRED 事务传播类型执行。
总结
上面 7 种事务传播类型,日常用到的基本上就 REQUIRED、REQUIRES_NEW、NOT_SUPPORTED 这几种。
Spring 事务举例
现在有这样一种场景,权限管理系统保存角色的时候同时保存角色绑定的菜单,下面开始测试,数据库角色表、角色绑定菜单表 初始数据情况 sys_role、sys_role_menu
无事务情况
父方法 insertRole 保存角色,子方法 insertRoleMenu 绑定角色菜单,代码如图
设置 insertRoleMenu 内部发生异常,则 insertRole 保存角色(普通角色)成功,insertRoleMenu 绑定角色菜单失败,验证结果
父方法未开启事务,子方法开启事务
父方法未开启事务,子方法开启默认事务传播类型 REQUIRED,代码如图
设置 insertRoleMenu 内部发生异常,同时 insertRoleMenu 开启 Spring 默认事务,而 insertRole 未添加事务注解,则 insertRole 保存角色(普通角色 1)成功,insertRoleMenu 绑定角色菜单内部发生异常,事务回滚,验证结果
父方法开启事务,子方法未开启事务
父方法开启默认事务传播类型,子方法未开启事务,代码如图
设置 insertRoleMenu 内部发生异常,同时 insertRole 开启 Spring 默认事务,而 insertRoleMenu 未添加事务注解,则 insertRoleMenu 内部发生异常,会影响外部 insertRole 方法的执行,此时均发生回滚,验证结果
新增(普通角色 2)异常回滚,绑定角色菜单异常回滚。
父方法开启事务,子方法开启事务
父方法开启 Spring 默认事务传播类型,子方法开启 Spring 默认事务传播类型
设置 insertRoleMenu 内部发生异常,同时 insertRole 开启 Spring 默认事务,insertRoleMenu 开启 Spring 默认事务,则 insertRoleMenu 内部发生异常,会影响外部 insertRole 方法的执行,此时均发生回滚,验证结果
新增(普通角色 3)异常回滚,绑定角色菜单异常回滚。
父方法开启 REQUIRED 事务,子方法开启 NOT_SUPPORTED 事务
父方法开启 Spring 默认事务传播类型,子方法开启 NOT_SUPPORTED 事务
设置 insertRoleMenu 内部发生异常,同时 insertRole 开启 Spring 默认事务,insertRoleMenu 开启 Spring NOT_SUPPORTED 事务,由于 insertRoleMenu 将父方法事务挂起,且执行绑定角色菜单之后发生异常,则 insertRoleMenu 会执行成功,同时由于子方法发生异常,则会影响外部 insertRole 方法的执行,则 insertRole 执行失败,事务回滚,验证结果
新增(普通角色 4)增加失败,绑定角色菜单执行成功。
父方法开启 REQUIRED 事务,子方法开启 REQUIRES_NEW 事务
父方法开启 Spring 默认事务传播类型,子方法开启 Spring REQUIRES_NEW 事务传播类型
设置 insertRoleMenu 内部发生异常,同时 insertRole 开启 Spring 默认事务,insertRoleMenu 开启 Spring REQUIRES_NEW 事务,此时 insertRoleMenu 发生异常时,则 insertRoleMenu、insertRole 均会执行失败,事务回滚,验证结果
新增(普通角色 5)增加失败,绑定角色菜单执行失败,事务回滚。
父方法开启 REQUIRED 事务,子方法开启 REQUIRES_NEW 事务
父方法开启 Spring 默认事务传播类型,子方法开启 Spring REQUIRES_NEW 事务传播类型,与上一种不同的是,异常产生发生在父方法最后一行
设置 insertRole 内部发生异常,且异常发生在最后一行,同时 insertRole 开启 Spring 默认事务,insertRoleMenu 开启 Spring REQUIRES_NEW 事务,此时 insertRole 发生异常时,则 insertRole 均会执行失败,事务回滚,insertRoleMenu 执行成功,验证结果
新增(普通角色 6)增加失败,绑定角色菜单执行成功。
父方法开启 REQUIRED 事务,子方法开启 REQUIRES_NEW 事务
父方法开启 Spring 默认事务传播类型,子方法开启 Spring REQUIRES_NEW 事务传播类型,异常产生发生在父方法最后一行,同时父方法、子方法是在同一个类中的内部调用
设置 insertRole 内部发生异常,且异常发生在最后一行,同时 insertRole 开启 Spring 默认事务,insertRoleMenu 开启 Spring REQUIRES_NEW 事务,此时 insertRole 发生异常时,则 insertRole、insertRoleMenu 执行失败,事务回滚,验证结果
新增(普通角色 7)增加失败,绑定角色菜单执行失败。
Spring 事务失效场景
数据库不支持事务
Spring 事务生效的前提是连接的数据库存储引擎支持事务,比如 Mysql 的 MyISAM 存储引擎不支持事务,则 Spring 事务会失效。
事务方法未被 Spring 管理
如果事务所在的类没有被交给 Spring 管理,或者说没有加载到 Spring IOC 容器中,则该类方法上的事务会失效,比如
如果没有 @Service 注解,则该类下的方法上的事务会失效。
事务方法私有 private
如果事务注解的方法为内部的私有 private 方法,则事务会失效。
同一个类中的方法调用
同一个类中的父方法调用子方法,都开启了事务,如图
子方法的事务会失效
不正确的异常捕获
如果发生异常的代码被 try catch 捕获,则 Spring 管理器无法感知到异常,导致事务失效。
错误的异常类型
Spring 事务默认的捕获的异常是 RuntimeException,如果代码中执行失败抛出了 Exception 异常,则事务会失效
总结
以上内容基于日常工作及学习,仅供参考,有兴趣的同学也可以买一本《深入理解分布式事务 原理与实战》,读之受益良多。
版权声明: 本文为 InfoQ 作者【六月的雨在infoQ】的原创文章。
原文链接:【http://xie.infoq.cn/article/d3e6d4f61310cdc35fc04478a】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论