资讯中心

Ibatis 调用存储过程返回 sys_refcursor:从配置到结果映射的完整实践

📅 2026/10/9 21:22:00
Ibatis 调用存储过程返回 sys_refcursor:从配置到结果映射的完整实践
1. 为什么 Ibatis 调 Oracle 存储过程总在游标上翻车Ibatis现在更多人叫它 MyBatis 的前身但老项目里 Ibatis 2.x 依然大量存在调用 Oracle 存储过程本身不算复杂真正让人头疼的是sys_refcursor这个 OUT 游标参数。它不像普通返回值那样直接给你一个对象而是通过 JDBC 的CallableStatement注册一个游标类型执行完再从指定位置把ResultSet取出来最后交给 Ibatis 的resultMap做列到属性的映射。问题就出在这条链路上parameterMap里 OUT 参数的jdbcType写错、resultMap的 column 和实体属性对不上、Java 端拿结果的 key 和 XML 里property不一致任何一个环节出问题你拿到的就是空列表或者直接抛SQLException。我见过太多人卡在「存储过程明明在 PL/SQL Developer 里跑得好好的一到 Java 就返回 null」这种场景。这篇就聚焦一件事在 Ibatis 里调用 Oracle 存储过程把sys_refcursor正确映射成 Java 实体列表。适合正在维护老 Ibatis 项目、需要对接 Oracle 存储过程的同学。下面从配置骨架到验证方式一步步来代码可以直接抄改。2. 前置准备TaoToken 接入与依赖确认在动手写 XML 之前先把两件事理清楚一是你的 Ibatis 版本和 Oracle JDBC 驱动版本二是如果你在调试过程中需要快速验证模型生成的 SQL 或存储过程逻辑可以用 TaoToken 的模型对话能力辅助排查。TaoToken 官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它提供统一的模型调用入口适合在写存储过程映射时让模型帮你检查参数类型是否匹配。API 地址是 https://taotoken.net/api 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。依赖方面Ibatis 2.x 需要ibatis-2.3.4.726.jar或相近版本Oracle 驱动用ojdbc6.jar或ojdbc8.jar。注意jdbcTypeORACLECURSOR这个类型是 Ibatis 内置支持的不需要你额外注册类型处理器但驱动版本太低会导致游标注册失败。如果你在排查过程中需要生成测试用的存储过程或构造模拟数据可以到模型对话页面 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 让模型帮你写一段 PL/SQL省去手写的时间。3. 可复制配置parameterMap 与 resultMap 骨架先看 Oracle 端的存储过程定义。假设我们有一个包DEAL_SEARCH_PKG里面有个过程GET_INFO_SEARCH接收两个 IN 参数返回一个sys_refcursorCREATE OR REPLACE PACKAGE DEAL_SEARCH_PKG AS PROCEDURE GET_INFO_SEARCH( login_id IN VARCHAR2, criteria_id_in IN INTEGER, deal_result OUT sys_refcursor ); END DEAL_SEARCH_PKG; / CREATE OR REPLACE PACKAGE BODY DEAL_SEARCH_PKG AS PROCEDURE GET_INFO_SEARCH( login_id IN VARCHAR2, criteria_id_in IN INTEGER, deal_result OUT sys_refcursor ) IS BEGIN OPEN deal_result FOR SELECT DYN_DATA_ID, DEAL_ID, FAC_ID FROM user_deal WHERE criteria_id criteria_id_in; END GET_INFO_SEARCH; END DEAL_SEARCH_PKG; /对应的 Java 实体类public class DynamicData { private Long dynDataId; private Long dealId; private Long facilityId; // getter / setter 省略 }接下来是 Ibatis 的 XML 映射文件Sql-deal.xml。这里的关键点有三个resultMap的 column 必须和游标 SELECT 出来的列名一致parameterMap里 OUT 参数的jdbcType必须是ORACLECURSORmode必须是OUT并且带上resultMap引用。resultMap iddynDealRM classcom.pojo.DynamicData result propertydynDataId columnDYN_DATA_ID / result propertydealId columnDEAL_ID / result propertyfacilityId columnFAC_ID / /resultMap parameterMap idsearchResultParameters classjava.util.Map parameter propertyloginId javaTypejava.lang.String jdbcTypeVARCHAR2 modeIN / parameter propertycriId javaTypejava.lang.Integer jdbcTypeINTEGER modeIN / parameter propertydealResult javaTypejava.sql.ResultSet jdbcTypeORACLECURSOR modeOUT resultMapdynDealRM / /parameterMap procedure idgetDealSearchInfo parameterMapsearchResultParameters {call DEAL_SEARCH_PKG.GET_INFO_SEARCH(?,?,?)} /procedure注意parameterMap的class是java.util.Map这意味着 Java 端传参时要用 Map 而不是实体对象。三个参数按顺序对应存储过程的三个形参OUT 参数在 Map 里先放一个占位值即可Ibatis 执行后会把它替换成结果集。4. Java 端调用与结果接收Java 端调用时把 IN 参数放进 MapOUT 参数随便放个 null 占位然后通过queryForList执行最后从返回的 Map 里按property名取出游标结果。public ListDynamicData getDealSearchInfo(String loginId, Integer criId) { MapString, Object param new HashMapString, Object(); param.put(loginId, loginId); param.put(criId, criId); param.put(dealResult, null); // OUT 占位 // 执行存储过程 getSqlMapClientTemplate().queryForList(getDealSearchInfo, param); // 从 Map 中取出游标映射后的结果 ListDynamicData list (ListDynamicData) param.get(dealResult); return list; }这里有个容易踩的坑queryForList的返回值本身不是结果集真正的结果在传入的paramMap 里key 就是parameterMap中 OUT 参数的property值。如果你写成List list queryForList(...)然后直接强转会得到一个空列表或者类型转换异常。如果你用的是SqlMapClient而不是 Spring 封装的SqlMapClientTemplate写法类似SqlMapClient client SqlMapClientBuilder.buildSqlMapClient(reader); MapString, Object param new HashMapString, Object(); param.put(loginId, user001); param.put(criId, 1001); param.put(dealResult, null); client.queryForList(getDealSearchInfo, param); ListDynamicData result (ListDynamicData) param.get(dealResult);实测下来只要resultMap的 column 和游标 SELECT 的列名大小写一致Oracle 默认返回大写列名映射就能正常完成。5. 验证请求与成功结果验证分两步先在数据库端确认存储过程本身能返回数据再在 Java 端确认映射结果。数据库端验证DECLARE v_cur SYS_REFCURSOR; v_id NUMBER; v_deal NUMBER; v_fac NUMBER; BEGIN DEAL_SEARCH_PKG.GET_INFO_SEARCH(user001, 1001, v_cur); LOOP FETCH v_cur INTO v_id, v_deal, v_fac; EXIT WHEN v_cur%NOTFOUND; DBMS_OUTPUT.PUT_LINE(v_id || , || v_deal || , || v_fac); END LOOP; CLOSE v_cur; END; /如果这段能打印出数据说明存储过程没问题。接下来在 Java 端加一行日志确认映射结果ListDynamicData list getDealSearchInfo(user001, 1001); System.out.println(size list.size()); for (DynamicData d : list) { System.out.println(d.getDynDataId() | d.getDealId() | d.getFacilityId()); }成功的话控制台会输出类似size 3 10001 | 20001 | 30001 10002 | 20002 | 30002 10003 | 20003 | 30003如果size 0先检查resultMap的 column 是否和游标列名完全一致如果抛ClassCastException检查 Java 端取结果的 key 是否和parameter的property一致。6. 本篇常见错误排查错误一jdbcTypeORACLECURSOR写成CURSOR或REFIbatis 只认ORACLECURSOR这个类型名写错会报Java type and jdbc type mismatch或者直接不注册游标。检查parameterMap里 OUT 参数的jdbcType。错误二resultMap的 column 用了小写Oracle 返回的列名默认是大写如果你在resultMap里写columndyn_data_id映射会失败属性值为 null。统一用大写。错误三Java 端从queryForList返回值取结果queryForList的返回值是空列表真正的结果在传入的 Map 参数里。这个坑我踩过当时排查了半天才发现取错了地方。错误四OUT 参数没有在 Map 里放占位如果 Map 里没有dealResult这个 keyIbatis 执行时会报参数数量不匹配。哪怕值是 null也要先 put 进去。错误五存储过程包名或过程名大小写不一致Oracle 对象名默认大写{call DEAL_SEARCH_PKG.GET_INFO_SEARCH(?,?,?)}里如果写成小写某些驱动会找不到过程。保持大写。如果你在排查过程中需要快速验证某段 SQL 或存储过程的逻辑可以用 TaoToken 的模型对话 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 让模型帮你分析报错信息。接入相关的 API Key 可以在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 获取文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。7. 长期维护与 Coding Plan 建议老 Ibatis 项目的存储过程调用一旦跑通就很少改动但维护阶段经常需要新增字段或调整游标查询。这时候resultMap和存储过程的 SELECT 列要同步改漏一个就出 null。建议在项目里维护一份存储过程列名和resultMap的对照表改的时候两边一起动。如果你经常需要处理这类老项目的编码和调试任务可以考虑用 TaoToken 的 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它在长上下文编码场景下比较省心适合需要反复对照 XML、Java、PL/SQL 三端代码的维护工作。控制台入口在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 可以管理你的调用额度。最后提醒一句sys_refcursor的映射核心就是「OUT 参数声明对、resultMap 列名对、Java 取结果位置对」这三对任何一对出问题都会让你拿到空结果。把这三处对齐剩下的就是体力活了。

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案