核心概念
在 Java 中,我们使用 JDBC (Java Database Connectivity) API 来与数据库交互,调用存储过程主要涉及 java.sql.CallableStatement 接口,这个接口专门用于执行 SQL 存储过程和函数。

环境准备
在开始之前,请确保你已经:
- JDK 安装: 已安装 Java 开发工具包。
- Oracle JDBC 驱动: 下载了适用于你 Oracle 数据库版本的 JDBC 驱动 JAR 文件(
ojdbc8.jar或ojdbc11.jar),你需要将此 JAR 文件添加到你的项目的 classpath 中。 - Oracle 数据库: 有一个可访问的 Oracle 数据库实例。
- 存储过程: 已经在数据库中创建了你想要调用的存储过程。
Oracle 存储过程示例
为了演示,我们先在 Oracle 数据库中创建几个不同类型的存储过程。
示例 1: 无参数的存储过程
CREATE OR REPLACE PROCEDURE say_hello AS
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello from Oracle Stored Procedure!');
END say_hello;
/
示例 2: 带有 IN 参数的存储过程

CREATE OR REPLACE PROCEDURE greet_user (p_user_name IN VARCHAR2) AS
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello, ' || p_user_name || '!');
END greet_user;
/
示例 3: 带有 IN OUT 参数的存储过程
CREATE OR REPLACE PROCEDURE process_number (p_number IN OUT NUMBER) AS
BEGIN
p_number := p_number * 2; -- 将传入的数字乘以2,然后返回
DBMS_OUTPUT.PUT_LINE('Processed number: ' || p_number);
END process_number;
/
示例 4: 带有 OUT 参数的存储过程
CREATE OR REPLACE PROCEDURE get_user_info (p_user_id IN NUMBER, p_user_name OUT VARCHAR2, p_create_date OUT DATE) AS
BEGIN
SELECT user_name, create_date INTO p_user_name, p_create_date
FROM users
WHERE user_id = p_user_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_user_name := 'Unknown';
p_create_date := NULL;
END get_user_info;
/
示例 5: 返回 Ref Cursor 的存储过程 (最常用,用于返回结果集) 创建一个类型:
CREATE TYPE USER_CURSOR_TYPE AS REF CURSOR;
然后创建存储过程:

