必须用CallableStatement,不可用PreparedStatement或Statement;所有OUT/INOUT参数须在execute()前调用registerOutParameter()注册,索引从1开始,类型严格匹配底层SQL类型(如NUMBER→Types.NUMERIC,REF CURSOR→Types.OTHER),取值须用对应getXXX()方法且不能晚于关闭语句。
Oracle存储过程有OUT参数时,JDBC必须用CallableStatement
java原生jdbc不支持直接用preparedstatement或statement调用带out或in out参数的存储过程——会报ora-01008: not all variables bound或invalid column index。必须用callablestatement,它专为存储过程/函数设计,能显式注册输出参数类型。
常见错误是把{call proc_name(?, ?)}写成SELECT * FROM ...风格,或者漏掉registerOutParameter()调用。
-
CallableStatement的SQL模板必须以{call ...}或{? = call ...}开头(后者用于函数) - 所有
OUT和IN OUT参数,必须在execute()前调用registerOutParameter(int parameterIndex, int sqlType) - 索引从1开始,和问号顺序严格对应,不是按声明顺序
- 如果存储过程返回结果集(
REF CURSOR),需用Types.OTHER注册,再用getResultSet()获取
registerOutParameter()的sqlType怎么选
Oracle的PL/SQL类型和JDBC java.sql.Types不是一一映射的,选错会导致SQLException: Invalid column type或数据截断。关键原则:看存储过程中该参数定义的**底层SQL类型**,而不是PL/SQL别名(如my_number NUMBER(10,2) → Types.NUMERIC,不是Types.DECIMAL)。
-
NUMBER无精度:用Types.NUMERIC或Types.DECIMAL(二者等价) -
NUMBER(p,s)带小数:优先用Types.NUMERIC,setBigDecimal()取值最准 -
VARCHAR2/CHAR:统一用Types.VARCHAR -
DATE:用Types.DATE,但注意OracleDATE含时分秒,建议用Types.TIMESTAMP配合java.time.LocalDateTime(JDBC 4.2+) -
REF CURSOR:必须用Types.OTHER,不能用Types.CURSOR(Oracle旧驱动可能识别,但标准不保证)
处理REF CURSOR输出参数的典型代码结构
这是最容易出错的场景:忘记注册、注册错类型、或在execute()后没调用getResultSet()就去读值,导致NullPointerException或空结果。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
String sql = "{call get_user_list(?, ?)}";
try (CallableStatement cs = conn.prepareCall(sql)) {
cs.setString(1, "ACTIVE"); // IN参数
cs.registerOutParameter(2, Types.OTHER); // REF CURSOR必须用OTHER
cs.execute();
try (ResultSet rs = cs.getResultSet()) { // 必须用getResultSet(),不是getObject()
while (rs.next()) {
String name = rs.getString("name");
int id = rs.getInt("id");
// ...
}
}
}
- 不要对
REF CURSOR参数调用getObject(2)——它返回的是OracleResultSet实例,但类型擦除后无法安全转型 - 确保存储过程中
OPEN cursor_var FOR SELECT ...已执行,否则getResultSet()返回null - 如果过程里有多个
REF CURSOR,按注册顺序依次调用getResultSet()
事务与连接关闭时的隐含风险
Oracle存储过程内部的DML操作(如INSERT/UPDATE)默认参与外部JDBC事务,但容易被忽略两点:
- 如果存储过程里写了
COMMIT或ROLLBACK,会破坏Java层的事务控制——JDBC事务将失效,后续conn.commit()不再生效 -
CallableStatement未关闭时,Oracle服务器端游标(尤其是REF CURSOR)可能持续占用资源,触发ORA-01000: maximum open cursors exceeded - 使用连接池(如HikariCP)时,务必确认驱动版本支持
REF CURSOR自动清理;旧版ojdbc6常需手动close()语句和结果集
真正麻烦的从来不是语法,而是PL/SQL里那句没注释的COMMIT,或者DBA给的存储过程文档里漏写了REF CURSOR需要客户端主动消费。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










