spring 注解事务遇到的大坑(查日志报Transaction rolled back because it has been marked as rollback-only,嵌套事务)

说我遇到的问题前请大家回顾一下spring的注解标注事务的几种,主要看标红的,其他的以后项目中用到在说,这个链接有对于嵌套事务的介绍

注解解释
REQUIRED表示业务方法需要在一个事务中处理,如果业务方法执行时已经在一个事务中,则加入该事务,否则重新开启一个事务。这也是默认的事务传播行为
NOT_SUPPORTED声明业务方法不需要事务,如果业务方法执行时已经在一个事务中,则事务被挂起,等方法执行完毕后,事务恢复进行
REQUIRES_NEW表明业务方法需要在一个单独的事务中进行,如果业务方法进行时已经在一个事务中,则这个事务被挂起,并重新开启一个事务来执行这个业务方法,业务方法执行完毕后,原来的事务恢复进行
MANDATORY该属性指定业务方法只能在一个已经存在的事务中进行,业务方法不能发起自己的事务;如果业务方法没有在一个既有的事务中进行,容器将抛出异常
SUPPORTS该属性指定,如果业务方法在一个既有的事务中进行,则加入该事务;否则,业务方法将在一个没有事务的环境下进行
NEVER该属性指定,如果业务方法在一个既有的事务中进行,则加入该事务;否则,业务方法将在一个没有事务的环境下进行
NESTED该属性指定,如果业务方法在一个既有的事务中执行,则该业务方法将在一个嵌套的事务中进行;否则,按照REQUEIRED来对待。它使用一个单独的事务,这个事务可以有多个rollback点,内部事务的rollback对外部事务没有影响,但外部事务的rollback会导致内部事务的rollback。这个行为只对DataSourceTransactionManager有效

上面是在spring中用注解@Transactional(propagation=Propagation.xxxx)的几种事务

这里主要讲我遇到的一个问题:前端访问时,后端异常了,我就去查日志,查出来但是查出来的日志信息是Transaction rolled back because it has been marked as rollback-only,之后经过查询问题发现是因为我在每一层都做了trycatch,而当时所有的事务都是用到默认的,内层事务的样式是下面这样的

@Transactional (propagation = Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class)
public 返回值 方法名(参数){
		try{
		正常逻辑代码
		return 正常信息
		}catch(Exception e){
			记录日志
			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
			throw new RuntimeException(e)
	}
}

外层事务差不多也是这样,但是造成了一些问题:
按照你的逻辑:你会以为自己把异常catch住了,手动回滚,再把异常抛给上级,这样上级的日志也会记录这个异常堆栈信息(每个catch里都加了手动回滚)

但是现实不是这样的,就像上面那个链接里说的一样,

一般我们都会使用默认的传播方式,这样无论外层事务和内层事务任何一个出现异常,那么所有的sql都不会执行。在嵌套事务场景中,内层事务的sql和外层事务的sql会在外层事务结束时进行提交或回滚。如果内层事务抛出异常e,在内层事务结束时,spring会把事务标记为“rollback-only”。这时如果外层事务捕捉了异常e,那么外层事务方法还会继续执行代码,直到外层事务也结束时,spring发现事务已经被标记为“rollback-only”,但方法却正常执行完毕了,这时spring就会抛出“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

链接的作者给出了解决方案

  1. 如果希望内层事务抛出异常时中断程序执行,直接在外层事务的catch代码块中抛出e.
  2. 如果希望程序正常执行完毕,并且希望外层事务结束时全部提交,需要在内层事务中做异常捕获处理。
  3. 如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务的传播方式指定为PROPAGATION_NESTED。注:PROPAGATION_NESTED基于数据库savepoint实现的嵌套事务,外层事务的提交和回滚能够控制嵌内层事务,而内层事务报错时,可以返回原始savepoint,外层事务可以继续提交。

人家给的解决方案很好,但是对于我这种对每一种事务场景不熟练的人,在结合同事聊天,总结一个方案。
我不清楚大家的项目大概是怎么分层的,我这是Controller-Service-Manager-Dao这样分的,下面也是按这个说的。

方案关键字:分清边界范围
1、在manager层都不加trycatch(涉及到manager层的都是查自己的库),增删改只加@Transactional (propagation = Propagation.REQUIRED,rollbackFor=Exception.class)

2、在service层如果需要加事务就考虑是加REQUIRED还是NESTED(看表格),但是还是不加trycatch
有2种情况例外,
(1)、比如org.springframework.dao.DuplicateKeyException,你想提示上级唯一索引重复了,就可以这么做,但是一定是在最外层事务捕获,记录日志,返回这个信息,3(1)的情况AOP也不会拦截异常
(2)、调别人的API或者IO异常等,如果用trycatch,那catch里可以记日志再抛出Exception,一定不要手动回滚

3 我分了两种情况,一种是别人调我的API,和我自己controller调我自己的SPI
(1)、第一种情况我直接用sping的AOP统一处理,在别人调我的API返回结果之前trycatch,是否有异常,有的话可以记录日志,返回一个统一的异常信息告诉调用方我这报异常了,没有直接返回结果,这里不用回滚,因为在到这个AOP之前事务就已经结束了,异常回滚已经完成了
(2)、第二种直接在controller层调用spi的外面加一个trycatch,就行了,

上面我自己的方案感觉还可以,业务逻辑代码瞬间整洁,少了很多的trycatch,也让@Transactional发挥了它自己的作用

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值