最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如何在Java中读取Oracle SYS_REFCURSOR
时间:2026-08-19 09:56:48 编辑:袖梨 来源:一聚教程网
Oracle JDBC驱动需显式注册SYS_REFCURSOR为Types.OTHER,执行前注册、执行后取结果,用getObject()获取ResultSet并手动close(),否则引发ORA-01000等错误。
Oracle JDBC驱动必须启用REF CURSOR支持
默认情况下,OracleDriver 不会自动将 SYS_REFCURSOR 映射为 ResultSet,必须显式注册类型。不注册就调用 getResultSet() 会抛 SQLException: invalid column type 或返回 null。
关键操作是:在 CallableStatement 执行前,对每个 SYS_REFCURSOR 输出参数调用 registerOutParameter(int paramIndex, Types.OTHER),且驱动版本需 ≥ 12.1(推荐使用 ojdbc8.jar)。
- 必须用
Types.OTHER,不能用Types.STRUCT或Types.JAVA_OBJECT - 若存储过程有多个 REF CURSOR,每个都要单独
registerOutParameter - 注册顺序必须与 SQL 中
OUT参数声明顺序一致
正确获取 ResultSet 的三步顺序不能颠倒
拿到 CallableStatement 后,执行顺序错一步就会失败:先注册 → 再执行 → 最后取结果。常见错误是执行完才去注册,或注册后又调用 setXXX() 改变参数状态。
String sql = "{call my_pkg.get_data(?, ?)}";CallableStatement cs = conn.prepareCall(sql);cs.registerOutParameter(2, Types.OTHER); // 第2个参数是 SYS_REFCURSORcs.setString(1, "key");cs.execute(); // 必须 execute() 之后才能 getResultSet()ResultSet rs = (ResultSet) cs.getObject(2); // 推荐用 getObject(),不是 getResultSet()
-
cs.getObject(2)比cs.getResultSet()更可靠;后者仅适用于最后一个 OUT 参数是 REF CURSOR 的场景 - 执行前不能调用
cs.getResultSet(),会报 “statement not executed” - 如果存储过程还返回其他标量值(如
INT),要在execute()后、取ResultSet前读取
ResultSet 关闭时机影响连接池行为
REF CURSOR 对应的 ResultSet 生命周期绑定到数据库游标,不及时关闭会导致 Oracle 端游标堆积、ORA-01000 错误,尤其在 HikariCP 或 Druid 连接池中表现明显。
- 必须在业务逻辑处理完后显式调用
rs.close(),不能依赖try-with-resources自动关闭 —— 因为rs是从cs拿出来的,不是直接创建的 -
cs.close()不会自动关闭其派生的ResultSet,JDBC 规范明确要求手动关 - 若用 Spring JdbcTemplate,需配合
ConnectionCallback手动管理,它不支持 REF CURSOR 自动释放
空结果集或 NULL REF CURSOR 的判断方式
存储过程可能因条件不满足返回 NULL 的 SYS_REFCURSOR,此时 getObject(2) 返回 null,而非空 ResultSet。直接调用 next() 会 NPE。
Object obj = cs.getObject(2);if (obj == null) {// 存储过程未打开游标,按无数据处理} else {ResultSet rs = (ResultSet) obj;while (rs.next()) { /* ... */ }rs.close();}
- 不能用
rs == null判断,要先检查getObject()返回值 - Oracle 侧若用
OPEN refcur FOR NULL;,Java 端收到的就是null - 某些旧版驱动在 NULL 游标下会抛
NullPointerException而非返回null,升级驱动可规避
实际中最容易被忽略的是:REF CURSOR 的生命周期完全由 Oracle 控制,Java 层没有任何缓冲或延迟释放机制。一旦忘记 close(),问题往往在压测时才暴露,且日志里只显示游标耗尽,很难关联到具体哪段代码漏关。