杰瑞科技汇

Java如何调用Oracle存储过程?

核心概念

在 Java 中调用存储过程,我们通常使用 JDBC(Java Database Connectivity),关键在于理解 Oracle 存储过程的几种不同类型以及对应的 JDBC 调用方式:

Java如何调用Oracle存储过程?-图1
(图片来源网络,侵删)
  1. 无参数过程:最简单的情况。
  2. 有输入参数的过程:Java 向过程传递数据。
  3. 有输出参数的过程:过程执行后返回数据到 Java。
  4. 有输入/输出参数的过程:参数既可以传入数据,也可以传出数据。
  5. 返回结果集的过程:过程返回一个类似 SELECT 查询的结果集,这是最复杂的一种,需要使用 Oracle 特有的 OracleCallableStatementREF CURSOR

准备工作:Oracle 驱动

确保你的项目中包含了 Oracle JDBC 驱动的 JAR 包,对于较新的 Oracle 数据库(如 11g, 12c, 19c),推荐使用 ojdbc8.jarojdbc11.jar

如果你使用 Maven,可以在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>19.3.0.0</version> <!-- 请根据你的 Oracle 版本选择合适的版本 -->
</dependency>

无参数的过程

假设我们有一个存储过程,它只是执行一些操作,不返回任何值。

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:@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) {
        // 使用 try-with-resources 确保 Connection 和 CallableStatement 被自动关闭
        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();
        }
    }
}

代码解释

Java如何调用Oracle存储过程?-图2
(图片来源网络,侵删)
  • DriverManager.getConnection(...): 建立数据库连接。
  • conn.prepareCall("{call say_hello()}"):
    • prepareCall 方法用于准备一个对存储过程或函数的调用。
    • {call procedure_name()} 是调用存储过程的语法格式。
  • cstmt.execute(): 执行存储过程。

有输入参数的过程

Java 向存储过程传递值。

Oracle 存储过程示例

