ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ibatis配置oracle存储过程返回cursor类型:从参数映射到结果集解析的完整实践

ibatis配置oracle存储过程返回cursor类型:从参数映射到结果集解析的完整实践 1. iBatis 调用 Oracle 存储过程返回 SYS_REFCURSOR 到底难在哪iBatis现在更多人叫它 MyBatis 的前身调用 Oracle 存储过程返回SYS_REFCURSOR是 Java 持久层里一个经典但容易翻车的场景。核心检索词就是ibatis 配置 oracle 存储过程返回 cursor 类型它指的是在 iBatis 的sqlMap里通过parameterMap声明一个jdbcTypeORACLECURSOR、javaTypejava.sql.ResultSet的 OUT 参数再配合resultMap把游标里的列映射成 Java 对象最终在 Java 代码里用List接住结果集。适合谁适合还在维护老系统、用 iBatis 2.x 做持久层、又必须对接 Oracle 存储过程的 Java 开发者。为什么它难因为这条链路涉及四个环节同时对齐存储过程里游标不能提前close、parameterMap里参数顺序必须和?占位符一一对应、jdbcType必须写成ORACLECURSOR而不是CURSOR、Java 端必须用HashMap接收 OUT 参数。任何一个环节错位报错信息都不会直接告诉你「是第几个参数错了」而是抛出PLS-00306或Ref 游标无效这种让人摸不着头脑的异常。我试过在项目里调一个带 5 个参数的存储过程光参数顺序就来回改了三遍才跑通。这篇内容会按「存储过程怎么写 → iBatis 怎么配 → Java 怎么调 → 报错怎么排」的顺序给出可以直接复制的sqlMap片段和最小验证步骤。你不需要先理解 iBatis 的全部机制跟着配置链路走一遍就能把游标结果集正确拿到手。下面先从最容易埋坑的存储过程本身讲起。2. 存储过程与 iBatis 前置准备SYS_REFCURSOR 声明和 sqlMap 骨架在动 iBatis 配置之前先把 Oracle 这边的存储过程写对。很多Ref 游标无效的根因不在 iBatis而在存储过程里手贱加了close outCursor。游标是「借」给调用方的存储过程负责open结果集由 Java 端消费完再释放过程内部绝对不能关。先看一个最小可用的存储过程返回单列游标create or replace procedure ww(outCursor out SYS_REFCURSOR) as begin open outCursor for select PAY_ORDER from st_bank_payinfo_temp_t; -- 注意这里不能 close outCursor end ww;如果你需要返回多列把select换成多字段即可比如select ONLYID, SEARCHNO from ...。存储过程编译通过后可以在 SQL 客户端里先单独验证一次var c refcursor; exec ww(:c); print c;能打印出结果集说明存储过程这端没问题问题就一定出在 iBatis 配置或 Java 调用上。这一步能帮你把排查范围砍掉一半。接下来是 iBatis 的sqlMap骨架。一个完整的调用链需要三块resultMap列到属性的映射、parameterMap入参和出参声明、procedure调用语句。三者通过id互相引用。resultMap的class指向你的 VO 全限定名column是游标结果集的列名property是 VO 的属性名大小写要和数据库列名严格对应Oracle 默认返回大写列名。parameterMap是重灾区。每个parameter的mode决定它是 IN 还是 OUTjdbcType决定 JDBC 驱动怎么绑定。游标参数必须写成jdbcTypeORACLECURSORjavaTypejava.sql.ResultSet并且带上resultMap指向前面定义的映射。参数在parameterMap里的书写顺序就是它们在?占位符里的顺序这一点后面会反复强调。procedure标签里的调用语句推荐写成{call ww(?)}这种纯占位符形式。网上很多文章写{call ww()}或? call ww()前者会漏掉 OUT 参数绑定后者在 iBatis 2.x 里经常触发PLS-00306。先把骨架搭对再往里填业务字段。3. 可复制配置parameterMap、resultMap 与 jdbcTypeCURSOR 的完整写法这一节给出可以直接粘贴进sqlMap的完整配置。假设你的 VO 叫generalBookingVO存储过程ww接收一个 IN 参数fltDispatchplace返回三个 OUT 参数searchNo、flag、msg外加一个游标result。先写resultMap把游标结果集的列映射到 VO 属性resultMap idcorp-map classcom.example.vo.generalBookingVO result columnONLYID propertyonlyid / result columnSEARCHNO propertysearchNo / /resultMap再写parameterMap注意游标参数放在最后且jdbcType用ORACLECURSORparameterMap classjava.util.HashMap idcurseHashMap parameter propertyfltDispatchplace jdbcTypeVARCHAR javaTypejava.lang.String modeIN / parameter propertysearchNo jdbcTypeVARCHAR javaTypejava.lang.String modeOUT / parameter propertyflag jdbcTypeVARCHAR javaTypejava.lang.String modeOUT / parameter propertymsg jdbcTypeVARCHAR javaTypejava.lang.String modeOUT / parameter propertyresult jdbcTypeORACLECURSOR javaTypejava.sql.ResultSet resultMapcorp-map modeOUT / /parameterMap最后是procedure调用语句procedure idgetPackage parameterMapcurseHashMap ![CDATA[{call ww(?)}]] /procedure这里有几个必须对齐的点。第一parameterMap里参数的顺序是fltDispatchplace → searchNo → flag → msg → result那么存储过程的形参顺序也必须是这个顺序{call ww(?)}里的?会按这个顺序依次绑定。第二jdbcTypeORACLECURSOR是 iBatis 对 Oracle 游标的专用类型名写成CURSOR或REF都会绑定失败。第三resultMapcorp-map必须和上面resultMap的id完全一致大小写敏感。如果你用的是SqlMapClient的 XML 配置文件还需要确认sqlMapConfig里已经加载了这个sqlMap文件sqlMapConfig sqlMap resourcecom/example/sqlmap/BookingSqlMap.xml / /sqlMapConfig配置写完后建议先用一个空HashMap跑一次确认没有 XML 解析错误。iBatis 在启动时会校验parameterMap和resultMap的引用关系如果resultMap的id写错启动阶段就会报resultMap not found这比运行时报错好排查得多。4. 验证请求Java 端用 HashMap 接收游标结果集并打印配置就绪后Java 端的调用方式决定了你能不能拿到游标。关键点必须用HashMap作为参数对象因为 OUT 参数是通过 key 回填到 map 里的用 VO 接收 OUT 参数在 iBatis 2.x 里支持很差。SqlMapClient client SqlMapClientBuilder.buildSqlMapClient(reader); HashMapString, Object mm new HashMapString, Object(); mm.put(fltDispatchplace, SHANGHAI); client.insert(PY_PAY_DETAIL_INFO_T.getPackage, mm); ListgeneralBookingVO list (ListgeneralBookingVO) mm.get(result); System.out.println(游标返回条数: (list null ? 0 : list.size())); for (generalBookingVO vo : list) { System.out.println(vo.getOnlyid() | vo.getSearchNo()); } System.out.println(searchNo mm.get(searchNo)); System.out.println(flag mm.get(flag)); System.out.println(msg mm.get(msg));注意这里用的是insert而不是queryForList。因为存储过程调用在 iBatis 里被当作更新操作处理insert会执行procedure并回填 OUT 参数。如果你用queryForList游标结果不会被回填到 mapmm.get(result)会是null。mm.get(result)返回的实际上是一个ListiBatis 已经帮你把ResultSet按resultMap转换成了 VO 列表。如果返回null先检查parameterMap里游标参数的property是不是叫resultJava 端取的 key 必须和它一致。一个最小验证流程先跑存储过程确认能出数据再跑 iBatis 配置确认启动无报错最后跑 Java 调用打印条数和字段。三步都通过说明整条链路打通。如果中间某步失败对照下一节的报错表定位。5. 本篇常见错排查PLS-00306、Ref 游标无效与 401 类报错对照调通过程中我踩过的坑基本集中在下面几类对照真实报错逐条排查效率最高。PLS-00306: 调用 P_HR_CUSTOMER_INFO 时参数个数或类型错误。这个报错几乎都是参数顺序或个数不匹配。检查parameterMap里parameter的数量和顺序是否和存储过程形参完全一致。特别注意如果存储过程第一个参数是 OUT 游标而你在parameterMap里把它放在最后就会报这个错。另外{call ww()}这种漏掉?的写法也会触发它。ORA-06550 / PL/SQL: Statement ignored。通常伴随PLS-00306出现是上一条的连带错误解决参数匹配后自动消失。Ref 游标无效 / retrieval of output parameters failed。根因是存储过程内部close outCursor了。游标一旦关闭Java 端再去读就是无效引用。把close语句删掉即可。另一个可能是jdbcType写成了CURSOR而不是ORACLECURSOR驱动无法正确绑定。resultMap not found。parameterMap里游标参数的resultMap属性值和resultMap的id不一致或者resultMap定义在parameterMap之后但没被正确加载。把resultMap放在parameterMap前面。mm.get(result) 返回 null。三种可能用了queryForList而不是insertparameterMap里游标参数的property名和 Java 端取的 key 不一致存储过程没有真正open游标。local proxy failed / 401 类报错。这类通常出现在你通过 HTTP 接口或网关调用后端服务时和 iBatis 本身无关属于鉴权或网络层问题。检查请求头里的 Key 是否正确、Base URL 是否可达。如果你在联调阶段用 API 网关转发存储过程调用确认网关没有把 OUT 参数截断。排查顺序建议先看 Oracle 端存储过程能否单独执行再看 iBatis 启动日志有无resultMap引用错误最后看 Java 端 map 里的 key 是否齐全。按这个顺序90% 的报错能在五分钟内定位。6. 语义一致 CTA把存储过程调用接进你的持久层工作流配置跑通之后下一步是把它接进日常开发流。如果你在本地调试存储过程调用需要频繁验证模型输出或接口返回可以用模型对话快速比对结果https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 配合接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 把请求链路固定下来避免每次手改参数。长期做 Java 持久层和 Agent 类编码的可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 把存储过程调用、参数映射、结果集解析这类重复劳动沉淀成可复用的配置模板。API Key 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 管理控制台入口是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。Claude Code 相关的接入配置参考 https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 官网总入口 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。最后留一个实用技巧把parameterMap和resultMap抽成独立的sqlMap片段文件按存储过程名命名比如BookingProcSqlMap.xml。这样下次新增存储过程时复制骨架改字段就行不用重新踩一遍参数顺序的坑。游标参数永远放最后jdbcType永远写ORACLECURSOR存储过程里永远不close游标——这三条记住基本不会再翻车。
返回列表