可以先去看看springboot @transactional 动态代理实现
场景:
同一个类中,无事务A方法调用有事务方法B,基于业务接口不能直接抛错,需要返回指令对象。
无事务A循环多次调用有事务方法B,方法B抛错,不影响正常数据的持久层写入。方法B会涉及到几张表数据的更新
遇到的问题:B方法抛错,数据不回滚。如B方法中的判断boxSet表数据的逻辑没有报错,到判断boxOne逻辑出错,boxSet数据写进了数据库,没有回滚。
即:无事务方法调用有事务方法,事务失效
解决方案1:
将无事务A方法和有事务方法B不要放在同一个类中,就可以了。
原因:动态代理机制,一次操作,proxy不会重复代理一个对象两次,基于proxy的method.invoke..的方法,然后无事务方法A是没有加 @Transaction注解的,所以代理时也不会开启事务,B方法开启也是失效的
解决方案2:
获取被代理对象的proxy,代理对象去调用方法,代码如下:
启动类加上注解:
@EnableAspectJAutoProxy(exposeProxy = true)//动态代理方式事务
@EnableEurekaClient
@EnableFeignClients
//开启事务
@EnableTransactionManagement
@SpringBootApplication
@EnableAsync
@MapperScan(basePackages = { "com.hierway.vslm.dataaccess.mybatis.mapper" })
@EnableAspectJAutoProxy(exposeProxy = true)//动态代理方式事务
public class VslmApplication {
public static void main(String[] args) {
SpringApplication.run(VslmApplication.class, args);
}
}
动态代理调用逻辑 :
//解决抛错事务不回滚
CapaStatCacheComponent currentProxy = (CapaStatCacheComponent) AopContext.currentProxy();
setId = currentProxy.creatSetAndupdateByResAlloca(commandId, reqId,specId, specAllotSboxMap.getResAllocaList());
下面是解决了事务不回滚的代码:
A方法:
creatSetAndupdateBySpec
public List<SpecAllotSboxVo> creatSetAndupdateBySpec(List<SpecAllotSboxMap> specAllotSboxMaps) {
//第一次查询,初始化加载统计缓存信息
if (isFirst) {
selectToStart();
}
String methodStr="creatSetAndupdateBySpec";
if (CollectionUtils.isEmpty(specAllotSboxMaps)){
logger.error("入参为空,{}",methodStr);
throw new ApiException(ResultCode.PARAMS_ERROR,"参数错误");
}
List<SpecAllotSboxVo> specAllotSboxVoList =new ArrayList<>();
for (SpecAllotSboxMap specAllotSboxMap : specAllotSboxMaps) {
SpecAllotSboxVo specAllotSboxVo = new SpecAllotSboxVo();
String commandId = specAllotSboxMap.getCommandId();
String reqId = specAllotSboxMap.getReqId();
String specId = specAllotSboxMap.getSpecId();
String setId =null ;
try {
//解决抛错事务不回滚
CapaStatCacheComponent currentProxy = (CapaStatCacheComponent) AopContext.currentProxy();
setId = currentProxy.creatSetAndupdateByResAlloca(commandId, reqId,specId, specAllotSboxMap.getResAllocaList());
// setId = creatSetAndupdateByResAlloca(commandId, reqId,specId, specAllotSboxMap.getResAllocaList());
}catch (Exception e){
logger.error("指令{},创建set并分配资源失败,回滚:{},{}",commandId,e.getMessage(),methodStr);
//throw new ApiException(ResultCode.FAILURE);
specAllotSboxVo.setResultCode(ResultCode.FAILURE.getCode());
}
specAllotSboxVo.setCommandId(commandId);
specAllotSboxVo.setSetId(setId);
if (StringUtils.isEmpty(setId)){
specAllotSboxVo.setResultCode(ResultCode.FAILURE.getCode());
}else {
specAllotSboxVo.setResultCode(ResultCode.SUCCESS.getCode());
}
specAllotSboxVoList.add(specAllotSboxVo);
}
return specAllotSboxVoList;
}
B方法:
creatSetAndupdateByResAlloca
//创建set并分配产能
@Transactional
public String creatSetAndupdateByResAlloca(String commandId,String reqId, String specId, List<ResAlloca> resAllocaList) {
String methodStr="creatSetAndupdateByResAlloca";
if (StringUtils.isEmpty(reqId) || StringUtils.isEmpty(specId)){
logger.error("需求号和规格号不能为空,{}",methodStr);
throw new ApiException(ResultCode.PARAMS_ERROR,"参数错误");
}
//创建set
StreamBoxSet set =null ;
String setId = null;
/*SboxSet record = new SboxSet();
record.setReqId(reqId);
record.setSpecId(specId);
List<SboxSet> setList = sboxSetDao.select(record);
if (!CollectionUtils.isEmpty(setList)){
setId=setList.get(0).getSetId();
}else {*/
StreamBoxSet streamBoxSet = new StreamBoxSet();
streamBoxSet.setReqId(reqId);
streamBoxSet.setSpecId(specId);
try {
set = sboxSetService.addNewStreamBoxSet(streamBoxSet);
}catch (Exception e){
logger.error("调用创建set接口报错,{}",methodStr);
throw new ApiException(ResultCode.FAILURE,"创建set失败");
}
if (set==null || StringUtils.isEmpty(set.getSetId())){
logger.error("创建set失败,set为空,{}",methodStr);
throw new ApiException(ResultCode.FAILURE,"创建set失败");
}
setId = set.getSetId();
// }
for (ResAlloca resAlloca : resAllocaList) {
resAlloca.setSetId(setId);
}
//只支持一个规格下的产能分配 多个规格分配无法和set映射
Boolean isAlloct=false;
try {
isAlloct=checkUpdateByResAlloca(resAllocaList);
}catch (ApiException e){
logger.error("调用资源占用接口抛错,{}",methodStr);
throw new ApiException(ResultCode.FAILURE,"资源占用失败");
}
if (isAlloct==false){
logger.error("调用资源占用接口失败,{}",methodStr);
throw new ApiException(ResultCode.FAILURE,"资源占用失败");
}
//return null;
return setId;
}