记spring事务传播机制引发的问题
引发问题
其实原因是这样的,今天早上sentry发来报错,Transaction rolled back because it has been marked as rollback-only
,这个事务会回滚,因为之前已经标记为回滚了。
其实这已经不是第一次遇到了,但是一直没有刨根问题找找到底是为啥,正好最近在写一个简易的事务管理器,这不是逮着了不得一锤子凿穿么。。。。
看了下这段代码,基础的模型是这样的:
@Transactionalclass 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的增强对象中就捕获了这个异常。
又运行了一个处理方法completeTransactionAfterThrowing
,这个方法里面回去判断当前是不是最外层的事务增强,因为其实事务处理器的远离就是用了一个ThreadLocal变量,里面保存了当前获取的数据库的Connection
对象,这个对象会在本线程里面一直传递,然后AOP又是一层一层嵌套的,比如ClassA
套了一层,ClassB
套了一层,ClassA
调用ClassB
,其实就是调用的时候会增加一个调用深度缓存在ThreadLocal里面,这样当ClassB
调用完成推出的时候,就不会直接调用commit()
方法,因为他不是最外层的调用,只有在最外面最后面返回的时候才能判断整个事务执行完没有问题,才能进行commit()
提交操作,保证这一整个业务的完整性,同样回滚也是在一整个业务的最外面进行的rollback()
操作。
问题就在与这个异常捕获的地方,当他判断这个不是最外层的操作的时候,他就只会设置这个Threadlocal
变量里面的rollbackonly
的属性为true
,也就是把它标记为回滚,他就返回把原始的Exception
继续向外层抛出,但是这时候ClassA
又把它给捕获了,继续运行正常的逻辑,当ClassA
运行完成后,AOP增强有会按正常的逻辑去commit()
数据,这时候判断roobackonly
标记位,发现woc,你这里不是标记让我回滚么,现在又让我正常提交?什么玩意儿?
算了我给你报个错全回滚了。。。。所以就造成了上面的问题。也就是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概念)
- mysql 事务之使用savepoint部分回滚
- Spring 采用保存点(Savepoint)实现嵌套事务原理
- CRUD更要知道的Spring事务传播机制
- Spring事务传播行为
以上是 记spring事务传播机制引发的问题 的全部内容, 来源链接: utcz.com/a/34732.html