CREATE OR REPLACE PROCEDURE greet_employee (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('Hello, ' || v_emp_name || '!');
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('Employee with ID ' || p_emp_id || ' not found.');
END greet_employee;
/

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:@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) {
        String sql = "{call greet_employee(?)}"; // ? 是一个占位符
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall(sql)) {
            // 设置输入参数
            cstmt.setInt(1, 101); // 设置第一个 ? 为 101
            System.out.println("Calling procedure with IN parameter...");
            cstmt.execute();
            System.out.println("Procedure called successfully.");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

代码解释

  • {call greet_employee(?)}: 是一个参数占位符。
  • cstmt.setInt(1, 101): 为第一个(索引从1开始)参数设置 int 类型的值。
    • setInt 用于 NUMBERINT 类型。
    • setString 用于 VARCHAR2 类型。
    • setDate 用于 DATE 类型,等等。

有输出参数的过程

存储过程执行后,将值返回给 Java。

Oracle 存储过程示例

CREATE OR REPLACE PROCEDURE get_employee_name (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 := 'Not Found';
END get_employee_name;
/

Java 代码示例

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:@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) {
        // 注意 OUT 参数的语法是 ? OUT
        String sql = "{call get_employee_name(?, ?)}";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall(sql)) {
            // 设置 IN 参数
            cstmt.setInt(1, 102);
            // 注册 OUT 参数
            // 必须在执行之前注册
            cstmt.registerOutParameter(2, java.sql.Types.VARCHAR);
            System.out.println("Calling procedure with OUT parameter...");
            cstmt.execute(); // 执行过程
            // 获取 OUT 参数的值
            String employeeName = cstmt.getString(2);
            System.out.println("Employee Name: " + employeeName);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

代码解释

Java如何调用Oracle存储过程?-图3
(图片来源网络,侵删)
  • cstmt.registerOutParameter(2, java.sql.Types.VARCHAR):
    • 这是最关键的一步,在执行 execute() 之前,你必须告诉 JDBC 驱动哪个参数是输出参数,以及它的 SQL 类型。
    • java.sql.Types.VARCHAR 对应 Oracle 的 VARCHAR2
  • cstmt.getString(2): 在执行之后,通过索引获取输出参数的值。

有输入/输出参数的过程

参数既可以传入,也可以传出。

Oracle 存储过程示例

CREATE OR REPLACE PROCEDURE get_employee_info (
    p_emp_id IN NUMBER,
    p_emp_name OUT VARCHAR2,
    p_salary IN OUT NUMBER
) AS
BEGIN
  SELECT first_name || ' ' || last_name, salary
  INTO p_emp_name, p_salary
  FROM employees
  WHERE employee_id = p_emp_id;
  -- 给涨10%的工资(仅作为演示)
  p_salary := p_salary * 1.10;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    p_emp_name := 'Not Found';
    p_salary := 0;
END get_employee_info;
/

Java 代码示例

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:@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) {
        // 注意 IN OUT 参数的语法是 ? INOUT
        String sql = "{call get_employee_info(?, ?, ?)}";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             CallableStatement cstmt = conn.prepareCall(sql)) {
            int empId = 103;
            double initialSalary = 5000.00;
            // 设置 IN 参数
            cstmt.setInt(1, empId);
            // 设置 IN OUT 参数的初始值
            cstmt.setDouble(3, initialSalary);
            // 注册 OUT 参数 (第二个和第三个都是)
            cstmt.registerOutParameter(2, java.sql.Types.VARCHAR);
            cstmt.registerOutParameter(3, java.sql.Types.DOUBLE);
            System.out.println("Calling procedure with IN OUT parameter...");
            cstmt.execute();
            // 获取 OUT 和 IN OUT 参数的返回值
            String empName = cstmt.getString(2);
            double finalSalary = cstmt.getDouble(3);
            System.out.println("Employee ID: " + empId);
            System.out.println("Employee Name: " + empName);
            System.out.println("Initial Salary: " + initialSalary);
            System.out.println("Final Salary (after 10% raise): " + finalSalary);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

代码解释

  • 对于 IN OUT 参数,你需要同时使用 setXxx() 来设置初始值,并用 registerOutParameter() 来注册它用于接收返回值。
  • 执行后,用 getXxx() 获取其最终值。

返回结果集的过程(使用 REF CURSOR)

这是处理存储过程返回多行数据的最佳方式,Oracle 使用 REF CURSOR 来实现。

Oracle 存储过程示例

CREATE OR REPLACE PACKAGE types AS
  -- 定义一个 REF CURSOR 类型
  TYPE emp_cursor IS REF CURSOR;
END types;
/
CREATE OR REPLACE PROCEDURE get_all_employees (p_cursor OUT types.emp_cursor) AS
BEGIN
  -- 打开游标,返回 employees 表的所有数据
  OPEN p_cursor FOR
    SELECT employee_id, first_name, last_name, email, hire_date
    FROM employees
    ORDER BY employee_id;
END get_all_employees;
/

Java 代码示例

这里需要使用 Oracle 特有的 OracleCallableStatement

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import oracle.jdbc.OracleCallableStatement; // Oracle 特有的接口
public class CallProcedureWithResultSet {
    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) {
        // 注意语法: {call procedure_name(?, ?)}
        // 第一个 ? 是 IN 参数(这里没有),第二个 ? 是 OUT 的 REF CURSOR
        // Oracle 的 REF CURSOR 用 OracleTypes.CURSOR 表示
        String sql = "{call get_all_employees(?)}";
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             // 必须使用 OracleCallableStatement 来处理 REF CURSOR
             OracleCallableStatement ocstmt = (OracleCallableStatement) conn.prepareCall(sql)) {
            // 注册 OUT 参数,类型为 OracleTypes.CURSOR
            ocstmt.registerOutParameter(1, oracle.jdbc.OracleTypes.CURSOR);
            System.out.println("Calling procedure that returns a result set...");
            ocstmt.execute();
            // 从 OUT 参数中获取 ResultSet
            // 注意:对于 REF CURSOR,getCursor() �的是一个 ResultSet
            ResultSet rs = ocstmt.getCursor(1);
            System.out.println("Employee List:");
            System.out.println("-------------------------------------------");
            while (rs.next()) {
                int id = rs.getInt("EMPLOYEE_ID");
                String firstName = rs.getString("FIRST_NAME");
                String lastName = rs.getString("LAST_NAME");
                String email = rs.getString("EMAIL");
                System.out.printf("ID: %d, Name: %s %s, Email: %s%n", id, firstName, lastName, email);
            }
            System.out.println("-------------------------------------------");
            rs.close(); // 关闭 ResultSet
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

代码解释

  • OracleCallableStatement: Oracle 扩展了 CallableStatement,增加了对 REF CURSOR 的支持。
  • ocstmt.registerOutParameter(1, oracle.jdbc.OracleTypes.CURSOR): 注册输出参数的类型为 OracleTypes.CURSOR
  • ocstmt.getCursor(1): 执行后,调用 getCursor() 方法从输出参数中获取 ResultSet 对象。
  • 后续代码就和处理普通 SELECT 查询的 ResultSet 一样了。

最佳实践和注意事项

  1. 使用 try-with-resources: 始终将 Connection, Statement, CallableStatement, ResultSet 等资源放在 try-with-resources 语句块中,以确保它们在使用后被正确关闭,防止资源泄漏。
  2. 使用连接池: 在生产环境中,绝对不要每次调用都创建和销毁连接,使用如 HikariCP, DBCP, C3P0 等连接池来管理数据库连接,能极大地提升性能和稳定性。
  3. 处理异常: 捕获 SQLException 并妥善处理,记录日志或向用户展示友好的错误信息。
  4. 防止 SQL 注入: 当使用 PreparedStatement(包括 CallableStatement)时,参数值会被正确转义,从而有效防止 SQL 注入攻击。永远不要用字符串拼接的方式来构建 SQL 调用语句。
  5. 明确参数类型: 在注册 OUT 参数时,务必提供正确的 java.sql.TypesOracleTypes,否则会导致类型转换错误。
  6. *PL/SQL Developer 或 SQLPlus 调试* 在 Java 中调用前,先在 PL/SQL Developer 或 SQLPlus 中测试你的存储过程,确保其逻辑正确,可以使用 DBMS_OUTPUT.PUT_LINE 来打印调试信息。
分享:
扫描分享到社交APP
上一篇
下一篇