UnexpectedRollbackException
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
问题产生的原因: 类中的子事务抛出异常后,子事务已经回滚,spring就已经标记的回滚状态(rollback-only),主事务在被提交时就会报上述错误。
举例子说明(花了半天一点一点验证的,其中还了解了spring事务的几个级别)
A→B→C
举例说明 下面ABC代表三个类 A调用B,B调用C,其中C中操作数据库,B类做业务逻辑处理,要求在业务逻辑报错的情况下,不报错返回异常信息即可{return message};现在B业务已经抛出异常并且要返回message此时再返回数据,C也要回滚事务;
代码如下: A B C调用关系的三个方法 测试使用
A.method(){
B.method();
}
}
=======================================
B.method(){
try{
C.method();
}catch(Exception e){
}
return message;
}
=====================================
C.method() {
try{
add("数据");
}catch(Exception e){
}
//更好的区分 单独定义一个异常
try{
throw new BusinessException("报错")
}catch(Exception e){
throw new BusinessException("报错");//自定义异常
}
}
===========================================
报错:
###分析一
可以看到在C抛出异常后,B在拿到异常继续走完流程也要提交事务,此时就会产生上述报错信息;
问题分析:Spring源码中有 Geting transaction for
Geting transaction for(C).....动作完成,已经把事务标记rollback-only,
继续Geting transaction for(B) 报错 Transaction has been rolled back because it has been marked as rollback错误
org.springframework.transaction.support.AbstractPlatformTransactionManager中
/**
* Return whether to globally mark an existing transaction as rollback-only
* after a participating transaction failed.
*/
public final boolean isGlobalRollbackOnParticipationFailure() {
return this.globalRollbackOnParticipationFailure;
}
参数默认true
当这个参数为false时会让主事务决定回滚,这个可以有调用方决定。但是只适用数据访问失败且所有操作事务提交,显而易见这个要依据自己需求而定,可以解决刚刚问题;
###解决办法一:
添加<property name="globalRollbackOnParticipationFailure" value="false" /> 控制台有返回,数据也写入了 没有回滚
<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="mips_dataSource"></property>
<property name="globalRollbackOnParticipationFailure" value="false" />
</bean>
结局办法:配置 A B类的事务,把事务给去除掉,这样即使A B出现异常也会继续执行,不会影响C的事务处理
<aop:pointcut id="interceptorPointCuts"
expression="execution(* com.th.supcom.mips.impl.service..*.*(..))
and !execution(* com.th.supcom.mips.impl.service.internal.core.AtmServiceImpl.*(..))
and !execution(* com.th.supcom.mips.impl.service.external.AtmOutpSettlementServiceImpl.*(..))/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" />
</aop:config>
###分析二:
上述问题我们可以结合具体需求,比如问题,B有报错但是会继续返回异常信息,也就是要吃掉这个事务,信息正常放回;
断点提示中也有一个AopUtils,也说明产生了不同的事务,且违背了面向切面的原理;我们可以从配置切面参数来管理是否需要事务;
参数配置:
<aop:pointcut id="interceptorPointCuts"
expression="execution(* com.bai.du.www.impl.service..*.*(..))
and !execution(* om.bai.du.www.impl.service.B.*(..)) ----------剔除B事务 也可以配置具体方法
and !execution(* om.bai.du.www.impl.service.A*(..))/> -----------剔除C事务
<aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" />
</aop:config>
首先要清楚C处理数据事务肯定要回滚,所以需要事务(剔除C数据修改,报错后数据不会回滚),但是如果我们只配置B不要事务,可以试运行下
结果还是报错: A也会提交,所以要把A B 都要配置进去;体现事务的原子性
这里的例子可以验证 事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability) {自己可以动手试试可以更好的理解}
所以为了不影响整体项目事务配置和回滚参数设置,推荐使用方法二处理该问题;