spring事务传播级别的实操案例2

目录

[一 问题描述](#一 问题描述)

[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

相关推荐
科技小花4 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸4 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain4 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希5 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神5 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员5 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java5 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿6 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴6 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU6 小时前
三大范式和E-R图
数据库