杰瑞科技汇

Java如何调用Oracle存储过程?

核心概念

在 Java 中,我们使用 JDBC (Java Database Connectivity) API 来与数据库交互,调用存储过程主要涉及 java.sql.CallableStatement 接口,这个接口专门用于执行 SQL 存储过程和函数。

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

环境准备

在开始之前,请确保你已经:

  1. JDK 安装: 已安装 Java 开发工具包。
  2. Oracle JDBC 驱动: 下载了适用于你 Oracle 数据库版本的 JDBC 驱动 JAR 文件(ojdbc8.jarojdbc11.jar),你需要将此 JAR 文件添加到你的项目的 classpath 中。
  3. Oracle 数据库: 有一个可访问的 Oracle 数据库实例。
  4. 存储过程: 已经在数据库中创建了你想要调用的存储过程。

Oracle 存储过程示例

为了演示,我们先在 Oracle 数据库中创建几个不同类型的存储过程。

示例 1: 无参数的存储过程

CREATE OR REPLACE PROCEDURE say_hello AS
BEGIN
  DBMS_OUTPUT.PUT_LINE('Hello from Oracle Stored Procedure!');
END say_hello;
/

示例 2: 带有 IN 参数的存储过程

Java如何调用Oracle存储过程?-图2
(图片来源网络,侵删)
CREATE OR REPLACE PROCEDURE greet_user (p_user_name IN VARCHAR2) AS
BEGIN
  DBMS_OUTPUT.PUT_LINE('Hello, ' || p_user_name || '!');
END greet_user;
/

示例 3: 带有 IN OUT 参数的存储过程

CREATE OR REPLACE PROCEDURE process_number (p_number IN OUT NUMBER) AS
BEGIN
  p_number := p_number * 2; -- 将传入的数字乘以2,然后返回
  DBMS_OUTPUT.PUT_LINE('Processed number: ' || p_number);
END process_number;
/

示例 4: 带有 OUT 参数的存储过程

CREATE OR REPLACE PROCEDURE get_user_info (p_user_id IN NUMBER, p_user_name OUT VARCHAR2, p_create_date OUT DATE) AS
BEGIN
  SELECT user_name, create_date INTO p_user_name, p_create_date
  FROM users
  WHERE user_id = p_user_id;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    p_user_name := 'Unknown';
    p_create_date := NULL;
END get_user_info;
/

示例 5: 返回 Ref Cursor 的存储过程 (最常用,用于返回结果集) 创建一个类型:

CREATE TYPE USER_CURSOR_TYPE AS REF CURSOR;

然后创建存储过程:

Java如何调用Oracle存储过程?-图3
(图片来源网络,侵删)
CREATE OR REPLACE PROCEDURE get_users_cursor (p_cursor OUT USER_CURSOR_TYPE) AS
BEGIN
  OPEN p_cursor FOR
    SELECT user_id, user_name, email FROM users ORDER BY user_id;
END get_users_cursor;
/

(注意:此处的 users 表需要预先存在)


Java 代码调用示例

下面我们用 Java 代码来依次调用上面的存储过程。

基础模板

