spring事务失效的场景
1.访问权限
在AbstractFallbackTransactionAttributeSource
类的computeTransactionAttribute
方法中有个判断,如果目标方法不是 public,则TransactionAttribute
返回 null,即不支持事务。
也就是说,如果我们自定义的事务方法(即目标方法),它的访问权限不是public
,而是 private、default 或 protected 的话,spring 则不会提供事务功能。
2.final修饰
spring的事务基于AOP,而AOP基于cglib ,而cglib动态代理的本质是基于继承实现的,在java中一旦方法被final修饰,则不能被重写,类被final修饰不能被继承。
对于final修饰方法:下面这段话摘自《Java编程思想》第四版第143页:
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
3.方法内部调用
在同一个类中,方法直接内部的调用,会导致事务失效
实例代码:
具体原因:在于调用的updateStatus方法不是代理对象的updateStatus方法
在我们看到在事务方法 add 中,直接调用事务方法 updateStatus,updateStatus 方法拥有事务的能力是因为 spring aop 生成代理了对象,但是这种方法直接调用了 this 对象的方法,所以 updateStatus 方法不会生成事务。
解决:通过依赖注入获取代理对象,通过代理对象调用其updateStatus方法就可。
或者通过AopContext类获取到代理对象,再通过代理对象调用
4.未被spring管理
也就是未注入到spring,事务未生效
5.多线程调用
spring事务是通过数据库链接实现的,同一事务是同一数据链接,不同线程涉及的事务是属于不同的数据库链接,只有拥有同一数据库链接,事务才可以提交和回滚。
实例问题代码:
6.表是否支持事务
在myisam 数据库引擎中是不支持事务的
扩展:myisam 不支持事务,不支持行锁,不支持外键
7.事务是否开启
传统spring项目需要在配置文件 applicationContext.xml 中开启事务,事务才可以生效
对于springboot项目 springboot 通过DataSourceTransactionManagerAutoConfiguration
类,已经默默地帮你开启了事务。只需要配置spring.datasource
相关参数即可。
8.错误的传播特性
使用@Transactional注解时,是可以指定propagation参数的。
该参数的作用是指定事务的传播特性,spring 目前支持 7 种传播特性:
-
REQUIRED 如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。
-
SUPPORTS 如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
-
MANDATORY 当前上下文中必须存在事务,否则抛出异常。
-
REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
-
NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
-
NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
-
NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
9.手动捕获处理了异常
程序员将操作数据库方法发生的异常自己捕获并处理,让spring认为该代码未发生异常,则便不会发生事务回滚
实例问题代码:
10.手动抛出spring事务不捕获的异常
对于spring事务来说,默认只捕获RuntimeException和Error
默认情况下只会回滚RuntimeException
(运行时异常)和Error
(错误),对于普通的 Exception(非运行时异常),它不会回滚。
实例问题代码:
11.自定义了回滚异常
@Transactional 注解声明事务时,有时我们想自定义回滚的异常,通过设置rollbackFor
参数
实例代码:
如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了 SqlException、DuplicateKeyException 等异常。而 BusinessException 是我们自定义的异常,报错的异常不属于 BusinessException,所以事务也不会回滚。
即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。 这是为什么呢? 因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成: Exception或Throwable
12.嵌套事务异常未捕获导致未必要回滚
问题代码:
public class UserService {
doOtherThing发生异常,该异常未捕获,会继续向上抛出到add方法,导致在add方法内正常更新操作的insertUser方法也发生不必要回滚
解决办法便是通过手动捕获doOtherThing发生的异常,使异常不再向上抛出。就不会使add方法捕获到异常发生updateUser的回滚。
扩展:其他注意点
大事务问题(声明式事务的问题)
直接在类或方法上加上@Transational注解,使得不必要使用事务的方法加上了事务,造成整个程序事务非常耗时
推荐使用编程式事务
编程式事务
:spring 还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务。
在 spring 中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的 execute 方法中,就实现了事务的功能。
相较于@Transactional注解声明式事务,我更建议大家使用基于TransactionTemplate的编程式事务。主要原因如下:
避免由于 spring aop 问题导致事务失效的问题。
能够更小粒度地控制事务的范围,更直观。
建议在项目中少使用 @Transactional 注解开启事务。但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用 @Transactional 注解开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。
复习:事务的传播特性
事务的传播特性,spring 目前支持 7 种传播特性:
-
REQUIRED 如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。
-
SUPPORTS 如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
-
MANDATORY 当前上下文中必须存在事务,否则抛出异常。
-
REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
-
NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
-
NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
-
NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
参考文章,原文地址:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!