必须使用SYS_REFCURSOR返回多行结果集,Java端通过registerOutParameter(2, Types.OTHER)注册并用getResultSet()获取,且须在同连接内及时处理完毕,否则报ORA-01001。

PL/SQL里用REF CURSOR才能返回多行结果集
Java端没法直接接收PL/SQL的SELECT语句结果,必须通过游标变量传递。Oracle原生支持SYS_REFCURSOR(系统预定义的弱类型游标),这是最常用也最稳妥的选择。别用自定义强类型游标——Java JDBC不认,会报ORA-06550: PLS-00382: expression is of wrong type。
存储过程参数得声明为OUT模式的SYS_REFCURSOR:
CREATE OR REPLACE PROCEDURE get_user_list (
p_dept_id IN NUMBER,
p_result OUT SYS_REFCURSOR
) AS
BEGIN
OPEN p_result FOR
SELECT id, name, email FROM users WHERE dept_id = p_dept_id;
END;
Java端必须用registerOutParameter注册Types.OTHER
JDBC驱动把SYS_REFCURSOR映射为java.sql.Types.OTHER,不是Types.RESULT_SET(那是JDBC 4.1+才有的伪类型,Oracle驱动不支持)。漏掉这步或填错类型,Java会抛SQLException: Invalid column type。
调用时关键三步不能少:
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 用
conn.prepareCall("{CALL get_user_list(?, ?)}")创建CallableStatement - 对游标参数调用
cs.registerOutParameter(2, Types.OTHER) -
cs.execute()后,用cs.getResultSet()获取结果集(不是getObject())
注意游标生命周期和连接绑定关系
REF CURSOR本质是服务端打开的一个结果集指针,它和当前数据库连接强绑定。Java端拿到ResultSet后,必须在同一个连接上处理完数据——如果中途关闭连接、或用别的连接去next(),会立刻报ORA-01001: invalid cursor。
常见翻车点:
- 在Spring
@Transactional方法里调用,但事务提交后连接被释放,结果集还没读完 - 用HikariCP等连接池时,误以为
ResultSet可跨连接复用 - 没及时
close()结果集,导致游标在DB端长期占用内存(尤其大数据量时)
复杂查询慎用REF CURSOR传参
如果Java要动态传入排序字段、WHERE条件片段,别想着在PL/SQL里拼SQL再OPEN游标——这会引入SQL注入风险,且Oracle无法缓存执行计划。正确做法是Java构造完整SQL,用PreparedStatement直连查询;或者在存储过程中用DBMS_SQL包(但开发维护成本高,多数场景不值当)。
真正适合REF CURSOR的场景就两类:固定结构的报表数据、权限控制严格的内部数据导出。其它情况,先问自己一句:这个逻辑非得塞进数据库层不可吗?

