import java.sql.*;
import oracle.jdbc.OracleDriver;
public class CallOracleProcedure {
    // 数据库连接信息 (请替换为你的实际信息)
    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) {
        // 1. 加载驱动 (对于新版本JDBC驱动,通常可以省略此步)
        try {
            Class.forName("oracle.jdbc.OracleDriver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }
        // 使用 try-with-resources 确保 Connection, CallableStatement, ResultSet 自动关闭
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
            System.out.println("数据库连接成功!");
            // --- 在这里调用各种存储过程 ---
            // callSimpleProcedure(conn);
            // callInProcedure(conn);
            // callInOutProcedure(conn);
            // callOutProcedure(conn);
            callRefCursorProcedure(conn);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    // ... 下面将添加各个调用方法 ...
}

示例 1: 调用无参数的存储过程

private static void callSimpleProcedure(Connection conn) throws SQLException {
    String sql = "{call say_hello()}";
    try (CallableStatement cstmt = conn.prepareCall(sql)) {
        System.out.println("\n--- 调用无参数存储过程 ---");
        cstmt.execute();
        System.out.println("存储过程执行完毕。");
    }
}

示例 2: 调用带 IN 参数的存储过程

使用 setXXX() 方法来设置 IN 参数。

private static void callInProcedure(Connection conn) throws SQLException {
    String sql = "{call greet_user(?)}";
    try (CallableStatement cstmt = conn.prepareCall(sql)) {
        System.out.println("\n--- 调用带IN参数的存储过程 ---");
        cstmt.setString(1, "Alice"); // 第一个参数是 IN 类型,设置为 "Alice"
        cstmt.execute();
        System.out.println("IN参数存储过程执行完毕。");
    }
}

示例 3: 调用带 IN OUT 参数的存储过程

需要同时注册参数类型,并获取返回值。

private static void callInOutProcedure(Connection conn) throws SQLException {
    String sql = "{call process_number(?)}";
    try (CallableStatement cstmt = conn.prepareCall(sql)) {
        System.out.println("\n--- 调用带IN OUT参数的存储过程 ---");
        // 注册参数类型 (第一个参数是 IN OUT)
        cstmt.registerOutParameter(1, Types.NUMERIC); // Types.INTEGER 也可以
        // 设置 IN 值
        int initialValue = 10;
        c.setInt(1, initialValue);
        System.out.println("传入的初始值: " + initialValue);
        // 执行存储过程
        cstmt.execute();
        // 获取 OUT 值
        int resultValue = cstmt.getInt(1);
        System.out.println("从存储过程返回的值: " + resultValue);
    }
}

示例 4: 调用带 OUT 参数的存储过程

与 IN OUT 类似,但只需要注册和获取 OUT 值。

private static void callOutProcedure(Connection conn) throws SQLException {
    String sql = "{call get_user_info(?, ?, ?)}";
    try (CallableStatement cstmt = conn.prepareCall(sql)) {
        System.out.println("\n--- 调用带OUT参数的存储过程 ---");
        // 设置 IN 参数
        cstmt.setInt(1, 101); // 假设用户ID为101的用户存在
        // 注册 OUT 参数
        cstmt.registerOutParameter(2, Types.VARCHAR); // p_user_name
        cstmt.registerOutParameter(3, Types.DATE);   // p_create_date
        // 执行
        cstmt.execute();
        // 获取 OUT 参数
        String userName = cstmt.getString(2);
        Date createDate = cstmt.getDate(3);
        System.out.println("用户名: " + userName);
        System.out.println("创建日期: " + createDate);
    }
}

示例 5: 调用返回 Ref Cursor 的存储过程 (重点)

这是最实用的场景,用于从存储过程返回一个结果集。

private static void callRefCursorProcedure(Connection conn) throws SQLException {
    // 语法 {? = call ...} 用于函数,对于返回参数的存储过程,使用 {call ...}
    // Oracle 的 REF CURSOR 通过 OUT 参数传递
    String sql = "{call get_users_cursor(?)}";
    try (CallableStatement cstmt = conn.prepareCall(sql)) {
        System.out.println("\n--- 调用返回Ref Cursor的存储过程 ---");
        // 注册 OUT 参数,类型为 Oracle 的 OracleCursor 或标准的 Types.REF_CURSOR
        // 使用 Types.REF_CURSOR 是标准做法,但需要驱动支持
        cstmt.registerOutParameter(1, oracle.jdbc.OracleTypes.CURSOR);
        // 或者 cstmt.registerOutParameter(1, Types.REF_CURSOR); // 标准类型
        // 执行
        cstmt.execute();
        // 获取结果集 (OUT 参数实际上返回的是 ResultSet)
        // 注意:这里需要将 Object 强制转换为 ResultSet
        ResultSet rs = (ResultSet) cstmt.getObject(1);
        // 处理结果集
        System.out.println("ID\tName\tEmail");
        System.out.println("------------------------");
        while (rs.next()) {
            System.out.print(rs.getInt("user_id") + "\t");
            System.out.print(rs.getString("user_name") + "\t");
            System.out.println(rs.getString("email"));
        }
        // 关闭 ResultSet (如果使用 try-with-resources,会自动关闭)
        if (rs != null) rs.close();
    }
}

最佳实践和注意事项

  1. 使用 try-with-resources: 始终将 Connection, Statement, PreparedStatement, CallableStatement, 和 ResultSet 放在 try-with-resources 语句中,以确保它们在使用完毕后能被正确关闭,防止资源泄漏。
  2. 使用 PreparedStatement/CallableStatement: 永远不要使用字符串拼接来构建 SQL 语句,这会导致 SQL 注入风险。CallableStatement 就是处理存储过程调用的标准、安全的方式。
  3. 处理异常: 妥善处理 SQLException,记录错误日志,并向用户友好的提示。
  4. 驱动版本: 确保你使用的 JDBC 驱动版本与你的 Oracle 数据库版本兼容,Oracle 11g 数据库最好使用 ojdbc6.jar 或更高,Oracle 12c 及以上使用 ojdbc8.jarojdbc11.jar
  5. 连接池: 在生产环境中,不要每次都创建新的数据库连接,使用如 HikariCP, DBCP 等连接池来管理数据库连接,可以大大提高性能和稳定性。
  6. SQL 转义语法: 是标准的 JDBC 转义语法,用于表示参数,数据库驱动会将其转换为特定数据库(如 Oracle)所需的格式。
  7. OUT 参数的顺序: 在注册 OUTIN OUT 参数时,必须按照它们在存储过程定义中的顺序进行注册。

希望这份详细的指南能帮助你顺利地在 Java 中调用 Oracle 存储过程!

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