CallableStatement 是 Java 调用 Oracle 游标存储过程的核心,必须注册 oracle.jdbc.OracleTypes.CURSOR 类型并用 getObject() 获取 ResultSet;游标需在包中定义 REF CURSOR 类型,不可在匿名块或独立过程中返回;参数索引从 1 开始,ResultSet 必须显式 close 以防 ORA-01000 错误。

CallableStatement 是 Java 调用带游标的 Oracle 存储过程的核心入口,但直接调用会失败——必须显式注册 OracleTypes.CURSOR 类型,并用 getObject() 或 getCursor() 提取 ResultSet。否则你会拿到 null 或抛出 ClassCastException。
存储过程必须定义在包里,且游标类型要声明为 REF CURSOR
Oracle 不允许在匿名块或独立过程里直接返回游标;必须通过包(package)定义公共游标类型。常见错误是把 TYPE my_cursor IS REF CURSOR 写在过程体里,或者和包体一起执行——这会导致编译失败。
- 包头定义类型:
CREATE OR REPLACE PACKAGE pkg_demo AS TYPE refcur IS REF CURSOR; PROCEDURE proc_list(p_id IN NUMBER, p_out OUT refcur); END; - 包体实现 OPEN:
CREATE OR REPLACE PACKAGE BODY pkg_demo AS PROCEDURE proc_list(p_id IN NUMBER, p_out OUT refcur) IS BEGIN OPEN p_out FOR SELECT id, name FROM users WHERE dept_id = p_id; END; END; - 不能在包外建独立过程返回游标;也不能在包体里重复定义游标类型
registerOutParameter 必须用 oracle.jdbc.OracleTypes.CURSOR,不是 Types.OTHER
用 java.sql.Types.OTHER 会导致 JDBC 驱动无法识别游标结构,返回空对象或报 ORA-06550 错误。Oracle JDBC 驱动要求明确使用其扩展类型常量。
- 正确写法:
cs.registerOutParameter(2, oracle.jdbc.OracleTypes.CURSOR); - 错误写法:
cs.registerOutParameter(2, Types.OTHER);或cs.registerOutParameter(2, Types.JAVA_OBJECT); - 注意导入:
import oracle.jdbc.OracleTypes;,不是java.sql.Types - 参数索引从 1 开始,和 SQL 中
?位置严格对应
获取结果集后必须手动 close(),尤其在连接池环境下
游标底层绑定数据库会话资源,JDBC 不会在 ResultSet.close() 时自动释放游标句柄。如果漏关,连接池中的连接可能被锁住,后续请求卡死或报 ORA-01000: maximum open cursors exceeded。
- 推荐用 try-with-resources 包裹
ResultSet:try (ResultSet rs = (ResultSet) cs.getObject(2)) { ... } - 不要在存储过程中执行
CLOSE p_out—— JDBC 会接管生命周期,显式 CLOSE 反而引发invalid cursor - 如果用
cs.getCursor(2),它只是getObject(2)的语义别名,行为完全一致,无需额外处理 - 确保
Connection和CallableStatement也按顺序关闭,否则游标资源释放延迟
字段名大小写敏感,且不能依赖列序号跳过元数据检查
Oracle 默认将字段名转为大写,而 Java ResultSet.getString("name") 在未启用 oracle.jdbc.mapUpperCaseToLowerCase 连接属性时会找不到列——报 Invalid column name。
立即学习“Java免费学习笔记(深入)”;
- 安全做法:先用
rs.getMetaData().getColumnLabel(i)确认实际列名 - 或统一用大写访问:
rs.getString("NAME")、rs.getInt("ID") - 避免用
rs.getString(1)这类序号访问,一旦 SQL 改了 SELECT 顺序就崩 - 别在循环里反复调用
rs.getMetaData(),性能差;提前提取一次即可
rs.next() 返回 false,这个游标就彻底读完了,再调一次 rs.beforeFirst() 会抛异常——这点和 MySQL 的可滚动 ResultSet 完全不同。


















