转载

记spring事务传播机制引发的问题

其实原因是这样的,今天早上sentry发来报错, Transaction rolled back because it has been marked as rollback-only ,这个事务会回滚,因为之前已经标记为回滚了。

记spring事务传播机制引发的问题

其实这已经不是第一次遇到了,但是一直没有刨根问题找找到底是为啥,正好最近在写一个简易的事务管理器,这不是逮着了不得一锤子凿穿么。。。。

看了下这段代码,基础的模型是这样的:

@Transactional
class A{
    public void testA(){
        // 操作了一些数据库表
        try{
            classB.testB();
        }catch(Ex e){
            log.info("不让子业务影响主流程,不抛异常只记录");
        }
        return;
    }
}


@Transactional
ClassB{
    pulbic void testB() throws Exception{
     //操作了一些数据库   
    }
}
复制代码

然后一切就这么发生了shit happens,追踪了一下spring的事务代理机制,其实就是 @Transactional 这个注解,会用cglib吧当前的方法或者类进行AOP代理,这个代理就是加了一个判断,用来看看你这个是不是需要一个新的Transaction,transation里面其实包含了以数据库Connection连接对象,用来向数据库提交语句运行。

spring事务传播的机制默认是Required,也就是如果当前有事务,就加入到这个事务里面,不重新 db.getConnection 去新创建一个事务。然而就是这个操作造成了报错。

因为在 ClaasA 里面调用 ClassB 方法时,经过查看日志,其实是发生了 DeadLockException 的异常,然后 ClassB 的Aop的增强对象中就捕获了这个异常。

记spring事务传播机制引发的问题

又运行了一个处理方法 completeTransactionAfterThrowing ,这个方法里面回去判断当前是不是最外层的事务增强,因为其实事务处理器的远离就是用了一个ThreadLocal变量,里面保存了当前获取的数据库的 Connection 对象,这个对象会在本线程里面一直传递,然后AOP又是一层一层嵌套的,比如 ClassA 套了一层, ClassB 套了一层, ClassA 调用 ClassB ,其实就是调用的时候会增加一个调用深度缓存在ThreadLocal里面,这样当 ClassB 调用完成推出的时候,就不会直接调用 commit() 方法,因为他不是最外层的调用,只有在最外面最后面返回的时候才能判断整个事务执行完没有问题,才能进行 commit() 提交操作,保证这一整个业务的完整性,同样回滚也是在一整个业务的最外面进行的 rollback() 操作。

记spring事务传播机制引发的问题
问题就在与这个异常捕获的地方,当他判断这个不是最外层的操作的时候,他就只会设置这个 Threadlocal 变量里面的 rollbackonly 的属性为 true ,也就是把它标记为回滚,他就返回把原始的 Exception 继续向外层抛出,但是这时候 ClassA 又把它给捕获了,继续运行正常的逻辑,当 ClassA 运行完成后,AOP增强有会按正常的逻辑去 commit() 数据,这时候判断 roobackonly

标记位,发现woc,你这里不是标记让我回滚么,现在又让我正常提交?什么玩意儿?

记spring事务传播机制引发的问题
算了我给你报个错全回滚了。。。。所以就造成了上面的问题。也就是 Transaction rolled back because it has been marked as rollback-only 这个异常,我想了一会儿一开始觉得不合理,因为我就是想让这个东西回滚啊,你个我报错干啥?其实后来想通了,这就是spring定义的Required传播机制,因为我就只有一条connection,一条事务,你如果 ClassB 报错了,让我回滚,那我 ClassA 之前做的操作算什么,无论如何都会回滚的,你 ClassB

自己做的操作应该自己消化啊,你抓鲁迅关我周树人什么事?

所以将 ClaasB.testB() 方法加一个 @Transactional(propagation= Propagation.NESTED) ,指定 NESTED 的传播等级,这里用了一个 SavePoint 事务保存点的技术,这个其实是Mysql提供的一个功能,能够实现部分回滚(这个东西感觉又点儿神奇),其实就是在内层事务里面加了一个标记点,假如内层事务出问题了,就回滚到这个记录点,如果成功了最外层事务提交了之后我在跟着一起提交,如果我自己报错了,那我就自己回滚,不影响你 ClassA 。不得不说Spring这个框架做的还是很完善的,咱家错怪他了。。。

下面贴出来事务各种传播机制,以警示人: PROPAGATION_REQUIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。

PROPAGATION_SUPPORTS:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。


PROPAGATION_MANDATORY:该传播级别要求上下文中必须存在事务,否则抛出异常。


PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)


PROPAGATION_NOT_SUPPORTED:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)


PROPAGATION_NEVER:该传播级别要求上下文中不能存在事务,否则抛出异常。


PROPAGATION_NESTED:嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务。(save point概念)
复制代码

很遗憾的说,推酷将在这个月底关闭。人生海海,几度秋凉,感谢那些有你的时光。

原文  https://juejin.im/post/5f1942f9f265da22a9247fa8
正文到此结束
Loading...