news 2026/10/3 11:50:59

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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但驱动版本不匹配、mode=OUT的参数没在 Java 端预置占位、resultMap的 column 和游标里的列名对不上。

这篇就按「能跑通」的标准来写。场景很具体:Oracle 里有个包PKG_TEST,过程P_TEST接收两个入参,通过一个OUT游标返回多行数据,我们要在 MyBatis 里把它接成List<User>或者List<Map>。适合谁看?正在做 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驱动。jdbcType=CURSOR这个类型在ojdbc8及以上才稳定支持,老版本ojdbc6容易报无效的列类型: 1111。Maven 里确认一下:

<dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>21.9.0.0</version> </dependency>

MyBatis 本身用mybatis-spring-boot-starter即可,版本 2.x 以上对CALLABLE支持没问题。

2.3 关于 TaoToken 的接入位置

如果你在本地调试时想用统一的模型网关来辅助生成 Mapper XML、排查报错信息,可以走 TaoToken 的 API 入口。它的 Base URL 是https://taotoken.net/api,控制台在https://taotoken.net/console,API Key 在https://taotoken.net/api-keys生成。注意这里只是把它当作编码辅助工具的接入点,存储过程本身的执行还是走你的 Oracle 数据源,两者不冲突。模型对话入口在https://taotoken.net/model-chat,遇到ORA-报错想快速定位原因时挺顺手。

3. 可复制配置:Mapper XML、接口与游标参数声明

这一节是全文核心,直接给能粘贴的片段。分两种接收方式:Map接收和实体类接收,按需选。

3.1 用 Map 接收结果集(最快验证)

先定义resultMap,类型是java.util.HashMap:

<resultMap id="cursorMap" type="java.util.HashMap"> </resultMap> <select id="findDataByMap" statementType="CALLABLE" parameterType="java.util.Map"> <![CDATA[ call PKG_TEST.P_TEST( #{param1, jdbcType=VARCHAR, mode=IN}, #{param2, jdbcType=VARCHAR, mode=IN}, #{pCur, jdbcType=CURSOR, mode=OUT, resultMap=cursorMap} ) ]]> </select>

关键点三个:statementType="CALLABLE"必须写,否则 MyBatis 当成普通查询;mode=OUT的参数pCur在 Java 端要预置一个占位值;resultMap指向上面那个空 Map 映射,MyBatis 会把游标每行塞成一个 Map。

Mapper 接口:

List<Map<String, Object>> findDataByMap(Map<String, Object> params);

注意返回值这里可以直接声明成List<Map>,MyBatis 会把 OUT 游标的内容作为返回列表返回,比从入参 Map 里get更直观。

3.2 用实体类接收(生产推荐)

定义实体User,字段和游标列名对应:

public class User { private Long id; private String name; private Date createTime; // getter / setter 省略 }

resultMap显式映射列:

<resultMap id="userResultMap" type="com.example.entity.User"> <result column="ID" property="id"/> <result column="NAME" property="name"/> <result column="CREATE_TIME" property="createTime"/> </resultMap> <select id="findDataByUser" statementType="CALLABLE" parameterType="java.util.Map"> <![CDATA[ call PKG_TEST.P_TEST( #{param1, jdbcType=VARCHAR, mode=IN}, #{param2, jdbcType=VARCHAR, mode=IN}, #{pCur, jdbcType=CURSOR, mode=OUT, resultMap=userResultMap} ) ]]> </select>

Mapper 接口:

List<User> findDataByUser(Map<String, Object> params);

3.3 Java 端调用与参数预置

@Service public class UserService { @Autowired private UserMapper userMapper; public List<User> query(String param1, String param2) { Map<String, Object> params = new HashMap<>(); params.put("param1", param1); params.put("param2", param2); // OUT 游标参数必须预置占位,否则 MyBatis 报参数缺失 params.put("pCur", new ArrayList<User>()); List<User> list = userMapper.findDataByUser(params); System.out.println("返回行数: " + list.size()); return list; } }

params.put("pCur", new ArrayList<User>())这行是很多人漏掉的。MyBatis 对mode=OUT的参数要求调用前存在这个 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() { List<User> 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 日志级别调到 DEBUG:

logging: level: com.example.mapper: debug

日志里应该能看到==> Preparing: {call PKG_TEST.P_TEST(?, ?, ?)}以及==> Parameters:的输出。如果看到的是普通SELECT语句,说明statementType="CALLABLE"没生效,检查 XML 是否被正确加载。

4.4 成功结果的判断标准

一次成功的调用,应该满足:日志显示 CALLABLE 语句、参数三个都绑定、返回列表行数和数据库侧一致、实体字段值正确。三者对齐,链路就算通了。

5. 常见报错排查:401、游标类型与映射错位

这一节按真实报错来对,遇到哪个查哪个。

5.1无效的列类型: 1111

这是最经典的。1111 是 JDBC 里OTHER类型的编码,Oracle 游标就属于这类。报这个错通常是jdbcType没写CURSOR,或者驱动版本太老不认。检查 XML 里是不是写成了jdbcType=OTHER或者干脆没写。改成jdbcType=CURSOR,并升级ojdbc8。

5.2ORA-01000: 超出打开游标的最大数

游标没关闭导致的泄漏。MyBatis 在mode=OUT且resultMap正确时会自动消费并关闭游标,但如果resultMap配错、映射失败,游标可能挂在那里。排查方向:确认resultMap的 type 和 column 都对,别让映射中途抛异常。

5.3Parameter 'pCur' not found

Java 端没预置 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 容易出错。我的做法是把常用模式沉淀成模板:CALLABLE+mode=OUT+resultMap三件套固定下来,新过程只改包名、过程名和列映射。

如果你在写这些 XML 时想让模型帮你检查参数映射是否完整,可以走 TaoToken 的 Coding Plan 入口https://taotoken.net/coding-plan,把 XML 片段贴进去让它对照jdbcType和mode逐项核对。接入文档在https://taotoken.net/doc,里面有完整的参数说明。

最后留一个实用技巧:Oracle 游标返回的列名如果和实体字段差异大,别硬改实体,用resultMap的column做桥接最干净。另外,statementType="CALLABLE"和parameterType="java.util.Map"这两个属性是绑定的,别只写一个。存储过程返回结果集这条链路,本质就是「Oracle 开游标、MyBatis 接游标、resultMap 映射列」三步,每一步都显式声明,就不会有玄学问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 11:49:36

深入理解model.eval()与torch.no_grad():推理阶段显存与速度优化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 11:49:06

wifit3固件加载全解析:24个厂商固件blob的上传与字节校验

wifit3固件加载全解析&#xff1a;24个厂商固件blob的上传与字节校验 【免费下载链接】wifit3 Wifite but USB-only & cross-platform. 项目地址: https://gitcode.com/GitHub_Trending/wi/wifit3 wifit3 是一款跨平台、纯 USB 的 Wi-Fi 审计工具&#xff08;Wifite…

作者头像 李华