由于工作需要需要把数据保存不同的数据库表,如果某一环节出现问题对应表中数据都全部回滚,结果测试过程发现方法内报
ApplicationException(自定义异常继承了Exception) 异常时b,c表数据都回滚,但是唯有a表所仍然提交了。
相关事务定义也配置
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Transactional(value="sptWarehouseTxManager",
isolation = Isolation.REPEATABLE_READ,
propagation = Propagation.REQUIRED,
rollbackFor = Exception.class)
public @interface WarehouseTransactional{
}
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<!-- 监听器 -->
<bean id="warehouseStartupListener" class="com.autrade.spt.warehouse.listener.WarehouseStartupListener"/>
<!-- SptWarehouse 数据源 -->
<bean id="dataSource" parent="parentDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- MyBatis -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:resources/mybatis/warehouse/myBatisConfig.xml" />
<property name="mapperLocations" value="classpath:resources/mybatis/warehouse/mapper/*.xml"/>
</bean>
<bean id="warehouseSqlSession" class="org.mybatis.spring.SqlSessionTemplate"
autowire="byName">
<constructor-arg ref="sqlSessionFactory" />
</bean>
<!-- MyBatis -->
<!-- 配置事务管理对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
<qualifier value="sptWarehouseTxManager"/>
</bean>
<!-- 将所有具有@Transactional注解的Bean自动配置为声明式事务支持 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
</beans>
/*Zw申请实现 ServiceA
* @author krcis
* @date 2018年6月4日
* <p></p>
* <p>描述</p>
*/
public class ZwApplyService extends WhServiceBase implements IZwApplyService {
private static final Logger logger = LoggerFactory.getLogger(ZwApplyService.class);
@Autowired
private IZwApplyDao zwApplyDao;
@Autowired
private IWhReceiptService whReceiptService;
@Override
@WarehouseTransactional
public zwRespEntity zwApproved(zwApproveUpEntity upEntity) throws ApplicationException, DBException {
zwRespEntity resp = new zwRespEntity();
zwRecordQueryUpEntity whRecordUpEntity = new zwRecordQueryUpEntity();
TblwhReceiptMasterEntity whReceiptEntity = new TblwhReceiptMasterEntity();
TblzwApplyEntity zwApplyUpEntity = new TblzwApplyEntity();
whRecordUpEntity.setStatus(zwRecordStatus.P.toString());//仅查询未入本地仓单申请
PagesDownResultEntity<TblzwRecrodEntity> whRecordList = zwApplyDao.findzwRecordList(whRecordUpEntity);
while(null != whRecordList && whRecordList.getDataList().size()>0){
for(TblzwRecrodEntity whRecord :whRecordList.getDataList()){
whReceiptEntity = new TblwhReceiptMasterEntity();
whReceiptEntity.setBlockNumber(BigDecimal.ZERO);
BeanUtils.copyProperties(whRecord, whReceiptEntity);
//step1:转为系统内部仓单 表A
whReceiptService.saveWharehouse(whReceiptEntity);//事务”未回滚“
//step2:更新仓单申请明细状态为 ’已映射本地仓单‘ 表B
whRecord.setStatus(zwRecordStatus.D.toString());
zwApplyDao.updatezwRecord(whRecord);//事务回滚
whRecordUpEntity.setPageNo(whRecordUpEntity.getPageNo()+1);
whRecordList = zwApplyDao.findzwRecordList(whRecordUpEntity);
}
}
zwApplyUpEntity.setApprovedStatus(zwApplyStatus.D.toString());
//step3:更新仓单申请状态为已审核通过 表C
zwApplyDao.updatezwApply(zwApplyUpEntity);//事务回滚
//step4:调用远程审核通过
resp = RemotePostUtil.doPost("/trade/service/approve", upEntity, zwRespEntity.class);
logger.info("审核结果{}",JsonUtility.toJSONString(resp));
throw new ApplicationException("测试事务回流");
return resp;
}
}
/**
serviceB
**/
public class WhReceiptService extends WarehouseServiceBase implements IWhReceiptService {
private static final Logger logger = LoggerFactory.getLogger(WhReceiptService.class);
@Autowired
private IWhReceiptDao whReceiptDao;
@Override
@ServiceExceptionAop
@WarehouseTransactional
public void saveWharehouse(TblwhReceiptMasterEntity entity)
throws Exception {
String warehouseId = getNextKey(KeySequenceId.KEYSEQ_WAREHOUSEID);
entity.setWarehouseId(warehouseId);
//保存记录
whReceiptDao.insert(entity);
}
}
通过跟踪源码发现执行 serviceB.saveWharehouse时事务是正常开启,当执行到
throw new ApplicationException("测试事务回流"); 当前异常类型是符件条件的(定义的为Exception是事务回滚,见事务定义)事务同样是回滚的。
详见下图:
//校验事务回流异常规则:
//满足条件继续执行jdbc 事务回滚
//执行数据库事务回滚
通过上面调试结果可以知道的是事务其实是有回滚的,但是为什么其他两个B,C表中数据被回滚条,唯有A表数据仍然能插入了数据库中呢,这个问题让人百思不知其解。理论上代码上应该不会有问题,后来和另外一个同事讨论怀疑是数据库表的问题。
后来看了A表的引擎是MyISAM(之前A表被删除过,又被重建过),而B、C表则是InnoDB。
问题就是出现在这。 由于【MyISAM是非事务安全型的,不支持事务,而InnoDB是事务安全型的(支持事务处理等高级处理)】
网上看了别人也遇到过同样的问题,真的没想到这个问题,之前创建的表一直设置在innoDB。现在总算找到问题了,其实我所遇到的并不是事务没回滚而是表引擎导致(mysql)
问题总结:
1.如果事务不回滚可以确认一下【 rollbackFor 】配置的异常类型,默认为RuntimeException类型。可以通过配置设置异常类型,设置某种类型才触发事务回滚,同时也可以设置[noRollbackFor]设置默种异常不回滚
2.如果使用的是mysql 的一定要注同数据库表的引擎要设置成[InnoDB],默认为;MyISAM.