ARTICLE DETAIL

资讯详情

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

SSM-Mybatis调用Oracle存储过程返回结果集(游标):从配置到验证的完整实践

SSM-Mybatis调用Oracle存储过程返回结果集(游标):从配置到验证的完整实践 1. 为什么 SSM 调 Oracle 游标存储过程总在返回结果集这一步翻车如果你正在做 SSMSpring SpringMVC Mybatis项目数据库是 Oracle业务里又要求把一段复杂查询逻辑封装进存储过程、通过游标把结果集吐回 Java 层那你大概率会遇到一个很别扭的现象存储过程在 PL/SQL Developer 里跑得好好的一放到 Mybatis 里就报错要么是参数类型对不上要么是结果集拿不到要么干脆抛一个invalid column type或者ORA-01000之类的异常。这个问题的本质是 Mybatis 对 Oracle 存储过程游标出参的处理方式和 MySQL 完全不一样。MySQL 的存储过程返回结果集相对随意直接select就能映射而 Oracle 必须显式声明一个ref cursor类型通过out参数把游标传出来Mybatis 侧还要用statementTypeCALLABLE、jdbcTypeCURSOR、javaTypeResultSet加上resultMap四件套配合缺一个都跑不通。我见过太多同学卡在parameterType上——有人写int有人写实体类结果全报错。实际上调用带游标出参的 Oracle 存储过程时parameterType必须是java.util.Map因为入参和出参要放在同一个 Map 里传递出参的游标结果会被 Mybatis 回填到这个 Map 的对应 key 上你再从 Map 里取出来强转成 List。这篇内容就围绕「SSM-Mybatis 调用 Oracle 存储过程返回结果集游标」这条完整链路展开从 Oracle 建包、建过程到 Mapper XML 的statementTypeCALLABLE配置、jdbcTypeCURSOR出参声明、resultMap字段映射再到 Service 层调用和 Controller 验证每一步都给可复制的代码。适合正在做 SSM 老项目维护、或者需要对接 Oracle 存储过程的 Java 后端开发者。跟着走一遍游标结果集映射这块基本就能一次跑通。2. TaoToken 前置准备把模型对话和编码辅助接进来辅助排障在正式写代码之前先说一个能明显提升排障效率的前置动作。调 Oracle 游标存储过程这类问题报错信息往往很隐晦比如ORA-06550、PLS-00306、invalid column type: 1111光看堆栈很难定位到底是 SQL 写错了、参数类型错了还是 Mybatis 映射错了。这时候如果有一个能理解代码上下文、又能帮你解释 Oracle 报错的模型对话入口会省很多时间。TaoToken 提供的就是这样一个统一入口它把模型对话、编码辅助、API 调用这几件事整合在一起。你可以通过官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 了解整体能力API 接入地址是 https://taotoken.net/api这个地址不加 UTM 参数直接用于程序调用。具体到本篇场景我建议你先做两件事。第一打开模型对话入口把 Oracle 的建包建过程脚本贴进去让它帮你检查ref cursor类型声明和open ... for语法有没有问题。第二把 Mybatis 的 Mapper XML 贴进去重点确认statementType、jdbcType、mode、resultMap这几个属性是否齐全。很多时候报错就是因为少写了一个modeOUT或者jdbcType写成了OTHER。如果你打算长期做这类 SSM Oracle 的维护工作可以考虑 Coding Plan它更适合持续性的编码和 Agent 场景把常见的存储过程调用模板沉淀下来下次直接复用。需要拿 Key 的话走 API Keys 页面接入细节看接入文档。这里要提醒一句TaoToken 是模型调用和编码辅助的入口不是数据库中间件也不替代你的 IDE 和 Oracle 客户端它的价值在于帮你快速定位报错原因、生成可复制的配置片段。前置准备做完下面进入正题。整个链路我按「Oracle 侧 → Mybatis 侧 → Service 侧 → Controller 侧 → 验证」的顺序来写你可以直接照着敲。3. 可复制配置Oracle 建包建过程 Mapper XML 的 CALLABLE 与 CURSOR 出参这一节是整篇的核心配置写对了后面基本就顺了。先看 Oracle 侧。3.1 创建包声明游标类型Oracle 里用游标作为out出参时必须先在一个包里声明ref cursor类型因为存储过程的参数类型不能直接用匿名游标。这一步很多人会漏掉直接在建过程时写out sys_refcursor虽然sys_refcursor也能用但为了类型可控建议自定义。-- 创建一个包用于声明游标类型 create or replace package types as type empListCursor is ref cursor; end types;这个包只做类型声明没有实现体所以不需要package body。3.2 创建带游标出参的存储过程CREATE OR REPLACE PROCEDURE QUERYEMPSBYDEPTNO( pdeptno in Integer, empList out types.empListCursor ) is BEGIN if pdeptno 0 then open empList for select * from emp; else open empList for select * from emp where deptno pdeptno; end if; END QUERYEMPSBYDEPTNO;这里pdeptno是in入参empList是out游标出参。逻辑很简单传 0 查全部否则按部门编号过滤。建完之后你可以在 PL/SQL Developer 里先测一下确认游标能正常打开。3.3 Mapper XML 配置重点这是最容易出错的地方我把关键属性逐个标出来。?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.casic.dao.EmpMapper resultMap idresultMap3 typecom.casic.model.Emp result propertyempno columnempno/ result propertyename columnename/ result propertyjob columnjob/ result propertymgr columnmgr/ result propertyhiredate columnhiredate/ result propertysal columnsal/ result propertycomm columncomm/ result propertydeptno columndeptno/ /resultMap !-- statementTypeCALLABLE 表明调用的是存储过程 -- !-- parameterType 必须是 java.util.Map入参出参都放这个 Map -- select idqueryEmpByDeptno statementTypeCALLABLE parameterTypejava.util.Map {call QUERYEMPSBYDEPTNO( #{pdeptno, modeIN, jdbcTypeINTEGER}, #{result, modeOUT, jdbcTypeCURSOR, javaTypeResultSet, resultMapresultMap3} )} /select /mapper几个必须注意的点我用引用块强调一下。注意parameterType只能是java.util.Map。我试过写int或者实体类全部报错这点和 MySQL 调存储过程不一样。入参pdeptno和出参result都是这个 Map 的 key。注意出参必须写modeOUT、jdbcTypeCURSOR、javaTypeResultSet并且用resultMap指定映射关系。少任何一个要么报invalid column type要么结果集为空。3.4 Mapper 接口package com.casic.dao; import java.util.List; import java.util.Map; import com.casic.model.Emp; public interface EmpMapper { /** * 根据部门编号加载员工信息列表 * 注意返回的不是 List而是通过 Map 出参回填 */ ListEmp queryEmpByDeptno(MapString, Object param); }接口方法返回ListEmp是可以的但实际数据是通过param这个 Map 回填的下面 Service 层会体现。3.5 Service 与实现类package com.casic.service; import java.util.List; import java.util.Map; import com.casic.model.Emp; public interface EmpService { ListEmp queryDeptEmps(MapString, Object param); }package com.casic.service.impl; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.casic.dao.EmpMapper; import com.casic.model.Emp; import com.casic.service.EmpService; Service(empService) public class EmpServiceImpl implements EmpService { Autowired private EmpMapper empMapper; Override public ListEmp queryDeptEmps(MapString, Object param) { // 调用过程中游标结果集已经被回填到 param 的 result key 上 empMapper.queryEmpByDeptno(param); // 从 Map 里取出结果集并强转 ListEmp empList (ListEmp) param.get(result); return empList; } }这里的关键是empMapper.queryEmpByDeptno(param)执行完之后param.get(result)就是游标返回的 List。这个回填机制是 Mybatis 对 CALLABLE 语句的特殊处理不需要你手动接收返回值。3.6 Controller 与参数准备package com.casic.controller; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.casic.model.Emp; import com.casic.service.EmpService; import oracle.jdbc.driver.OracleTypes; Controller RequestMapping(/empController) public class EmpController { Autowired private EmpService empService; RequestMapping(/queryEmp) public String showDeptEmps(Emp emp, Model model) { MapString, Object param new HashMapString, Object(); // in 参数赋值 param.put(pdeptno, emp.getDeptno()); // out 参数声明用 OracleTypes.CURSOR 占位 param.put(result, OracleTypes.CURSOR); ListEmp emps empService.queryDeptEmps(param); model.addAttribute(emps, emps); return showEmps; } }param.put(result, OracleTypes.CURSOR)这一步是给 out 参数占位值本身不重要重要的是 key 要和 XML 里的#{result,...}对应上。4. 验证请求从页面提交到游标结果集成功映射配置写完接下来验证整条链路是否跑通。我按实际运行顺序拆成几步。4.1 确认 Oracle 侧数据先确认emp表里有数据并且部门编号分布正常select deptno, count(*) from emp group by deptno order by deptno;假设返回 10、20、30 三个部门各有若干条记录那传 0 应该返回全部传 10 只返回 10 号部门。4.2 启动 SSM 项目并访问页面项目部署到 Tomcat 后访问类似这样的地址http://localhost:8080/your-app/empController/queryEmp?deptno10页面上的下拉框选择部门编号点 Research 提交。如果配置正确你会看到表格里渲染出对应部门的员工列表序号、编号、姓名、职位、入职日期、工资等字段都正常显示。4.3 看日志确认 CALLABLE 执行在 Mybatis 日志里你应该能看到类似这样的输出 Preparing: {call QUERYEMPSBYDEPTNO(?, ?)} Parameters: 10(Integer), 1111(Integer) Total: 14这里的1111就是OracleTypes.CURSOR对应的整数值说明 out 参数被正确识别为游标类型。Total: 14表示游标返回了 14 条记录映射成功。4.4 验证结果集映射是否准确重点检查hiredate字段。Oracle 的date类型映射到 Java 的java.util.Date页面上用fmt:formatDate格式化。如果这里显示正常说明resultMap的字段映射没问题。如果某个字段为空检查列名和 property 名是否大小写一致——Oracle 默认列名大写Mybatis 的resultMap里写小写通常也能匹配但保险起见建议和实际列名对齐。4.5 边界验证传deptno0应该返回全部员工传一个不存在的部门编号比如deptno99游标会打开一个空结果集页面表格为空但不报错。这两种情况都验证一下确认存储过程的if/else分支和 Mybatis 的空结果集处理都正常。5. 本篇常见错排查401、invalid column type、ORA-06550 逐个击破这一节把调 Oracle 游标存储过程时最常撞到的几个报错列出来对照着排查。5.1 invalid column type: 1111这是最典型的报错完整信息类似org.springframework.jdbc.UncategorizedSQLException: ### Error querying database. Cause: java.sql.SQLException: invalid column type: 1111原因通常是出参的jdbcType没写或者写错了。1111是OTHER类型的编码Mybatis 默认把未知类型当成OTHER传给 OracleOracle 不认。解决办法就是显式写jdbcTypeCURSOR并且加上javaTypeResultSet和resultMap。5.2 ORA-06550 / PLS-00306 参数个数或类型错误ORA-06550: line 1, column 7: PLS-00306: wrong number or types of arguments in call to QUERYEMPSBYDEPTNO这个报错说明 Mybatis 传给存储过程的参数和过程定义对不上。检查两点一是{call ...}里的参数个数是否和过程定义一致二是入参的jdbcType是否匹配pdeptno是Integer对应jdbcTypeINTEGER别写成NUMERIC或DECIMAL。5.3 结果集为空但没报错如果日志显示Total: 0但数据库里明明有数据先确认param.put(result, OracleTypes.CURSOR)这一步有没有做。如果 Controller 里忘了给 out 参数占位Mybatis 可能不会正确回填结果。另外确认 XML 里出参的 key 和 Controller 里 put 的 key 完全一致都是result。5.4 local proxy failed 类连接问题如果你的项目通过某种本地代理访问数据库偶尔会看到local proxy failed之类的连接层报错。这类问题一般和存储过程配置无关先确认数据库连接串、监听端口、服务名是否正确再回来排查 Mybatis 配置。别一看到报错就改 XML先分层定位。5.5 OAuth / 认证类报错有些团队会把数据库访问包装成带认证的服务这时候可能出现 OAuth 相关的 token 失效报错。这类问题同样和游标映射无关属于接入层问题检查 token 有效期和刷新逻辑即可。5.6 参数 Map 的 key 拼写错误这个不报错但结果不对最难查。XML 里写#{pdeptno,...}Controller 里 put 的是pdeptNo大小写不一致Mybatis 找不到参数可能传 null 进去存储过程按 null 处理返回空结果。建议 key 统一用小写加下划线或全小写。排查顺序建议先看日志里的Preparing和Parameters确认参数传对了再看Total确认结果集条数最后看页面字段映射。三步定位基本不会绕远路。6. 语义一致 CTA把游标存储过程模板沉淀下来游标结果集映射这块配置一旦跑通其实是可以模板化的。statementTypeCALLABLEjdbcTypeCURSORjavaTypeResultSetresultMap这四件套换个存储过程名和字段映射就能复用。我建议你把本篇的 Mapper XML 和 Service 调用代码存成一个模板文件下次遇到类似的 Oracle 存储过程直接改。如果你在排障过程中需要快速解释 Oracle 报错、生成对应的 Mybatis 配置片段可以走 API Keys 拿 Key接入方式看接入文档把模型对话能力接到你的开发流程里。验证模型返回是否准确用模型对话入口直接试。长期做 SSM Oracle 维护和 Agent 辅助编码的Coding Plan 更适合持续沉淀这类模板。最后留一个实操建议每次改完 Mapper XML先把 Mybatis 日志级别调到 DEBUG确认Preparing里的 SQL 和Parameters里的参数值都对再去页面看结果。这一步能帮你省掉大量「改了不知道哪错了」的时间。
返回列表