【踩坑日记】那些年Spring声明式事务不回滚的离奇事件

最近在做一个项目的时候,写着写着到最后自测的时候发现存在事务不回滚的情况,检查数据库时还是有发现数据不一致的情况,当时我手里的西瓜刀就不冷静了,明明大家都是用的注解@Transactional,凭什么我的腰间盘就这么突出???
后面想想肯定是事务没起作用,出现异常的时候事务没有回滚。在项目中配置的时候我采用的是声明式事务,个人觉得优点:使用方便,一次配置就可以了;缺点:事务的粒度比较大,只能到方法级别

在我的不懈…嗯?百度之下,后面找到事务不回滚的原因有以下两点:
一.try…catch异常
Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚,如果一个方法抛出Exception或者Checked异常,Spring事务管理默认不进行回滚!
贫僧的坑就在这个地方,使用了try…catch子句捕获异常并throw了一个自定义异常,原来是因为我自定义的异常不是 RuntimeException,这种情况导致了事务未回滚,真的是大意出Bug,老脸一红。话不多说,上代码:

@Transactional(propagation = Propagation.MANDATORY)
public void updateXianYuInfo(People people) throws TransactionException{
	try{
	  peopleDao.updateByPrimaryKey(people);
	  peopleLogDao.updateByPrimaryKey(people);
	}catch(Exception e){
		Logger.error(e.getMessage());
		throw new TransactionException(e.getErrCode(),e.getMessage());
	}
}

TransactionException自定义异常如下:

public class TransactionExceptionextends extends Exception {
  // 自定义异常
}

上面代码中的声明式事务在出现异常的时候,事务是不会回滚的。在代码中我虽然捕获了异常,但是同时我也抛出了异常,为什么事务未回滚呢?猜测是异常类型不对,于是苦苦查询原因,找到了答案:

在默认配置中,Spring FrameWork 的事务框架代码只会将出现runtime, unchecked 异常的事务标记为回滚;也就是说事务中抛出的异常时RuntimeException或者是其子类,这样事务才会回滚(默认情况下Error也会导致事务回滚)。在默认配置的情况下,所有的 checked 异常都不会引起事务回滚。

Note:Unchecked Exception包括Error与RuntimeException.
RuntimeException的所有子类也都属于此类。另一类就是checked Exception。

二.Service类内部方法调用

大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。
但是…声明式事务是通过AOP动态代理实现的,这样会产生一个代理类来做事务管理,而目标类(service)本身是不能感知代理类的存在的。
最后总结,在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以事务是不会开启的。

那么问题来了,挖掘机学校哪家强…嗯?搞错了,重来,大家所关心的事务不回滚的解决方案有哪些呢

a. 如果是service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层要继续捕获这个异常并处理

b. 在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在我的项目的做法)

c.在注解@Transactional中添加 rollbackFor={TransactionException.class}

这是一个本猿真实的挖坑以及埋坑的经历,希望能帮到在坐的各位老铁,挥一挥衣袖,深藏功与名~~~

上一篇:Java常用类库面试题


下一篇:异常