杰瑞科技汇

Java如何调用Oracle存储过程?

核心概念

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

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

调用存储过程的基本语法是 {call procedure_name(?, ?, ...)}


环境准备

  1. JDBC 驱动: 确保你的项目中包含了 Oracle JDBC 驱动,对于较新的 Oracle 数据库(如 12c 及以上),推荐使用 ojdbc8.jarojdbc11.jar
    • Maven 依赖示例:
      <dependency>
          <groupId>com.oracle.database.jdbc</groupId>
          <artifactId>ojdbc8</artifactId>
          <version>19.3.0.0</version>
      </dependency>
  2. 数据库连接: 你需要一个有效的数据库连接 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,并打印出该员工的姓名。

Java如何调用Oracle存储过程?-图2
(图片来源网络,侵删)
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 代码示例

关键点:

Java如何调用Oracle存储过程?-图3
(图片来源网络,侵删)
  • 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 CURSOR OUT 参数时,使用 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 的特定类型通常更可靠。

总结与最佳实践

  1. 使用 try-with-resources: 始终将 Connection, Statement, CallableStatement, 和 ResultSet 放在 try-with-resources 块中,以确保它们被正确关闭,防止资源泄漏。
  2. 预处理 SQL: 使用 CallableStatement 可以有效防止 SQL 注入攻击。
  3. 参数顺序: 在 registerOutParameter, setXXX 等方法中,参数的索引顺序必须与 SQL 语句中 的顺序完全一致。
  4. 处理异常: 妥善处理 SQLException,获取有意义的错误信息。
  5. 驱动版本: 使用与你的 Oracle 数据库服务器版本兼容的 JDBC 驱动版本。
分享:
扫描分享到社交APP
上一篇
下一篇