由于工作需要需要把数据保存不同的数据库表,如果某一环节出现问题对应表中数据都全部回滚,结果测试过程发现方法内报
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.