核心概念
在 Java 中调用存储过程,主要使用 java.sql.CallableStatement 接口,它继承自 PreparedStatement,专门用于执行 SQL 存储过程和函数。

(图片来源网络,侵删)
调用存储过程的基本语法是 {call procedure_name(?, ?, ...)}。
环境准备
- JDBC 驱动: 确保你的项目中包含了 Oracle JDBC 驱动,对于较新的 Oracle 数据库(如 12c 及以上),推荐使用
ojdbc8.jar或ojdbc11.jar。- Maven 依赖示例:
<dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.3.0.0</version> </dependency>
- Maven 依赖示例:
- 数据库连接: 你需要一个有效的数据库连接 URL、用户名和密码。
调用无参数的存储过程
这是最简单的情况。
Oracle 存储过程示例
CREATE OR REPLACE PROCEDURE say_hello AS
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello from Oracle Stored Procedure!');
END say_hello;
/
Java 代码示例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
import java.sql.SQLException;
public class CallSimpleProcedure {
// 替换为你的数据库连接信息
private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521:ORCLCDB";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
// 使用 try-with-resources 确保资源被自动关闭
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall("{call say_hello}")) {
System.out.println("Calling stored procedure...");
// 执行存储过程
cstmt.execute();
System.out.println("Procedure called successfully.");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
调用带 IN 参数的存储过程
存储过程接收输入参数,但不返回值。
Oracle 存储过程示例
这个存储过程接收一个员工 ID,并打印出该员工的姓名。

(图片来源网络,侵删)
CREATE OR REPLACE PROCEDURE get_emp_name (p_emp_id IN NUMBER)
AS
v_emp_name VARCHAR2(100);
BEGIN
SELECT first_name || ' ' || last_name INTO v_emp_name
FROM employees
WHERE employee_id = p_emp_id;
DBMS_OUTPUT.PUT_LINE('Employee Name: ' || v_emp_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee with ID ' || p_emp_id || ' not found.');
END get_emp_name;
/
(假设你有一个 employees 表)
Java 代码示例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
import java.sql.SQLException;
public class CallProcedureWithInParam {
private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521:ORCLCDB";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
String sql = "{call get_emp_name(?)}";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall(sql)) {
// 设置 IN 参数
int empId = 101; // 假设员工ID为101的员工存在
cstmt.setInt(1, empId);
System.out.println("Calling procedure for emp_id: " + empId);
cstmt.execute();
System.out.println("Procedure call finished.");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
调用带 OUT 参数的存储过程
存储过程执行后,通过 OUT 参数将结果返回给调用者。
Oracle 存储过程示例
这个存储过程接收一个员工 ID,并返回该员工的姓名。
CREATE OR REPLACE PROCEDURE get_emp_name_out (p_emp_id IN NUMBER, p_emp_name OUT VARCHAR2)
AS
BEGIN
SELECT first_name || ' ' || last_name INTO p_emp_name
FROM employees
WHERE employee_id = p_emp_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_emp_name := 'Employee not found.';
END get_emp_name_out;
/
Java 代码示例
关键点:

(图片来源网络,侵删)
- 在
CallableStatement中,OUT参数需要用registerOutParameter方法注册。 - 参数索引从 1 开始。
- 必须在执行
execute()或executeQuery()之后,才能通过getXXX()方法获取OUT参数的值。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
import java.sql.SQLException;
public class CallProcedureWithOutParam {
private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521:ORCLCDB";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
String sql = "{call get_emp_name_out(?, ?)}";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall(sql)) {
int empId = 101;
// 设置 IN 参数
cstmt.setInt(1, empId);
// 注册 OUT 参数 (VARCHAR2 对应 Types.VARCHAR)
cstmt.registerOutParameter(2, java.sql.Types.VARCHAR);
System.out.println("Calling procedure for emp_id: " + empId);
cstmt.execute();
// 执行后,获取 OUT 参数的值
String empName = cstmt.getString(2);
System.out.println("Retrieved Employee Name: " + empName);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
调用带 IN OUT 参数的存储过程
参数既可以传入值,也可以在过程执行后被修改并返回。
Oracle 存储过程示例
这个存储过程接收一个数字,将其加倍后返回。
CREATE OR REPLACE PROCEDURE double_the_number (p_num IN OUT NUMBER) AS BEGIN p_num := p_num * 2; END double_the_number; /
Java 代码示例
关键点:
IN OUT参数也需要用registerOutParameter注册。- 设置
IN值和获取OUT值的步骤都必不可少。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
import java.sql.SQLException;
public class CallProcedureWithInOutParam {
private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521:ORCLCDB";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
String sql = "{call double_the_number(?)}";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall(sql)) {
int originalNum = 10;
// 注册 IN OUT 参数 (NUMBER 对应 Types.NUMERIC 或 Types.INTEGER)
cstmt.registerOutParameter(1, java.sql.Types.INTEGER);
// 设置 IN 值
cstmt.setInt(1, originalNum);
System.out.println("Original number: " + originalNum);
cstmt.execute();
// 获取 IN OUT 参数返回的值
int doubledNum = cstmt.getInt(1);
System.out.println("Doubled number: " + doubledNum);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
调用返回结果集的存储过程
这是一个更复杂但也很常见的情况,Oracle 使用 REF CURSOR 来返回结果集。
Oracle 存储过程示例
你需要一个包 来定义 REF CURSOR 类型,因为标准的 JDBC 驱动需要这个类型。
-- 1. 创建包定义 CREATE OR REPLACE PACKAGE pkg_employees AS TYPE ref_cursor IS REF CURSOR; END pkg_employees; /
然后创建存储过程:
-- 2. 创建使用 REF CURSOR 的存储过程
CREATE OR REPLACE PROCEDURE get_emps_by_dept (p_dept_id IN NUMBER, p_emps_cursor OUT pkg_employees.ref_cursor)
AS
BEGIN
OPEN p_emps_cursor FOR
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department_id = p_dept_id;
END get_emps_by_dept;
/
Java 代码示例
关键点:
- 注册
REF CURSOROUT参数时,使用Types.REF_CURSOR。 - 执行
executeQuery()方法,它会返回一个ResultSet对象,这个对象就是存储过程返回的结果集。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class CallProcedureWithRefCursor {
private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521:ORCLCDB";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
// 注意:SQL 语句中要指定包名.游标类型
String sql = "{call get_emps_by_dept(?, ?)}";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
CallableStatement cstmt = conn.prepareCall(sql)) {
int deptId = 50; // 假设部门ID为50
// 设置 IN 参数
cstmt.setInt(1, deptId);
// 注册 OUT 参数 (REF CURSOR)
cstmt.registerOutParameter(2, oracle.jdbc.OracleTypes.CURSOR);
System.out.println("Calling procedure to get employees for dept_id: " + deptId);
// executeQuery() 用于返回结果集的存储过程
cstmt.execute();
// 从 OUT 参数中获取 ResultSet
// 注意:这里需要将 getObject() 的结果强制转换为 ResultSet
ResultSet rs = (ResultSet) cstmt.getObject(2);
System.out.println("Employee Details:");
System.out.println("---------------------------------");
while (rs.next()) {
// 通过列名或索引获取数据
int empId = rs.getInt("EMPLOYEE_ID");
String firstName = rs.getString("FIRST_NAME");
String lastName = rs.getString("LAST_NAME");
double salary = rs.getDouble("SALARY");
System.out.printf("ID: %d, Name: %s %s, Salary: %.2f%n", empId, firstName, lastName, salary);
}
// 关闭 ResultSet
if (rs != null) rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
注意: 在上面的代码中,我们使用了 oracle.jdbc.OracleTypes.CURSOR,这是 Oracle 特有的类型,标准的 JDBC Types.REF_CURSOR 在某些驱动中也可能工作,但使用 Oracle 的特定类型通常更可靠。
总结与最佳实践
- 使用
try-with-resources: 始终将Connection,Statement,CallableStatement, 和ResultSet放在try-with-resources块中,以确保它们被正确关闭,防止资源泄漏。 - 预处理 SQL: 使用
CallableStatement可以有效防止 SQL 注入攻击。 - 参数顺序: 在
registerOutParameter,setXXX等方法中,参数的索引顺序必须与 SQL 语句中 的顺序完全一致。 - 处理异常: 妥善处理
SQLException,获取有意义的错误信息。 - 驱动版本: 使用与你的 Oracle 数据库服务器版本兼容的 JDBC 驱动版本。
