DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Spring 7.0.9 存储过程执行后取不到 OUT 参数,检查代理中的登记分支

Spring 7.0.9 的共享查询代理可能跳过 OUT/INOUT 参数登记。已确认代理对照在 7.0.8 正常,临时恢复完整基线后须验证输出及数据库副作用。

Spring 7.0.9 中,存储过程已经执行,随后读取 OUT 参数却报下面的异常,需要检查共享 EntityManager 的延迟 query 代理:

IllegalArgumentException: OUT/INOUT parameter not available: outParam

对应场景是没有活动事务、也没有线程绑定 EntityManager 的调用。后台任务、异步线程可能经过这条路径;网页请求有其他 EntityManager 绑定时,则可能表现不同。不能只在一个带事务的端点验证正常,就排除后台调用的回归。

问题位于参数登记,不是数据库没有输出。共享代理会为临时创建的 EntityManager 包装查询,先记下 OUT/INOUT 参数,在执行结束、关闭这个 EntityManager 前预取输出。7.0.9 的返回值适配将这个登记放进 else-if 链:如果 registerStoredProcedureParameter() 返回原 query 自身,前面的代理返回适配分支已经命中,后面的 OUT 登记便不再执行。

因此调用顺序看起来完全合法,内部记录却为空。查询执行后 EntityManager 被关闭,下一次取输出值时就找不到预取结果。不要因调用者抛异常而马上重新执行存储过程;第一次执行可能已经产生数据库副作用。

可以先保留原有调用序列作为回归测试,例如:

StoredProcedureQuery query = entityManager.createStoredProcedureQuery("MY_PROC");
query.registerStoredProcedureParameter("inParam", String.class, ParameterMode.IN);
query.registerStoredProcedureParameter("outParam", Long.class, ParameterMode.OUT);
query.setParameter("inParam", "input");
query.execute();
Object output = query.getOutputParameterValue("outParam");

这里的 entityManager 要与生产中一样来自共享代理;过程名、参数名和类型替换成测试数据库中的定义。若把它换成手动创建、始终未关闭的真实 EntityManager,可能避开缺陷,却不是原路径的验收。

已确认的最小代理对照在 7.0.8 正常、7.0.9 失败;它不使用数据库,只模拟登记方法返回 query 自身这一点。临时措施是恢复项目原来验收过的 7.0.8 Spring Framework 基线,并按现有 BOM/依赖管理统一相关模块,重新跑这个路径。不能只替换 spring-orm 一个 jar,也不能从 Boot 版本号推断所有 Framework 模块已经回到同一基线。

修补方向已经明确:OUT/INOUT 登记必须独立于返回值适配分支执行。但尚未确认正式 7.0.10 可用,不能把代码已修等同于可升级。给方法随手增加事务也会改变业务提交、锁与回滚语义,不应当作未经验证的快捷修复。

验收分两层:代理测试断言执行后仍能取得预期输出;数据库集成测试检查实际 OUT/INOUT 值以及过程副作用只发生一次。不要通过失败后自动重试制造“最终成功”的假象。若本来已经有活动事务、参数名确实登记错误,或 JDBC 在多结果处理上出错,则需要按各自路径排查,不由这一处 else-if 回归解释。