spring中事务失效场景和循环依赖问题

本文详细探讨了Spring中事务可能出现的失效场景,包括检查异常处理、业务方法异常捕获、AOP切面顺序、非public方法、父子容器问题、内部方法调用和多线程操作数据。同时,介绍了Spring的循环依赖问题,分析了单例对象set循环依赖、构造方法循环依赖的解决思路,并探讨了一级、二级和三级缓存的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、spring中事务失效场景
1、抛出检查异常导致事务不能正确回滚
原因:spring默认只会回滚(runtimeException和Error子类) 非检查异常
解法:配置rollbackFor 属性 为Exception.calss


解法
2、业务方法内自己try-catch异常 导致事务不能正常回滚
原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
解法1:异常原因抛出
解法2:手动设置TransactionStastus.setRollbackOnly()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
3、一个方法在多切面情况下,aop切面顺序导致事务不能正确回滚
原因:事务的切面优先级最低,但如果自定义切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正却抛出异常,自定义的事务切面无法知悉,从而做出一些错误的判断,不能正确回滚异常
解法1:同情况2
解法2:设置切面的执行顺序 @(ordered.LOWEST_PRECEDENCE-1) (不推荐)

在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/737beb00d6f54f23aaeea9f325d7d00b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiA55Sf5Lik5qyi,size_16,color_FFFFFF,t_70,g_se,x_16
在这里插入图片描述
在这里插入图片描述
!](https://img-blog.csdnimg.cn/5b7fb14b094b4218b95d0b668bd0bd5b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiA55Sf5Lik5qyi,size_16,color_FFFFFF,t_70,g_se,x_16)

在这里插入图片描述
在这里插入图片描述
4、非public方法导致事务失效
原因:Spring会创建代理Bean(Spring为方法创建代理、添加事务通知、前提条件都是该方法是public的)
解法:改为public

5、父子容器导致的事务失效
原因:子容器扫描范围过大,吧未加事务配置的serviceBean扫描进来
解法1:各扫描各的,不要图简便
解法2:不要用父子容器,所以的Bean放在同一容器(springBoot就一个容器,不会出现这种情况)
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
6、调用本类方法导致事务的传播行为失效(在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务,只要不是用的代理对象proxy的,被调用的目标方法都不会开启事务,目标方法里面报错也不会回滚)

原因:spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。

解法1:注入本类代理对象,使用代理对象来调用(spring中通过三级缓存解决了循环依赖问题)
解法2:通过容器上下文方式获取代理对象 (Service6) AopContext.currentProxy()
容器获取前提:设置@EnableAspectJAutoProxy(exposeProxy=true)表示通过aop框架暴露该代理对象,aopContext能够访问.
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
7、多线程或多请求操作同一个(一行)数据的情况

@Transaction没有保证select语句的原子行为
原因:事务的原子性仅涵盖insert、update、delete、select…for update语句,select方法并不阻塞
理解:数据库的insert、update、delete、select…for update操作默认实现了隐性事务(行锁也叫互斥锁),select语句并没有实现
@Transaction方法导致synchronized失效
原因:synchronized保证的仅是目标方法的原子性,环绕目标方法的还有commit等操作,它们并未处于sync块内
解法1:使用select…for update 的互斥锁机制(数据层面)
解法2:在方法最外层调用加锁(代码控制)
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述
二、spring中循环依赖及解决方案

1、循环依赖–单例对象set循环依赖

一级缓存

能够保证bean在容器中只存在一份(单例),但解决不了循环依赖的问题

在这里插入图片描述
一级缓存中循环依赖问题产生
在这里插入图片描述

二级缓存

二级缓存能够解决循环依赖问题,但如果原始bean在生成代理bean的过程中,使用了切面比如@Transaction,
对原始bean做了增强,此时注入的bean是原始bean,并不是代理之后的bean,因此会丢失增强的功能
(这里和spring中事务,调用本类方法导致事务的传播行为失效 大同小异)

在这里插入图片描述
二级缓存出现问题
在这里插入图片描述

三级缓存

spring用三级缓存解决了set循环依赖的问题,提前创建代理对象保留了bean在代理之后所增强的功能

在这里插入图片描述
2、循环依赖–构造方法循环依赖

spring的三级缓存并不能解决构造的循环依赖,因为set循环依赖是:最开始的原始对象A,B是能够被创建,构造方法并不能创建最原始的A,B对象(A的实例都创建不成功)。

在这里插入图片描述
解决思路:
在这里插入图片描述方案一:
在这里插入图片描述
方案二:
在这里插入图片描述
方案三:

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

### Spring 框架事务管理失效的常见场景及其解决方案 #### 场景一:未启用事务管理 当应用程序配置中缺少 `@EnableTransactionManagement` 注解或 XML 配置中的 `<tx:annotation-driven/>` 时,即使方法上标注了 `@Transactional`,也不会有任何效果。 解决办法是在启动类或配置文件中加入必要的声明来开启基于注解的事务支持[^2]。 ```java @Configuration @EnableTransactionManagement public class AppConfig { // Configuration code here... } ``` #### 场景二:异常被捕获并吞掉 如果在一个带有 `@Transactional` 的方法内部捕获到了异常却没有抛出,那么这个异常就不会触发回滚操作。这通常发生在开发者为了防止程序崩溃而简单地忽略了错误的情况之下。 应当让受控业务逻辑能够感知到发生的异常以便决定是否继续执行流程;对于不可恢复性的严重问题则应允许其传播给调用者从而实现自动回滚机制[^3]。 #### 场景三:使用异步方式调用服务层接口 由于默认情况下每个线程都有独立的作用域,在多线程环境下可能会遇到无法识别当前存在的事务上下文的问题。因此直接通过代理对象同步访问其他组件的方法可以正常工作,但是利用诸如 `CompletableFuture.runAsync()` 或者 `@Async` 这样的特性就会破坏原有链路内的事务状态传递关系。 建议采用编程式的 TransactionTemplate 来手动控制子任务所属父级事物边界范围,或者调整为同一线程内顺序化处理模式以维持一致的行为表现形式。 #### 场景四:违反只读属性设置原则 某些持久化引擎(比如 Hibernate)会因为开启了 flush() 自动刷新策略而在查询期间意外修改实体字段进而导致脏写入现象发生。此时即便指定了 readOnly=true 参数也无法阻止此类变更动作的发生。 最佳实践是要确保所有参与同一笔交易过程里的 DAO 层交互都遵循相同的存取权限级别定义,并且尽可能减少不必要的更新语句频率以免影响性能指标并发安全性[^1]。 #### 场景五:跨多个不同数据源的操作 一旦涉及到两个以上相互关联却又隶属于各自独立连接池资源的对象实例之间的协作互动,就很容易引发分布式一致性难题。特别是当其中一个环节失败之后怎样安全地中止整个批次提交成为了一个棘手的技术挑战。 推荐做法是引入 XA 协议兼容型中间件产品如 Atomikos、Bitronix 等来进行全局协调调度,亦或是重构设计思路使之转换成幂等性质更强的服务单元组合而成的整体架构体系结构。 #### 场景六:嵌套式调用同一个 Bean 方法 假设有 A B 是由相同容器实例化的单例 bean ,而且它们之间存在互相依赖关系即 A 调用了 B 中同样加有 @Transactional 标记函数 C 。然而根据 JDK 动态代理原理可知除非经过特殊定制否则普通反射手段难以穿透内部私有成员变量所指向的目标对象的真实身份信息,所以这里很可能造成新的外部包裹层而非继承已有正在进行之中的那个共享环境。 为了避免上述情况出现,应该考虑重新规划模块间的职责划分界限,尽量避免循环引用情形的同时也方便后期维护人员理解整体运作机理。 #### 场景七:Propagation 设置不合理 不同的 Propagation 枚举值决定了新发起请求如何融入现有活动之中去。如果不小心选择了不当选项的话,很可能会引起意想不到的结果,例如重复创建额外级别的隔离副本或者是忽视已经存在的约束条件限制等等。 务必仔细权衡各种可能性后再做出最终抉择,一般而言 REQUIRED 类型最为常用因为它能够在必要时候新建 session 同时又能很好地复用已有的 context 结构体。 #### 场景八:Rollback For 不匹配 只有那些显式指定要响应的具体 Exception 子类别才会促使系统强制终止当前正在运行的任务流并且撤销之前所做的任何更改记录。反之如果没有特别说明的话,默认只会对 unchecked runtime exception 生效而已。 所以在编写 catch block 分支判断逻辑的时候一定要谨慎行事,最好把所有可能遇见的风险因素全部罗列出来加以防范措施部署到位。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值