目录
[一 问题描述](#一 问题描述)
[1.1 描述](#1.1 描述)
[二 解决办法](#二 解决办法)
[2.1 添加事务级别](#2.1 添加事务级别)
[2.2 分析以及问题说明](#2.2 分析以及问题说明)
[2.3 优化方案](#2.3 优化方案)
[2.4 结论](#2.4 结论)
一 问题描述
1.1 描述
在一个方法中,先修改数据库,然后使用http请求远程dify服务,出现数据不一致的问题。如下图

2.请求方法后:本地修改数据库成功,dify调用失败;数据库中名称有xx-222改成了xx-333;

怎么办?数据不一致,dify服务没有修改成功,而本地数据库修改成功了,正常要同生共死,一起成功,一起失败的。
二 解决办法
2.1 添加事务级别
1.修改代码后:添加事务传播级别:@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public Result<P> updatePersonalWorkflow(PersonalWorkflowDTO personalWorkflowDto) {
// 1. 校验
String wkId = personalWorkflowDto.getId();
if (StringUtils.isBlank(wkId)) {
return ResponseResultUtil.error("工作流id参数有误或为空");
}
// 2. 获取dir的URL
String dirUrl = dirOperationApiUtils.getDirRequestUrl(); // 修改接口
dirUrl = dirUrl + "/" + personalWorkflowDto.getId();
// 3. 封装请求参数
String param = requestDirVisitParam(personalWorkflowDto);
// 4. 先操作本地数据库
PersonalWorkflowFO personalWorkflowFO = new PersonalWorkflowFO();
BeanUtils.copyProperties(personalWorkflowDto, personalWorkflowFO);
// 5. 查询一条旧数据
PersonalWorkflowFO oldPersonalWorkflowFO = personalWorkflowWrapper.getWorkFlowByWkId(personalWorkflowDto.getId());
int flag = 0;
try {
flag = personalWorkflowWrapper.updatePersonalWorkflow(personalWorkflowFO);
if (flag < 1) {
return ResponseResultUtil.error("编辑工作流接口,操作本地数据库失败");
}
String result = IfDdirService.requestDirApiServer(ResultEnum.SUCCESS.getCode(), dirUrl, HttpMethod.POST, "lazy", param);
LOGGER.info("调用safe编辑接口,返回结果:{}", result);
} catch (Exception e) {
// 说明dir调用失败,给本地数据库的修改进行复原
String msg = "编辑工作流接口调用编辑接口失败(工作流id:{})";
LOGGER.info(msg + "开始恢复本地数据库的数据", oldPersonalWorkflowFO.getId());
int rollbackFlag = personalWorkflowWrapper.updatePersonalWorkflow(oldPersonalWorkflowFO);
LOGGER.info(msg + "进行恢复本地数据库的数据,操作{}", oldPersonalWorkflowFO.getId(), rollbackFlag < 1 ? "失败,结果小于1" : "成功");
throw e;
}
String info = flag > 0 ? "success" : "failure";
return ResponseResultUtil.ok(info);
}
2.2 分析以及问题说明
以上代码的主题逻辑如下:
try {
// 数据库操作1:在事务中执行
flag = personalWorkflowWrapper.updatePersonalWorkflow(personalWorkflowFO);
// 远程调用:如果失败抛出异常
String result = IfDdirService.requestDirApiServer(...);
} catch (Exception e) {
// 手动恢复代码(有问题)
int rollbackFlag = personalWorkflowWrapper.updatePersonalWorkflow(oldPersonalWorkflowFO);
// 抛出异常:触发Spring事务回滚
throw e; // 这里会再次回滚!
}
以上原则上能实现触发本地数据库操作的回滚,但存在如下问题:
当远程调用失败时:
1)数据库更新操作已执行但未提交(事务还在进行中)
2)远程调用抛出异常
3)进入catch块 ,执行手动恢复
4)再次抛出异常,Spring检测到异常,触发事务回滚
5)最终结果:手动恢复的操作也被回滚了!
2.3 优化方案
1.将catch块中自动写的恢复补偿逻辑给去掉,让报异常,执行spring事务管理器所报的异常。
即:移除手动恢复代码,完全依赖Spring事务:
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public Result<P> updatePersonalWorkflow(PersonalWorkflowDTO personalWorkflowDto) {
// 1. 校验
String wkId = personalWorkflowDto.getId();
if (StringUtils.isBlank(wkId)) {
return ResponseResultUtil.error("工作流id参数有误或为空");
}
// 2. 获取dir的URL
String dirUrl = dirOperationApiUtils.getDirRequestUrl(); // 修改接口
dirUrl = dirUrl + "/" + personalWorkflowDto.getId();
// 3. 封装请求参数
String param = requestDirVisitParam(personalWorkflowDto);
// 4. 先操作本地数据库
PersonalWorkflowFO personalWorkflowFO = new PersonalWorkflowFO();
BeanUtils.copyProperties(personalWorkflowDto, personalWorkflowFO);
int flag = 0;
try {
flag = personalWorkflowWrapper.updatePersonalWorkflow(personalWorkflowFO);
if (flag < 1) {
return ResponseResultUtil.error("编辑工作流接口,操作本地数据库失败");
}
String result = IfDdirService.requestDirApiServer(ResultEnum.SUCCESS.getCode(), dirUrl, HttpMethod.POST, "lazy", param);
LOGGER.info("调用safe编辑接口,返回结果:{}", result);
} catch (Exception e) {
// 说明dir调用失败,给本地数据库的修改进行复原
String msg = "编辑工作流接口调用编辑接口失败(工作流id:{})";
LOGGER.info(msg + "开始恢复本地数据库的数据", oldPersonalWorkflowFO.getId());
throw e;
}
String info = flag > 0 ? "success" : "failure";
return ResponseResultUtil.ok(info);
}
2.4 结论
-
✅ REQUIRED传播级别下,远程调用失败会触发事务回滚
-
❌ 不需要手动在catch块中恢复数据
-
✅ Spring会在方法抛出异常时自动回滚整个事务
-
❌ 手动恢复+抛出异常会导致重复回滚,逻辑混乱
结论:完全信任Spring的事务管理机制,移除手动恢复代码即可。
当一个方法使用事务传播级别required,先执行本地数据库操作,然后调用dify服务,当dify远程调用失败时,会执行本地事务回滚。
原因是 触发异常,spring事务管理器捕捉到异常,执行本地数据库回滚,说白了就是dify失败报异常,捕捉到能触发本地数据库操作回滚。即当dify远程调用失败时,会执行本地事务回滚。
https://chat.deepseek.com/a/chat/s/56b88c28-b88c-4b55-ae6b-509539e3c257