Oracle REF CURSOR 必须先 registerOutParameter(index, Types.REF_CURSOR),再 execute(),最后用 getObject() 强转 ResultSet;不可直接调用 getResultSet();需注意驱动版本≥12.1、索引顺序、fetchSize 设置及及时关闭资源。
Oracle REF CURSOR 不能直接用 getResultSet() 获取
java 调用 oracle 存储过程返回 ref cursor 时,最常见错误是调用 callablestatement.getresultset() 直接取结果 —— 这会抛出 sqlexception: invalid column type 或空结果。因为 ref cursor 在 jdbc 中被映射为 java.sql.types.other,必须显式注册为 types.ref_cursor 后再用 getobject() 或专用方法获取。
实操要点:
- 必须在
execute()前调用registerOutParameter(index, Types.REF_CURSOR) - 注册索引必须与存储过程中
OUT参数顺序严格一致(不是名字) - Oracle JDBC 驱动版本需 ≥ 12.1(
ojdbc7.jar或更高),旧版不支持Types.REF_CURSOR - 不要用
setObject(?, null)或setNull(?, Types.OTHER)初始化该参数
正确获取 ResultSet 的三步链:注册 → 执行 → 强转
拿到 REF CURSOR 后,不能直接当 ResultSet 用,必须通过 getObject() 拿到原始对象,再强制转型。JDBC 规范不保证返回类型,但 Oracle 驱动实际返回的是 oracle.jdbc.driver.OracleResultSetImpl(或其子类),可安全转为 ResultSet。
典型写法:
CallableStatement cs = conn.prepareCall("{call pkg.get_data(?, ?)}");
cs.setString(1, "KEY");
cs.registerOutParameter(2, Types.REF_CURSOR); // 注意:这里是第2个参数
cs.execute();
// ✅ 正确方式
ResultSet rs = (ResultSet) cs.getObject(2);
// ❌ 错误方式(会 NPE 或 ClassCastException)
// ResultSet rs = cs.getResultSet();
// ResultSet rs = (ResultSet) cs.getString(2);
注意:cs.getObject(2) 返回值可能为 null(比如存储过程未打开游标),务必判空。
立即学习“Java免费学习笔记(深入)”;
批量读取大结果集时,关闭自动提交 + 设置 fetchSize
REF CURSOR 对应的 ResultSet 默认使用 Oracle 的“服务器端游标”,但 JDBC 客户端仍会一次性拉取多行(默认 fetchSize=10)。对百万级结果,不调整会导致内存暴涨、GC 频繁甚至 OOM。
优化建议:
- 连接开启前设置
conn.setAutoCommit(false)(非必需但推荐,避免隐式事务干扰) - 获取
ResultSet后立即调用rs.setFetchSize(500)(值需根据网络延迟和单行大小权衡) - 避免调用
rs.last()、rs.getRowCount()等触发全量拉取的操作 - 用
while (rs.next())流式处理,处理完及时rs.close()和cs.close()
Spring JDBC 中用 SqlQuery 或 SimpleJdbcCall 封装更稳
手写 CallableStatement 容易漏注册、错索引、忘 close。Spring 提供了更高层封装,能自动处理类型映射和资源释放。
推荐用法:
-
SimpleJdbcCall:适合参数固定、返回单个 REF CURSOR 的场景 - 配置
withReturnValue()并指定returningResultSet("result", new ColumnRowMapper()) - 底层仍调用
registerOutParameter(..., Types.REF_CURSOR),但屏蔽了索引细节 - 若存储过程有多个 OUT REF CURSOR,需改用
SqlQuery或原生CallableStatement(Spring 不支持多游标自动映射)
关键点:Spring 的 ColumnRowMapper 或自定义 RowMapper 必须在 rs.next() 循环内完成映射,不能依赖 rs.isBeforeFirst()。
REF CURSOR 的本质是 Oracle 服务端的一块内存游标句柄,JDBC 层只是透传 —— 所以驱动版本、fetchSize、close 时机这些底层细节,比语法糖更重要。别信“设了 Types.REF_CURSOR 就万事大吉”,漏掉 fetchSize 或没关 auto-commit,线上跑几天就内存告警。


















