下面我将为你提供一个全面、详细的指南,涵盖从基础概念到不同类型存储过程调用的具体代码示例。

核心概念
在 Java 中调用 Oracle 存储过程,我们主要使用 java.sql.CallableStatement 接口,这个接口专门用于执行 SQL 存储过程和函数。
调用存储过程的基本语法是 {call procedure_name(?, ?, ...)},如果存储函数(有返回值),则使用 {? = call function_name(?, ?, ...)}。
CallableStatement 中的 是占位符,你需要通过 setXXX() 方法为其设置值,这些 的顺序必须与存储过程定义的参数顺序完全一致。
准备工作
-
Oracle JDBC 驱动:确保你的项目中包含了 Oracle JDBC 驱动 JAR 文件(
ojdbc8.jar或ojdbc11.jar),对于 Maven 项目,可以在pom.xml中添加依赖:
(图片来源网络,侵删)<dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.3.0.0</version> <!-- 使用合适的版本 --> </dependency> -
数据库连接信息:你需要知道数据库的 URL、用户名和密码。
-
存储过程:假设我们已经在 Oracle 数据库中创建了几个存储过程用于演示。
创建示例存储过程
为了演示,我们先在 Oracle 数据库中创建几个不同类型的存储过程。
无参数的存储过程
CREATE OR REPLACE PROCEDURE say_hello AS
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello from Oracle Stored Procedure!');
END say_hello;
/
带输入参数的存储过程
CREATE OR REPLACE PROCEDURE greet_user (p_user_name IN VARCHAR2) AS
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello, ' || p_user_name || '!');
END greet_user;
/
带输入和输出参数的存储过程
CREATE OR REPLACE PROCEDURE get_user_details (
p_user_id IN NUMBER,
p_user_name OUT VARCHAR2,
p_user_email OUT VARCHAR2
) AS
BEGIN
SELECT user_name, email INTO p_user_name, p_user_email FROM users WHERE user_id = p_user_id;
END get_user_details;
/
注意:执行此代码需要一个名为 users 的表,并包含 user_id, user_name, email 列。
帉入/输出参数的存储过程
CREATE OR REPLACE PROCEDURE update_salary (
p_employee_id IN NUMBER,
p_raise_amount IN OUT NUMBER
) AS
BEGIN
-- 获取当前薪水
SELECT salary INTO p_raise_amount FROM employees WHERE employee_id = p_employee_id;
-- 更新薪水
p_raise_amount := p_raise_amount + 500; -- 假设固定加薪500
UPDATE employees SET salary = p_raise_amount WHERE employee_id = p_employee_id;
DBMS_OUTPUT.PUT_LINE('Salary updated for employee ' || p_employee_id || '. New salary: ' || p_raise_amount);
END update_salary;
/
注意:执行此代码需要一个名为 employees 的表,并包含 employee_id, salary 列。
返回游标的存储过程 这是处理多行返回结果集的标准方式。
CREATE OR REPLACE PACKAGE pkg_types AS -- 定义一个游标类型 TYPE ref_cursor IS REF CURSOR; END pkg_types; / CREATE OR REPLACE PROCEDURE get_all_users (p_cursor OUT pkg_types.ref_cursor) AS BEGIN -- 打开游标,将查询结果关联到游标 OPEN p_cursor FOR SELECT user_id, user_name, email FROM users ORDER BY user_id; END get_all_users; /
Java 代码实现
下面我们逐一编写 Java 代码来调用上述存储过程。
工具类(获取连接)
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
private static final String URL = "jdbc:oracle:thin:@//your_host:your_port/your_service_name";
private static final String USER = "your_username";
private static final String PASSWORD = "your_password";
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
public static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
调用无参数的存储过程
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
public class CallNoParamProcedure {
public static void main(String[] args) {
Connection conn = null;
CallableStatement cstmt = null;
try {
conn = DBUtil.getConnection();
// 1. 创建 CallableStatement
// 语法: {call procedure_name}
String sql = "{call say_hello()}";
cstmt = conn.prepareCall(sql);
// 2. 执行
cstmt.execute();
System.out.println("Procedure 'say_hello' called successfully.");
// 注意:要看到 DBMS_OUTPUT 的结果,需要额外设置
// java.sql.Statement stmt = conn.createStatement();
// stmt.execute("BEGIN DBMS_OUTPUT.ENABLE(NULL); END;");
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 3. 关闭资源
DBUtil.closeConnection(conn);
if (cstmt != null) {
try {
cstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
调用带输入参数的存储过程
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
public class CallInParamProcedure {
public static void main(String[] args) {
Connection conn = null;
CallableStatement cstmt = null;
try {
conn = DBUtil.getConnection();
// 1. 创建 CallableStatement
// 语法: {call procedure_name(?, ?)}
String sql = "{call greet_user(?)}";
cstmt = conn.prepareCall(sql);
// 2. 设置输入参数 (索引从1开始)
cstmt.setString(1, "Alice");
// 3. 执行
cstmt.execute();
System.out.println("Procedure 'greet_user' called for 'Alice'.");
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.closeConnection(conn);
if (cstmt != null) {
try { cstmt.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
}
}
调用带输入和输出参数的存储过程
这是最常见的场景之一,关键在于使用 registerOutParameter 来注册输出参数。
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
public class CallInOutParamProcedure {
public static void main(String[] args) {
Connection conn = null;
CallableStatement cstmt = null;
try {
conn = DBUtil.getConnection();
// 1. 创建 CallableStatement
// 语法: {call procedure_name(?, ?, ?)}
String sql = "{call get_user_details(?, ?, ?)}";
cstmt = conn.prepareCall(sql);
// 2. 设置参数
int userId = 101; // 假设用户ID为101存在
cstmt.setInt(1, userId); // 设置第一个参数 (IN)
// 3. 注册输出参数 (必须设置在执行之前)
// 参数2和3是 OUT 类型
cstmt.registerOutParameter(2, java.sql.Types.VARCHAR); // user_name
cstmt.registerOutParameter(3, java.sql.Types.VARCHAR); // user_email
// 4. 执行
cstmt.execute();
// 5. 获取输出参数的值
String userName = cstmt.getString(2);
String userEmail = cstmt.getString(3);
System.out.println("User ID: " + userId);
System.out.println("User Name: " + userName);
System.out.println("User Email: " + userEmail);
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.closeConnection(conn);
if (cstmt != null) {
try { cstmt.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
}
}
调用帀入/输出参数的存储过程
与 OUT 参数类似,但 IN OUT 参数需要先用 setXXX 设置初始值,执行后再用 getXXX 获取最终值。
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
public class CallInOutParamProcedure {
public static void main(String[] args) {
Connection conn = null;
CallableStatement cstmt = null;
try {
conn = DBUtil.getConnection();
// 1. 创建 CallableStatement
String sql = "{call update_salary(?, ?)}";
cstmt = conn.prepareCall(sql);
// 2. 设置参数
int employeeId = 201; // 假设员工ID为201存在
double raiseAmount = 1000.0; // 初始的涨薪提议
cstmt.setInt(1, employeeId);
// 对于 IN OUT 参数,先设置输入值
cstmt.setDouble(2, raiseAmount);
// 3. 注册输出参数
cstmt.registerOutParameter(2, java.sql.Types.DOUBLE);
// 4. 执行
cstmt.execute();
// 5. 获取最终值 (IN OUT 参数的最终值)
double finalRaiseAmount = cstmt.getDouble(2);
System.out.println("Initial raise amount: " + raiseAmount);
System.out.println("Final amount after procedure (new salary): " + finalRaiseAmount);
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.closeConnection(conn);
if (cstmt != null) {
try { cstmt.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
}
}
调用返回游标的存储过程
处理返回游标稍微复杂一些,需要使用 OracleCallableStatement 和 REF CURSOR 类型。
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import oracle.jdbc.OracleCallableStatement; // 需要导入 Oracle 特有的类
public class CallRefCursorProcedure {
public static void main(String[] args) {
Connection conn = null;
OracleCallableStatement ocstmt = null; // 使用 Oracle 的 CallableStatement
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
// 1. 创建 CallableStatement
// 语法: {call procedure_name(?)}
String sql = "{call get_all_users(?)}";
ocstmt = (OracleCallableStatement) conn.prepareCall(sql);
// 2. 注册输出参数 (类型为 OracleTypes.CURSOR)
ocstmt.registerOutParameter(1, oracle.jdbc.OracleTypes.CURSOR);
// 3. 执行
ocstmt.execute();
// 4. 获取结果集 (从 OUT 参数中)
rs = (ResultSet) ocstmt.getObject(1);
// 5. 遍历结果集
System.out.println("All Users:");
System.out.println("ID\tName\t\tEmail");
while (rs.next()) {
System.out.println(rs.getInt("user_id") + "\t" +
rs.getString("user_name") + "\t\t" +
rs.getString("email"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源 (顺序很重要: ResultSet -> Statement -> Connection)
if (rs != null) {
try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
}
if (ocstmt != null) {
try { ocstmt.close(); } catch (SQLException e) { e.printStackTrace(); }
}
DBUtil.closeConnection(conn);
}
}
}
最佳实践和注意事项
-
使用
try-with-resources:从 Java 7 开始,推荐使用try-with-resources语句来自动管理资源(如Connection,Statement,ResultSet),可以避免忘记关闭资源导致的泄漏。try (Connection conn = DBUtil.getConnection(); CallableStatement cstmt = conn.prepareCall("{call your_procedure(?)}")) { // ... 设置参数和执行 ... } catch (SQLException e) { e.printStackTrace(); } // conn 和 cstmt 会自动关闭 -
处理异常:总是妥善处理
SQLException,记录错误日志,而不是简单地打印堆栈。 -
防止 SQL 注入:虽然存储过程本身可以降低 SQL 注入的风险,但在构建 SQL 字符串时,如果包含任何动态内容,仍需保持警惕。
CallableStatement的setXXX方法会为你处理参数的转义。 -
事务管理:默认情况下,JDBC 在每个 DML 操作(如
UPDATE,INSERT)后都会自动提交,如果你需要在一个事务中执行多个操作,需要手动控制:conn.setAutoCommit(false); // 关闭自动提交 // ... 执行多个存储过程调用 ... conn.commit(); // 手动提交 // 如果出错 // conn.rollback();
-
性能:
CallableStatement对象是预编译的,如果需要多次执行同一个存储过程(只是参数不同),可以重复使用同一个CallableStatement对象来提高性能。
