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

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

本文档总结配置导入(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

相关推荐
远洪20 小时前
excel 找出两列不同的数据
excel
pcplayer21 小时前
非常好用的 Excel 读写控件
excel·delphi·office
Navicat中国1 天前
使用 Navicat 导入向导导入 Excel 数据时,系统提示导入成功,表中也能看到数据,但行数统计显示为 0,这是什么原因?
数据库·excel·导入
穿着内裤的外星人1 天前
触控精灵远程读写Excel步骤配置
excel
是孑然呀1 天前
【小记】excel vlookup一对多(第二篇)
excel
开开心心就好1 天前
专为视障人士设计的免费辅助工具
windows·计算机视觉·计算机外设·excel·散列表·推荐算法·csdn开发云
transformer_WSZ1 天前
excel两列数据绘制折线图
excel·折线图
蒋胜山2 天前
Excel 练习题(5)
经验分享·excel
Data-Miner2 天前
数以轻舟聚焦Excel-Agent场景:当AI做表工具学会说人话
人工智能·excel
夏日清风有你2 天前
Excel 中绘制散点图(Scatter Plot)
excel