说我遇到的问题前请大家回顾一下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
”
链接的作者给出了解决方案
- 如果希望内层事务抛出异常时中断程序执行,直接在外层事务的catch代码块中抛出e.
- 如果希望程序正常执行完毕,并且希望外层事务结束时全部提交,需要在内层事务中做异常捕获处理。
- 如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务的传播方式指定为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
发挥了它自己的作用