在使用Jpa的@Version的时候偶然发现了一个Spring事务的坑,花了一个下午的时间才成功解决,记录一下。
@Version当修改或删除操作的数据版本不一致的时候会抛出一个异常:
ObjectOptimisticLockingFailureException
Service用以下代码捕获了这个异常,消化并抛出了一个新的自定义异常:
try {
//调用持久化方法抛出ObjectOptimisticLockingFailureException异常
} catch (ObjectOptimisticLockingFailureException e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage());
}
//捕获处理后抛出一个新的自定义异常
throw new SkeletonBaseException("数据版本错误");
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error(e.getMessage());
}
throw SkeletonBaseException.getException(e, e.getMessage());
}
但是在Controller的最终异常处理中
try {
//调用上面的方法
} catch (Exception e) {
e.printStackTrace();
if (logger.isErrorEnabled()) {
logger.error(e.getMessage());
}
responseRange.setException(e);
}
return responseRange;
却得到了一个完全不一样的异常信息:
Transaction rolled back because it has been marked as rollback-only
这个异常信息是说事务已经回滚,因为它被标记成了回滚。
这里就很奇怪了。
按照我的想法,这个异常信息应该是Service抛出的自定义异常数据版本错误才对啊。
想了一下这就说明在Service返回Controller的过程中还发生了一个新的异常,而把我的异常顶掉了。
为什么会发生这种事情?
在网上查了许多大神的资料,Debugger了半天Spring的源码。
最终总结出了一个答案
Spring的事务切面认为Service的方法没有抛出异常,在Service结束后,打算正常Commit提交事务!但是这个事务已经被标记成了rollback-only状态,所以提交失败,抛出上面出乎意料的异常信息!
异常被顶掉的原因知道了。
但是我们Service明明最终抛出了一个自定义异常啊!它跑到哪里去了!
为什么事务切面认为没有抛出异常呢?!
这就是坑之所在了!
我的自定义异常继承了Exception而不是RuntimeException
因为知道Spring的事务默认回滚是发生RuntimeException所以特地配置了rollbackFor = Exception.class
按理来说不应该Commit而是正常rollback。
不过问题范围缩小了,又针对性的搜索了好一阵儿。
原因:
在try{}catch(){}
这样的代码块中,最后必须抛出一个RuntimeException,Spring事务切面才会认为你的方法有异常出现!即使配置了rollbackFor = Exception.class
也不管用!
解决方法:把自定义异常改成继承RuntimeException就OK了!