ARTICLE DETAIL

资讯详情

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

MyBatis 调用 Oracle 存储过程返回结果集:从参数映射到游标遍历的完整配置

MyBatis 调用 Oracle 存储过程返回结果集:从参数映射到游标遍历的完整配置 1. 为什么 MyBatis 调 Oracle 存储过程总拿不到结果集先说结论MyBatis 调 Oracle 存储过程返回结果集卡点几乎不在 SQL 本身而在「OUT 游标参数怎么声明」和「返回的游标怎么被 MyBatis 接住」。Oracle 的存储过程不像 MySQL 那样能直接SELECT返回一张表它必须通过OUT SYS_REFCURSOR把结果集「递」出来而 MyBatis 需要你显式告诉它这个参数是游标、用哪个 resultMap 去映射。我见过太多项目里存储过程在 PL/SQL Developer 里跑得好好的一进 Java 就报ORA-01000、无效的列类型或者干脆map.get(p_cur)拿到 null。根因通常是三类jdbcType写成了CURSOR但驱动版本不匹配、modeOUT的参数没在 Java 端预置占位、resultMap的 column 和游标里的列名对不上。这篇就按「能跑通」的标准来写。场景很具体Oracle 里有个包PKG_TEST过程P_TEST接收两个入参通过一个OUT游标返回多行数据我们要在 MyBatis 里把它接成ListUser或者ListMap。适合谁看正在做 Oracle 老系统对接、被存储过程返回值折磨的后端同学尤其是用 Spring Boot MyBatis 组合的。核心检索词先摆出来mybatis 调用 oracle 存储过程 返回结果集本质是「参数映射 游标遍历」两件事。下面从建包开始一步步给可复制的配置。2. 前置准备Oracle 包、游标类型与 TaoToken 接入环境2.1 先把 Oracle 侧的包和过程建好存储过程返回结果集Oracle 侧必须定义一个REF CURSOR类型。最规范的做法是放在包里而不是散在过程里。先建包头CREATE OR REPLACE PACKAGE PKG_TEST IS TYPE V_CUR IS REF CURSOR; PROCEDURE P_TEST ( PARAM1 IN VARCHAR2, PARAM2 IN VARCHAR2, P_CUR OUT V_CUR ); END PKG_TEST;再建包体过程里用OPEN P_CUR FOR SELECT ...把结果集挂到游标上CREATE OR REPLACE PACKAGE BODY PKG_TEST IS PROCEDURE P_TEST ( PARAM1 IN VARCHAR2, PARAM2 IN VARCHAR2, P_CUR OUT V_CUR ) IS V_PARAM1 VARCHAR2(8) : NULL; BEGIN IF PARAM1 IS NULL THEN V_PARAM1 : 00000000; ELSE V_PARAM1 : PARAM1; END IF; OPEN P_CUR FOR SELECT ID, NAME, CREATE_TIME FROM T_USER WHERE STATUS V_PARAM1 AND CREATE_TIME BETWEEN PARAM2 AND SYSDATE; END P_TEST; END PKG_TEST;这里有个细节OPEN P_CUR FOR后面的查询列名就是后面resultMap里column要对应的名字。别用SELECT *列顺序一变映射就乱显式列名最稳。2.2 依赖与驱动版本别踩坑MyBatis 处理 Oracle 游标依赖ojdbc驱动。jdbcTypeCURSOR这个类型在ojdbc8及以上才稳定支持老版本ojdbc6容易报无效的列类型: 1111。Maven 里确认一下dependency groupIdcom.oracle.database.jdbc/groupId artifactIdojdbc8/artifactId version21.9.0.0/version /dependencyMyBatis 本身用mybatis-spring-boot-starter即可版本 2.x 以上对CALLABLE支持没问题。2.3 关于 TaoToken 的接入位置如果你在本地调试时想用统一的模型网关来辅助生成 Mapper XML、排查报错信息可以走 TaoToken 的 API 入口。它的 Base URL 是https://taotoken.net/api控制台在https://taotoken.net/consoleAPI Key 在https://taotoken.net/api-keys生成。注意这里只是把它当作编码辅助工具的接入点存储过程本身的执行还是走你的 Oracle 数据源两者不冲突。模型对话入口在https://taotoken.net/model-chat遇到ORA-报错想快速定位原因时挺顺手。3. 可复制配置Mapper XML、接口与游标参数声明这一节是全文核心直接给能粘贴的片段。分两种接收方式Map接收和实体类接收按需选。3.1 用 Map 接收结果集最快验证先定义resultMap类型是java.util.HashMapresultMap idcursorMap typejava.util.HashMap /resultMap select idfindDataByMap statementTypeCALLABLE parameterTypejava.util.Map ![CDATA[ call PKG_TEST.P_TEST( #{param1, jdbcTypeVARCHAR, modeIN}, #{param2, jdbcTypeVARCHAR, modeIN}, #{pCur, jdbcTypeCURSOR, modeOUT, resultMapcursorMap} ) ]] /select关键点三个statementTypeCALLABLE必须写否则 MyBatis 当成普通查询modeOUT的参数pCur在 Java 端要预置一个占位值resultMap指向上面那个空 Map 映射MyBatis 会把游标每行塞成一个 Map。Mapper 接口ListMapString, Object findDataByMap(MapString, Object params);注意返回值这里可以直接声明成ListMapMyBatis 会把 OUT 游标的内容作为返回列表返回比从入参 Map 里get更直观。3.2 用实体类接收生产推荐定义实体User字段和游标列名对应public class User { private Long id; private String name; private Date createTime; // getter / setter 省略 }resultMap显式映射列resultMap iduserResultMap typecom.example.entity.User result columnID propertyid/ result columnNAME propertyname/ result columnCREATE_TIME propertycreateTime/ /resultMap select idfindDataByUser statementTypeCALLABLE parameterTypejava.util.Map ![CDATA[ call PKG_TEST.P_TEST( #{param1, jdbcTypeVARCHAR, modeIN}, #{param2, jdbcTypeVARCHAR, modeIN}, #{pCur, jdbcTypeCURSOR, modeOUT, resultMapuserResultMap} ) ]] /selectMapper 接口ListUser findDataByUser(MapString, Object params);3.3 Java 端调用与参数预置Service public class UserService { Autowired private UserMapper userMapper; public ListUser query(String param1, String param2) { MapString, Object params new HashMap(); params.put(param1, param1); params.put(param2, param2); // OUT 游标参数必须预置占位否则 MyBatis 报参数缺失 params.put(pCur, new ArrayListUser()); ListUser list userMapper.findDataByUser(params); System.out.println(返回行数: list.size()); return list; } }params.put(pCur, new ArrayListUser())这行是很多人漏掉的。MyBatis 对modeOUT的参数要求调用前存在这个 key值本身会被覆盖但 key 不能少。3.4 多数据源场景的注意点如果项目里用了DS之类的多数据源注解确保 Mapper 方法上标注的数据源和 Oracle 库一致。存储过程调用对数据源切换敏感切错了会报ORA-00942: 表或视图不存在其实是连到了别的库。4. 验证请求一次本地调用跑通多行返回配置写完得验证。我一般分三步走从数据库到 Java 逐层确认。4.1 先在数据库侧确认过程能返回数据在 SQL 客户端里直接跑一段匿名块确认游标有内容DECLARE V_CUR PKG_TEST.V_CUR; V_ID NUMBER; V_NAME VARCHAR2(50); BEGIN PKG_TEST.P_TEST(00000000, 2024-01-01, V_CUR); LOOP FETCH V_CUR INTO V_ID, V_NAME; EXIT WHEN V_CUR%NOTFOUND; DBMS_OUTPUT.PUT_LINE(V_ID || - || V_NAME); END LOOP; CLOSE V_CUR; END;如果这里都取不到行问题在 SQL 条件或数据本身跟 MyBatis 无关先别往下查。4.2 Java 单元测试验证映射写个简单的测试方法SpringBootTest public class UserServiceTest { Autowired private UserService userService; Test public void testQuery() { ListUser list userService.query(00000000, 2024-01-01); System.out.println(size list.size()); list.forEach(u - System.out.println(u.getId() / u.getName())); } }跑起来后控制台应该打印出实际行数和每行内容。如果size 0但数据库侧有数据八成是resultMap的 column 大小写或列名对不上。4.3 观察日志确认 CALLABLE 生效把 MyBatis 日志级别调到 DEBUGlogging: level: com.example.mapper: debug日志里应该能看到 Preparing: {call PKG_TEST.P_TEST(?, ?, ?)}以及 Parameters:的输出。如果看到的是普通SELECT语句说明statementTypeCALLABLE没生效检查 XML 是否被正确加载。4.4 成功结果的判断标准一次成功的调用应该满足日志显示 CALLABLE 语句、参数三个都绑定、返回列表行数和数据库侧一致、实体字段值正确。三者对齐链路就算通了。5. 常见报错排查401、游标类型与映射错位这一节按真实报错来对遇到哪个查哪个。5.1无效的列类型: 1111这是最经典的。1111 是 JDBC 里OTHER类型的编码Oracle 游标就属于这类。报这个错通常是jdbcType没写CURSOR或者驱动版本太老不认。检查 XML 里是不是写成了jdbcTypeOTHER或者干脆没写。改成jdbcTypeCURSOR并升级ojdbc8。5.2ORA-01000: 超出打开游标的最大数游标没关闭导致的泄漏。MyBatis 在modeOUT且resultMap正确时会自动消费并关闭游标但如果resultMap配错、映射失败游标可能挂在那里。排查方向确认resultMap的 type 和 column 都对别让映射中途抛异常。5.3Parameter pCur not foundJava 端没预置 OUT 参数。回到 3.3 节params.put(pCur, ...)必须有。注意 key 名要和 XML 里#{pCur}完全一致大小写敏感。5.4local proxy failed/ 连接类报错如果日志里出现local proxy failed或连接超时先确认数据库连接串、监听端口、服务名是否正确。这类和存储过程无关是网络或数据源配置问题。用 TaoToken 的模型对话入口贴报错让它帮你分析连接串格式也行入口在https://taotoken.net/model-chat。5.5reading choices类解析异常如果你在调用外部模型接口辅助排查时遇到reading choices之类的 JSON 解析错误通常是返回体结构和预期不符。这类问题看原始响应体最快别猜。5.6 映射后字段全为 null游标有数据但实体字段全是 null。九成是resultMap的column和游标列名不一致。Oracle 默认列名大写resultMap里写ID还是id取决于你的查询。最稳的办法是显式SELECT ID AS id加双引号或者resultMap里统一用大写。5.7 OAuth / 鉴权类报错如果接入辅助工具时遇到 OAuth 相关报错检查 API Key 是否有效、是否过期。API Key 在https://taotoken.net/api-keys管理重新生成一个再试。5.8 三件套对照表无论用哪种方式接入配置三件套要齐全配置项值说明Base URLhttps://taotoken.net/api接口根地址API Key控制台生成鉴权凭证Model ID按需选择模型标识存储过程这边则是数据源 URL、用户名密码、驱动类名三件套缺一不可。6. 继续深入把存储过程调用接进你的编码工作流跑通一次调用只是开始。真实项目里Oracle 存储过程往往有几十个参数各异手写 Mapper XML 容易出错。我的做法是把常用模式沉淀成模板CALLABLEmodeOUTresultMap三件套固定下来新过程只改包名、过程名和列映射。如果你在写这些 XML 时想让模型帮你检查参数映射是否完整可以走 TaoToken 的 Coding Plan 入口https://taotoken.net/coding-plan把 XML 片段贴进去让它对照jdbcType和mode逐项核对。接入文档在https://taotoken.net/doc里面有完整的参数说明。最后留一个实用技巧Oracle 游标返回的列名如果和实体字段差异大别硬改实体用resultMap的column做桥接最干净。另外statementTypeCALLABLE和parameterTypejava.util.Map这两个属性是绑定的别只写一个。存储过程返回结果集这条链路本质就是「Oracle 开游标、MyBatis 接游标、resultMap 映射列」三步每一步都显式声明就不会有玄学问题。
返回列表