CREATE OR REPLACE PROCEDURE get_users_cursor (p_cursor OUT USER_CURSOR_TYPE) AS
BEGIN
OPEN p_cursor FOR
SELECT user_id, user_name, email FROM users ORDER BY user_id;
END get_users_cursor;
/
(注意:此处的 users 表需要预先存在)
Java 代码调用示例
下面我们用 Java 代码来依次调用上面的存储过程。
基础模板
import java.sql.*;
import oracle.jdbc.OracleDriver;
public class CallOracleProcedure {
// 数据库连接信息 (请替换为你的实际信息)
private static final String DB_URL = "jdbc:oracle:thin:@//your_host:your_port/your_service_name";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
// 1. 加载驱动 (对于新版本JDBC驱动,通常可以省略此步)
try {
Class.forName("oracle.jdbc.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
// 使用 try-with-resources 确保 Connection, CallableStatement, ResultSet 自动关闭
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
System.out.println("数据库连接成功!");
// --- 在这里调用各种存储过程 ---
// callSimpleProcedure(conn);
// callInProcedure(conn);
// callInOutProcedure(conn);
// callOutProcedure(conn);
callRefCursorProcedure(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
// ... 下面将添加各个调用方法 ...
}
示例 1: 调用无参数的存储过程
private static void callSimpleProcedure(Connection conn) throws SQLException {
String sql = "{call say_hello()}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
System.out.println("\n--- 调用无参数存储过程 ---");
cstmt.execute();
System.out.println("存储过程执行完毕。");
}
}
示例 2: 调用带 IN 参数的存储过程
使用 setXXX() 方法来设置 IN 参数。
private static void callInProcedure(Connection conn) throws SQLException {
String sql = "{call greet_user(?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
System.out.println("\n--- 调用带IN参数的存储过程 ---");
cstmt.setString(1, "Alice"); // 第一个参数是 IN 类型,设置为 "Alice"
cstmt.execute();
System.out.println("IN参数存储过程执行完毕。");
}
}
示例 3: 调用带 IN OUT 参数的存储过程
需要同时注册参数类型,并获取返回值。
private static void callInOutProcedure(Connection conn) throws SQLException {
String sql = "{call process_number(?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
System.out.println("\n--- 调用带IN OUT参数的存储过程 ---");
// 注册参数类型 (第一个参数是 IN OUT)
cstmt.registerOutParameter(1, Types.NUMERIC); // Types.INTEGER 也可以
// 设置 IN 值
int initialValue = 10;
c.setInt(1, initialValue);
System.out.println("传入的初始值: " + initialValue);
// 执行存储过程
cstmt.execute();
// 获取 OUT 值
int resultValue = cstmt.getInt(1);
System.out.println("从存储过程返回的值: " + resultValue);
}
}
示例 4: 调用带 OUT 参数的存储过程
与 IN OUT 类似,但只需要注册和获取 OUT 值。
private static void callOutProcedure(Connection conn) throws SQLException {
String sql = "{call get_user_info(?, ?, ?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
System.out.println("\n--- 调用带OUT参数的存储过程 ---");
// 设置 IN 参数
cstmt.setInt(1, 101); // 假设用户ID为101的用户存在
// 注册 OUT 参数
cstmt.registerOutParameter(2, Types.VARCHAR); // p_user_name
cstmt.registerOutParameter(3, Types.DATE); // p_create_date
// 执行
cstmt.execute();
// 获取 OUT 参数
String userName = cstmt.getString(2);
Date createDate = cstmt.getDate(3);
System.out.println("用户名: " + userName);
System.out.println("创建日期: " + createDate);
}
}
示例 5: 调用返回 Ref Cursor 的存储过程 (重点)
这是最实用的场景,用于从存储过程返回一个结果集。
private static void callRefCursorProcedure(Connection conn) throws SQLException {
// 语法 {? = call ...} 用于函数,对于返回参数的存储过程,使用 {call ...}
// Oracle 的 REF CURSOR 通过 OUT 参数传递
String sql = "{call get_users_cursor(?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
System.out.println("\n--- 调用返回Ref Cursor的存储过程 ---");
// 注册 OUT 参数,类型为 Oracle 的 OracleCursor 或标准的 Types.REF_CURSOR
// 使用 Types.REF_CURSOR 是标准做法,但需要驱动支持
cstmt.registerOutParameter(1, oracle.jdbc.OracleTypes.CURSOR);
// 或者 cstmt.registerOutParameter(1, Types.REF_CURSOR); // 标准类型
// 执行
cstmt.execute();
// 获取结果集 (OUT 参数实际上返回的是 ResultSet)
// 注意:这里需要将 Object 强制转换为 ResultSet
ResultSet rs = (ResultSet) cstmt.getObject(1);
// 处理结果集
System.out.println("ID\tName\tEmail");
System.out.println("------------------------");
while (rs.next()) {
System.out.print(rs.getInt("user_id") + "\t");
System.out.print(rs.getString("user_name") + "\t");
System.out.println(rs.getString("email"));
}
// 关闭 ResultSet (如果使用 try-with-resources,会自动关闭)
if (rs != null) rs.close();
}
}
最佳实践和注意事项
- 使用
try-with-resources: 始终将Connection,Statement,PreparedStatement,CallableStatement, 和ResultSet放在try-with-resources语句中,以确保它们在使用完毕后能被正确关闭,防止资源泄漏。 - 使用
PreparedStatement/CallableStatement: 永远不要使用字符串拼接来构建 SQL 语句,这会导致 SQL 注入风险。CallableStatement就是处理存储过程调用的标准、安全的方式。 - 处理异常: 妥善处理
SQLException,记录错误日志,并向用户友好的提示。 - 驱动版本: 确保你使用的 JDBC 驱动版本与你的 Oracle 数据库版本兼容,Oracle 11g 数据库最好使用
ojdbc6.jar或更高,Oracle 12c 及以上使用ojdbc8.jar或ojdbc11.jar。 - 连接池: 在生产环境中,不要每次都创建新的数据库连接,使用如 HikariCP, DBCP 等连接池来管理数据库连接,可以大大提高性能和稳定性。
- SQL 转义语法: 是标准的 JDBC 转义语法,用于表示参数,数据库驱动会将其转换为特定数据库(如 Oracle)所需的格式。
- OUT 参数的顺序: 在注册
OUT或IN OUT参数时,必须按照它们在存储过程定义中的顺序进行注册。
希望这份详细的指南能帮助你顺利地在 Java 中调用 Oracle 存储过程!
