配置导入事务问题与修复总结

配置导入事务问题与修复总结

本文档总结配置导入(Excel 模板导入)过程中出现的 UnexpectedRollbackException 与前端错误信息不对的问题、根因及修复方案。


一、问题现象

  1. 后端日志org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
  2. 前端展示:页面提示「配置导入失败:Transaction rolled back because it has been marked as rollback-only」,而不是真实的业务错误(如「数据库表不存在:xxx」)
  3. 调试现象 :Sheet1 的 for 循环里已用 try-catch 把异常包进 result,并在 78-79 行正常 return result,但 Controller 仍进入异常捕获分支(110-111 行)

二、根因分析

2.1 事务与"异常即标记回滚"

  • ConfigImportServiceImpl.importFromExcel(InputStream) 使用 @Transactional(rollbackFor = Exception.class),整段导入在一个事务中执行。
  • Spring 行为 :只要在事务内任意时刻 抛出了符合 rollbackFor 的异常,当前事务就会被标记为 rollback-only
    标记发生在异常抛出的瞬间,与后续是否在业务代码里 catch 无关。

2.2 Sheet1 场景下的实际流程

  1. Sheet1 的 for 循环中调用 factTableSchemaService.importTableFromDatabase(...)
  2. 若某张表不存在,FactTableSchemaServiceImpl 抛出 IllegalArgumentException("数据库表不存在:" + tableName)
  3. 此时 :当前事务(即 importFromExcel 所在事务)被标记为 rollback-only。
  4. 异常随后在 for 循环的 catch 中被捕获,仅执行 result.addError(...),未 rethrow。
  5. 代码继续执行,到 78-79 行 if (!result.isSuccess() && !result.getErrors().isEmpty()) { return result; }正常 return result
  6. 方法正常返回后,Spring 事务拦截器执行 commit
  7. 发现事务已是 rollback-only,不允许提交,于是抛出 UnexpectedRollbackException
  8. 该异常冒泡到 Controller,被 110-111 行 catch (Exception e) 捕获,前端看到的是 commit 失败的消息,而非 result 中已写好的「表xxx导入失败: 数据库表不存在:xxx」。

2.3 两种失败路径对比

场景 执行路径 结果
仅 Sheet1 出错 for 内 catch → 78-79 return result → commit 时抛 UnexpectedRollbackException → Controller 进 catch 前端看到 rollback 提示,result 中正确错误信息未展示
Sheet2/3/5 或字段配置出错 后续某步抛异常 → 外层 catch 后 rethrow → Controller 进 catch 前端看到真实异常信息(修复 rethrow 后)

三、修复方案

3.1 修复一:Sheet1 表导入使用独立事务(REQUIRES_NEW)

目的:让 Sheet1 中单表导入失败时,只回滚该次调用的事务,不污染外层导入事务。

修改FactTableSchemaServiceImpl.importTableFromDatabase

  • 原:@Transactional(rollbackFor = Exception.class)
  • 现:@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)

效果

  • 每次调用 importTableFromDatabase 都会挂起外层事务并开启一个新事务。
  • 某张表导入失败抛异常时,仅该新事务被标记 rollback-only 并回滚。
  • 异常在 Sheet1 的 for 中被 catch 并写入 result外层 importFromExcel 事务未被标记
  • 78-79 行 return result 后,外层事务正常 commit,Controller 收到带错误信息的 result,走 success(result),前端可正确展示 result 中的错误列表。

3.2 修复二:外层 catch 不再吞异常(Rethrow)

目的 :当异常来自 Sheet2/3/5 或字段配置等未内部 catch 的步骤时,将真实异常抛出,避免只看到 UnexpectedRollbackException。

修改ConfigImportServiceImpl.importFromExcel(InputStream) 最外层 catch

  • 原:catch (Exception e) { ... result.addError(...); result.setSuccess(false); } 后直接 return result
  • 现:在 catch 末尾增加 throw new RuntimeException(e.getMessage() != null ? e.getMessage() : "配置导入失败", e);

效果

  • 后续 Sheet 或字段配置中一旦抛异常,会直接抛出,事务按预期回滚。
  • Controller 的 catch 收到的是包装后的 RuntimeException,getMessage() 为原始业务错误信息,前端可显示「配置导入失败:数据库表不存在:xxx」等真实原因。

四、涉及文件与位置

文件 修改内容
backend/.../FactTableSchemaServiceImpl.java importTableFromDatabase 增加 propagation = Propagation.REQUIRES_NEW,并增加 Propagation 的 import
backend/.../ConfigImportServiceImpl.java 最外层 catch 中在写入 result 后 throw new RuntimeException(..., e)

五、小结

  • 问题本质:事务内抛出的异常会使事务被标记 rollback-only;若异常被业务 catch 且未 rethrow,方法仍会"正常返回",但 commit 时因 rollback-only 再抛 UnexpectedRollbackException,前端只能看到后者。
  • Sheet1 场景 :通过 importTableFromDatabase 使用 REQUIRES_NEW,把单表导入放到独立事务中,表导入失败只回滚该次调用,外层可正常 return result 并 commit,前端拿到 result 中的错误信息。
  • 后续 Sheet 场景:通过外层 catch 后 rethrow,保证真实异常传到 Controller,前端显示真实错误信息。

相关功能说明见:docs/导入模板/配置导入模板说明.md

相关推荐
程序猿乐锅9 小时前
【苍穹外卖 Day12| Excel 入门】
excel
用户2986985301415 小时前
Python 数据处理:XML 与 Excel 互转的实用指南
后端·python·excel
xiaopai9451 天前
采购系统和Excel表格区别:钢结构项目物资管理的真实较量
excel·建米软件·采购系统
协享科技1 天前
2026 年 AI 表格工具怎么选:ChatGPT、原生 Copilot/Gemini 与公式助手
人工智能·chatgpt·excel·copilot
用户298698530142 天前
前端实战:在 React 中使用 JavaScript 一键导出 Excel 图表与形状为图片
javascript·react.js·excel
Q一件事2 天前
arcpy实现分区统计到一张excel中
excel
AI刀刀2 天前
deepseek 内容粘贴后符号丢失怎么办?AI 导出鸭实测解决排版乱码问题
android·人工智能·excel·ai导出鸭
圣光SG3 天前
Excel 函数大全
算法·excel
小小的木头人3 天前
Python 批量解析 Excel 经纬度,调用高德地图 API 获取中文地址
开发语言·python·excel
观远数据3 天前
从离线开发到实时同步:DataFlow如何支撑企业级数据治理闭环
java·windows·microsoft·excel