Spring之声明式事务管理

Spring声明式事务异常回滚机制

前言

Spring事务管理是根据异常来进行回滚操作;

Spring与Mybatis整合时,虽然在Service方法中并没有checked异常,但是如果数据库有异常发生,默认会进行事务回滚。

Spring如果不添加rollbackFor等属性,Spring碰到Unchecked Exceptions都会回滚,不仅仅是RuntimeException,也包括Error。

如果在事务方法中捕获异常并进行处理,一定要继续抛出异常并在Spring事务管理中进行rollbak-for配置。

概述

首先看一个Service层的方法,该方法调用两个DAO层的方法分别往数据库中的两张表中插入数据,实现一个业务逻辑。而且根据业务规则,这两个插入动作是要进行事务控制的原子操作,即要么一起成功,要么一起失败,否则会导致数据一致性问题。

1
2
3
4
5
6
@Override
public void SaveDispatchInfoService(GxluProjectInfo projInfo, GxluDispatchResultSchedule dispatchResultSchedule) throws Exception
{
projectInfoDAO.updateOrderInfoByOrderId(projInfo); //此句执行完后,事务已经被提交了
gxluDispatchResultScheduleDAO.saveInstance(dispatchResultSchedule);//若此句执行发生异常,已无法将上一句执行的结果进行回滚,产生数据一致性问题
}

说明:若在这个Service层的方法上不加任何事务注解,则事务的边界为DAO层的方法。即当程序执行完第一个DAO方法的调用后,事务已经被提交了。(可以通过pl/sql developer工具连接到数据库后,发现此时记录已经被插入到表中了来验证)。因此,第二句的DAO层的方法如果一旦执行错误,已经无法将上一句DAO层方法以及插入到数据库中的数据进行回滚,导致数据一致性问题。

Spring通过@Transactional注解实现声明式事务。一般事务的边界定义在Service层的方法中。

1
2
3
4
5
6
7
@Override
@Transactional
public void SaveDispatchInfoService(GxluProjectInfo projInfo, GxluDispatchResultSchedule dispatchResultSchedule) throws Exception
{
projectInfoDAO.updateOrderInfoByOrderId(projInfo);//此句执行完后,事务没有被提交。
gxluDispatchResultScheduleDAO.saveInstance(dispatchResultSchedule);//此句若执行失败,若抛出的异常为RuntimeExcepition类型或其子型的异常对象,则上句DAO层语句执行的结果会被回滚。否则,上句执行的结果会被提交。这样也会导致业务原子信被破坏,导致数据不一致的情况。
}//当程序执完整个Service方法,且上述两句都执行成功,则事务才被提交

说明:若如上述代码一样,只加了一个默认的@Transactional标注,而不加任何标注参数。则此时,当第一个DAO层方法执行完成后,事务没有被提交(可以通过pl/sql developer工具连接到数据库后,发现此时记录没有被插入到表中来验证)。当程序执完整个Service方法,且上述两句DAO层方法都执行成功,则事务才被提交(可以通过pl/sql developer工具连接到数据库后,发现此时两条记录都已经被插入到表中来验证)。

若第二句DAO层方法执行失败,Spring会根据异常类型来做不同的处理,即:若执行失败,若抛出的异常为RuntimeExcepition类型或其子型的异常对象,则上句DAO层语句执行的结果会被回滚。否则,上句执行的结果会被提交。这样也会导致业务原子性被破坏,导致数据不一致的情况。

为了保证在Service层的方法中,无论遇到任何类型的异常,都让事务进行回滚,而不是让之前已经执行成功的语句提交,则必须在@Transactional注解中增加如下参数rollbackFor:

1
2
3
4
5
6
7
@Override
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public void SaveDispatchInfoService(GxluProjectInfo projInfo, GxluDispatchResultSchedule dispatchResultSchedule) throws Exception
{
projectInfoDAO.updateOrderInfoByOrderId(projInfo);
gxluDispatchResultScheduleDAO.saveInstance(dispatchResultSchedule);
}

说明:由于Exception是所有异常类的超类,这样对于任何异常,Spring都会进行回滚,而不会将引发异常之前已经执行成功的提交掉。

对于那些我们在代码中主动抛出的异常,我们可以将rollbackFor参数指定为这些特定异常,即遇到该异常,就进行回滚操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=ArcpipeException.class)
public void insertRouteConditions(List<GxluProjectInfo> projInfos) throws ArcpipeException,JAXBException,RemoteException,Exception
{
//插入路由选择条件(起始、终止)到数据库
for (GxluProjectInfo projInfo : projInfos)
{
projectInfoDAO.insertRouteCondition(projInfo);
}

//将选择条件推送给综资Webservice
Body bodyOut=convertBean2dto(projInfos);
String configConditionXml = JaxbUtil.convertToXml(bodyOut);
String configConditionResultXml=configConditionItfService.submitConfigCondition2Purdo(configConditionXml);
Body bodyIn = JaxbUtil.converyToJavaBean(configConditionResultXml, Body.class);
if(!"1".equals(bodyIn.getConfigConditionResultInfo().getErrorCode()))
{
throw new ArcpipeException(); //若抛出该异常,则整个事务将被回滚
}
}

说明:上述Service方法作为一个原子业务逻辑,实现了两个操作,一个是调用DAO层方法往数据库里面插入数据,另一个是调用另一个系统的Webservice接口,将数据推送过去。若第一个DAO层方法执行成功,而第二个操作由于网络等原因导致调用Webservice接口失败,则程序会主动抛出ArcpipeException异常,而由于事务注解中已经设置了参数rollbackFor=ArcpipeException.class,因此该事务会被回滚掉。

或者我们在自定义异常的时候,让自定义的异常继承自RuntimeException,这样抛出的时候才会被Spring默认的事务处理。

秉持初心,继续向前。
显示 Gitment 评论