杰瑞科技汇

Oracle Java如何调用存储过程?

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

Oracle Java如何调用存储过程?-图1
(图片来源网络,侵删)

核心概念

在 Java 中调用 Oracle 存储过程,我们主要使用 java.sql.CallableStatement 接口,这个接口专门用于执行 SQL 存储过程和函数。

调用存储过程的基本语法是 {call procedure_name(?, ?, ...)},如果存储函数(有返回值),则使用 {? = call function_name(?, ?, ...)}

CallableStatement 中的 是占位符,你需要通过 setXXX() 方法为其设置值,这些 的顺序必须与存储过程定义的参数顺序完全一致。


准备工作

  1. Oracle JDBC 驱动:确保你的项目中包含了 Oracle JDBC 驱动 JAR 文件(ojdbc8.jarojdbc11.jar),对于 Maven 项目,可以在 pom.xml 中添加依赖:

    Oracle Java如何调用存储过程?-图2
    (图片来源网络,侵删)
    <dependency>
        <groupId>com.oracle.database.jdbc</groupId>
        <artifactId>ojdbc8</artifactId>
        <version>19.3.0.0</version> <!-- 使用合适的版本 -->
    </dependency>
  2. 数据库连接信息:你需要知道数据库的 URL、用户名和密码。

  3. 存储过程:假设我们已经在 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(); }
            }
        }
    }
}

调用返回游标的存储过程

处理返回游标稍微复杂一些,需要使用 OracleCallableStatementREF 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);
        }
    }
}

最佳实践和注意事项

  1. 使用 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 会自动关闭
  2. 处理异常:总是妥善处理 SQLException,记录错误日志,而不是简单地打印堆栈。

  3. 防止 SQL 注入:虽然存储过程本身可以降低 SQL 注入的风险,但在构建 SQL 字符串时,如果包含任何动态内容,仍需保持警惕。CallableStatementsetXXX 方法会为你处理参数的转义。

  4. 事务管理:默认情况下,JDBC 在每个 DML 操作(如 UPDATE, INSERT)后都会自动提交,如果你需要在一个事务中执行多个操作,需要手动控制:

    conn.setAutoCommit(false); // 关闭自动提交
    // ... 执行多个存储过程调用 ...
    conn.commit(); // 手动提交
    // 如果出错
    // conn.rollback();
  5. 性能CallableStatement 对象是预编译的,如果需要多次执行同一个存储过程(只是参数不同),可以重复使用同一个 CallableStatement 对象来提高性能。

分享:
扫描分享到社交APP
上一篇
下一篇