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

引发问题

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

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

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

@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的增强对象中就捕获了这个异常。

又运行了一个处理方法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

回到顶部