【spring】Spring事务相互调用失效分析,事务回滚失败原因

文章可能会动态更新 保证结论不误导人 拒绝复制粘贴

1.第一种情况 :调用方加上@Transactional 注解,被调用方未加上注解 (同一个service类)
Exp:

@Transactional
void methodA(){
    
    
// insert table1
methodB(Object obj);
}

void methodB(){
    
    
// insert table2
}

结论:methodB()出现异常,抛给methodA,A中捕获到异常,进行回滚处理,table1,table2都未插入

2.第二种情况:调用方未加上注解,被调用方加上注解(同一个service类)
Exp:


void methodA(){
    
    
// insert table1
methodB(Object obj);
}

@Transactional
void methodB(){
    
    
// insert table2
}

结论: methodB()非事务执行。spring的aop proxy 代理对象执行methodA(),由于methodA()上面没有注解标记,所以不会开启事务 , 所以不管A方法里面写什么 ,都不能回滚,table1插入 table2未插入。

基于上述结论的理解
3.第三种情况:调用方未加上注解,被调用方加上注解(不同service类
结论:此时被调用方在另一个类BserviceImpl,该类代理对象bProxy调用methodB()时 是有注解的 能开启事务,所以出现异常都会回滚 table1,table2都未插入。

拓展:
存在业务逻辑: 需要批量插入员工信息至公司表与部门表,员工信息要么同时在公司、部门表,要么同时不存在。
因此需要将 insert t_company 和 insert t_dept 进行事务管理,当第二个员工插入部门表出现异常时,第一个已正常插入的员工不能受到影响,第二个员工的信息需要回滚。
此时调用方 methodA() 上面肯定不能加上注解 ,如果将methodB(){ insert t_company,insert t_dept}写在同一个service中,并加上注解,就会出现上述第二种情况,回滚失败,第二个员工的信息存在公司表 而不存在部门表。

解决方式:1.写在另一个service 2.写在同一个service也可以,获取代理对象 通过代理对象调用 methodB() 这样就能执行回滚了(推荐), 方式较多 可以@Autowired一遍自己 (推荐) 或者用下述方法 ,具体原理 在下面超链接的那篇文章有解释。

// 需要先在启动类加上注解 @EnableAspectJAutoProxy(exposeProxy = true)
YourServiceImpl yourService = (YourServiceImpl ) AopContext.currentProxy();
yourService.methodB();

拓展:

之前有评论 我回复了 但是因为那条回复不够严谨 可能造成误导 现在已经将评论删除 请见谅;
在通过对aop有了稍微更深入一点点的学习后 ,
(如果有看到这篇文章感到困惑的朋友 可能需要参见我的另一篇文章 spring工作流程 )
突然发现以前的认知不够完善 , 上述结论仍然是正确的 但感觉不够深入 所以进行补充。

  1. 可以看到 我们在testB方法中 加上了注解 传播级别设置为never ,可是并没有生效 ,也就是说 这个注解相当于没写,对的 没听错
    在这里插入图片描述
  2. 我们换用bean(该场景下为代理对象)来调用,可以看到达到了我们预期的效果在这里插入图片描述
  3. 那么问题来了 我们知道事务开启一定是在代理对象执行的,那么下述情况 为什么还会回滚呢
    在这里插入图片描述
    注意:我们看到即使是写了注解 传播机制为never也未生效 ,且这只是在同一个对象内执行的 , 所以仍然能回滚的原因 不能用事务传播机制默认为自动加入去理解 这是个初学者(没错 就是初学时的我)容易犯的错误

下面用伪代码分析代理到底做了什么:


class proxy extends TransactionalController{
    
    
	/**
     * bean对象 注意现在正在准备执行的步骤就是动态代理 所以这个bean是指代理前的bean
     */
    private TransactionalController transactionalController;

    /**
     * 代理testA方法
     * @author qkj
     */
    public void testA(){
    
    
        // 事务开启
        conn.start;
        try {
    
    
            // 被代理的testA方法
            transactionalController.testA()->{
    
    
                // 里面包含了testB方法
                transactionalController.testB();
            };
        // 所以即使testB方法中 出现异常 也能捕获 并回滚
        }catch (Exception e){
    
    

            conn.rockback;
        }
        conn.commit;

    }


}

事务的传播机制

既然上面那种情形 不是传播机制 那么传播机制到底怎么体现呢?

我们从REQUIRES_NEW 分析 先看看复制粘贴的话是怎么描述的:

.  粘贴1:如果当前存在事务,就把当前事务挂起;如果当前方法没有事务,就新建事务;
.  粘贴2:不管是否存在事务,业务方法总会自己开启一个事务,如果在已有事务的环境中调用,
	已有事务会被挂起,新的事务会被创建,直到业务方法调用结束,已有事务才被唤醒

第二句会比第一句稍微好理解一点点 但还是有些抽象 咱也不知道网上复制一大堆 这些话他们到底懂不懂.

模拟一种可能存在的业务,结合业务理解会比较容易:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
运行结果:
在这里插入图片描述

数据库结果:
摸鱼人员表:
在这里插入图片描述
绩效记录表:
在这里插入图片描述

这样符合实际业务,摸鱼被抓了 绩效该扣还是得扣 不会因为人员名单插入异常就全部回滚了,
这里因为绩效业务事务传播机制是Propagation.REQUIRES_NEW,外层摸鱼业务有事务,当执行到绩效时,绩效开启一个新的事务 并将旧的事务挂起,直到绩效事务提交后,旧事务继续,这样就保证了绩效是个独立的事务。有个这个分析,其它的也就比较容易理解了。

おすすめ

転載: blog.csdn.net/qq_36268103/article/details/112307201