最近在做的项目和之前有点不一样,以前我们可能经常做后端的一直从Controller写到最终的持久层,当然这样的写法我也写了很久,中间转用restful风格,但是这种形式依然没有转变,这次项目结构和之前有所调整,所以在写着写着到最后自测的时候发现存在事务不会滚的情况,当时我就纳闷了,起初自我检查怀疑是我事务配置的有问题,但是最终发现我的配置是没有什么问题的(但是说有问题也是存在的,那就是切面的问题),我在项目中配置的时候采用的是声明式事物,正常来说我们对这个事务的认知优缺点是存在的!优点:使用方便,一次配置就可以了;缺点:事务的粒度比较大,只能到方法级别!好了,下面说正题:我们在做事物配置的时候一直都会紧记的就是Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚;如果一个方法抛出Exception或者Checked异常,Spring事务管理默认不进行回滚!我的坑就在这个地方,之前都是遇到Exception就直接往外丢,然后直到Controller才会做try..catch操作,但是这次我们采用的dubbo的模式,所以很多地方就直接到Service就结束了,这里因为我们依然要留存日志等操作,所以为了打印清楚日志,很多方法我都加tyr catch,在catch中打印日志,但是这边情况来了,当这个方法异常时候日志是打印了,但是加的事务却没有回滚!
下面对集中代码结构留存以备查:
①下面的存在两个操作的方法,其中第二个操作异常了第一个操作的记录不会回滚,因为异常被捕获不会被spring拿到运行异常!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
Message insert(Mp entity){
try
{
mpdao.insert(entity);
Mp mptwo=
new
Mp();
mptwo.setCreateTime(
new
Date());
mptwo.setMpAlias(
"1234566"
);
mptwo.setMpAppId(
"147852"
);
mptwo.setMpAppSecret(
"14782239941"
);
mptwo.setMpMchPaykey(
"14721313"
);
mptwo.setMpMenu(
"sdfafadfsafasfadfsaf"
);
mptwo.setMpMsgType(
"14123"
);
//这里的数据数据库字段是char(2),所以插入操作会报错
mpdao.insert(mptwo);
return
Message.success();
}
catch
(Exception e){
logger.error(e.toString(),e);
return
Message.error();
}
}
|
②下面的方法第二个操作执行错误第一个会回滚,因为主动抛出了一个运行异常!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
Message insert(Mp entity){
try
{
mpdao.insert(entity);
Mp mptwo=
new
Mp();
mptwo.setCreateTime(
new
Date());
mptwo.setMpAlias(
"1234566"
);
mptwo.setMpAppId(
"147852"
);
mptwo.setMpAppSecret(
"14782239941"
);
mptwo.setMpMchPaykey(
"14721313"
);
mptwo.setMpMenu(
"sdfafadfsafasfadfsaf"
);
mptwo.setMpMsgType(
"14123"
);
//这里的数据数据库字段是char(2),所以插入操作会报错
mpdao.insert(mptwo);
return
Message.success();
}
catch
(Exception e){
logger.error(e.toString(),e);
throw
new
RuntimeException();
//主动抛出异常
return
Message.error();
}
}
|
③下面的第二个操作执行错误第一个也会回滚,因为我们手动做了事物处理!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
Message insert(Mp entity){
try
{
mpdao.insert(entity);
Mp mptwo=
new
Mp();
mptwo.setCreateTime(
new
Date());
mptwo.setMpAlias(
"1234566"
);
mptwo.setMpAppId(
"147852"
);
mptwo.setMpAppSecret(
"14782239941"
);
mptwo.setMpMchPaykey(
"14721313"
);
mptwo.setMpMenu(
"sdfafadfsafasfadfsaf"
);
mptwo.setMpMsgType(
"14123"
);
//这里的数据数据库字段是char(2),所以插入操作会报错
mpdao.insert(mptwo);
return
Message.success();
}
catch
(Exception e){
logger.error(e.toString(),e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
//手动事务处理
return
Message.error();
}
}
|
不知道你看到这里有没有明白我这篇文章的意图和想要表达的内容呢?下面我们来做一个总结:
①我们的声明式事物为什么不会滚呢??
a. 默认spring事务只在发生未被捕获的 runtimeexcetpion时才回滚;
b. spring aop 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeexception的异常,但可以通过配置来捕获特定的异常并回滚 !
②解决方案
a. 如果是service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理
b. 在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在我的项目的做法)
这是一个真实的挖坑以及埋坑的经历,在这里记录下来希望可以对朋友们给出帮助,最后感谢你的翻阅,如有疑问可以留言讨论,谢谢!
这样搞定
/** * 支付超时,改变订单状态 * @param map * @return */ @Override public int cancelOrder(Map map) { Integer count = 1; Integer orderId = Integer.parseInt(map.get("orderId").toString()); Integer activityId = Integer.parseInt(map.get("activityId").toString()); Integer signupCount = Integer.parseInt(map.get("signupCount").toString()); int one = orderMapper.cancelOrder(orderId); int two = orderMapper.updateActivityCurrentNumber(signupCount,activityId); /* int i = 1/0; System.err.println(i);*/ int three = orderMapper.deleteActivitySignUpRecords(orderId,0); if (one > 0 && two > 0 && three > 0) { return count; } else { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //手动事务处理 } return 0; }