杰瑞科技汇

Oracle如何调用Java存储过程?

  1. 编写 Java 代码:创建一个 Java 类,其中包含一个或多个 public static 方法。
  2. 将 Java 代码加载到 Oracle 数据库:使用 loadjava 命令或 SQL*Plus 将编译后的 .class 文件上传到数据库中,并将其创建为模式对象(JAVA SOURCE, JAVA CLASS, JAVA RESOURCE)。
  3. 在 Oracle 中创建 PL/SQL 封装:创建一个 PROCEDUREFUNCTION 声明,它作为 PL/SQL 和 Java 代码之间的桥梁,这个声明会告诉 Oracle 如何调用你的 Java 方法。

下面我们通过一个完整的、分步的示例来详细说明这个过程。

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

示例:创建一个 Java 存储过程,用于计算字符串的 MD5 哈希值

MD5 算法在标准 Java 中有实现,但在 PL/SQL 中没有内置函数,因此这是一个很好的用例。

第 1 步:编写 Java 代码

我们创建一个 Java 类,它包含一个 public static 方法来计算 MD5 哈希值。

MD5Utils.java

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
    /**
     * 计算字符串的 MD5 哈希值,并以十六进制字符串形式返回。
     * 这个方法必须是 public static 的,才能被 Oracle PL/SQL 调用。
     *
     * @param inputStr 要哈希的输入字符串
     * @return MD5 哈希值的十六进制字符串
     * @throws NoSuchAlgorithmException 如果找不到 MD5 算法
     */
    public static String getMD5(String inputStr) throws NoSuchAlgorithmException {
        // MessageDigest 是线程不安全的,每次调用都创建新实例是安全的做法
        MessageDigest md = MessageDigest.getInstance("MD5");
        // 计算 MD5 哈希,返回一个字节数组
        byte[] messageDigest = md.digest(inputStr.getBytes());
        // 将字节数组转换为符号整型
        BigInteger no = new BigInteger(1, messageDigest);
        // 将整型转换为十六进制字符串
        String hashtext = no.toString(16);
        // 为了保证 32 位,前面补零
        while (hashtext.length() < 32) {
            hashtext = "0" + hashtext;
        }
        return hashtext;
    }
}

关键点

Oracle如何调用Java存储过程?-图2
(图片来源网络,侵删)
  • public static:Oracle 只能调用 publicstatic 的方法,实例方法(非静态)无法被直接调用。
  • 异常处理:Java 方法可以抛出异常,在 PL/SQL 中调用时,通常使用 EXCEPTIONS 块来捕获和处理这些异常。
  • 依赖:此代码仅使用 Java 标准库,无需额外 JAR 包。

第 2 步:编译 Java 代码并加载到 Oracle 数据库

你需要先将 .java 文件编译成 .class 文件,然后将其加载到数据库中。

使用命令行工具 loadjava (推荐)

  1. 编译 Java 文件

    javac MD5Utils.java

    这会生成 MD5Utils.class 文件。

  2. 使用 loadjava 上传: 假设你的数据库用户是 scott,密码是 tiger,数据库服务名是 orcl

    loadjava -u scott/tiger@orcl -v -r MD5Utils.class
    • -u scott/tiger@orcl:指定数据库连接。
    • -v:详细模式,可以看到加载过程的详细信息。
    • -r:以 RESOUCE 形式加载,这是最常用的方式,会创建 JAVA SOURCE, JAVA CLASS, JAVA RESOURCE 三种对象。

*使用 SQLPlus / SQL Developer**

  1. 获取 .class 文件的 Base64 编码: 你需要一个工具(如 Python 脚本或在线工具)将 MD5Utils.class 文件转换为 Base64 字符串,这通常比较繁琐,loadjava 是首选。

  2. *在 SQLPlus 中执行 LOADJAVA 命令**:

    -- 连接到数据库
    -- CONNECT scott/tiger@orcl
    -- 执行 loadjava 命令
    -- 注意:你需要提供完整的路径到 .class 文件
    -- HOST loadjava -u scott/tiger@orcl -v -r /path/to/your/MD5Utils.class

加载成功后,你可以查询 USER_OBJECTS 视图来确认:

SELECT object_name, object_type, status
FROM user_objects
WHERE object_name = 'MD5UTILS';

你应该能看到 JAVA CLASS, JAVA SOURCE 等类型的对象。

第 3 步:创建 PL/SQL 封装过程

我们创建一个 PL/SQL 过程,它作为 Java 方法的“代理”。

CREATE OR REPLACE PROCEDURE

CREATE OR REPLACE PROCEDURE compute_md5_hash (
    p_input_string   IN  VARCHAR2,
    p_md5_hash       OUT VARCHAR2,
    p_error_msg      OUT VARCHAR2
)
AS
    LANGUAGE JAVA
    NAME 'MD5Utils.getMD5(java.lang.String) return java.lang.String';
BEGIN
    -- 尝试调用 Java 方法
    -- Java 方法抛出异常,控制权会自动跳转到下面的 EXCEPTIONS 块
    p_md5_hash := MD5_UTILS_PKG.get_md5_hash(p_input_string);
    -- 如果执行到这里,说明成功
    p_error_msg := NULL;
EXCEPTION
    WHEN OTHERS THEN
        -- 捕获所有在 Java 中抛出的异常
        p_error_msg := 'Java Error: ' || SQLERRM;
        p_md5_hash := NULL;
END compute_md5_hash;
/

代码解释

  • CREATE OR REPLACE PROCEDURE ...:标准的 PL/SQL 过程创建语法。
  • p_input_string IN VARCHAR2:输入参数,类型是 VARCHAR2,Oracle 会自动将其转换为 Java 的 java.lang.String
  • p_md5_hash OUT VARCHAR2:输出参数,用于接收 Java 方法的返回值,Java 的 String 会自动转换为 PL/SQL 的 VARCHAR2
  • p_error_msg OUT VARCHAR2:自定义的输出参数,用于传递错误信息。
  • AS LANGUAGE JAVA ...:这是关键部分,它告诉 Oracle 这是一个 Java 调用。
  • NAME '...':指定要调用的 Java 方法的完整签名,格式为 '类名.方法名(参数类型) 返回类型'
    • 'MD5Utils.getMD5(java.lang.String) return java.lang.String':这表示调用 MD5Utils 类中的 getMD5 方法,它接受一个 java.lang.String 参数,并返回一个 java.lang.String
  • EXCEPTION WHEN OTHERS THEN:这是处理 Java 异常的标准方式,Java 方法抛出 NoSuchAlgorithmException 或任何其他异常,PL/SQL 会捕获它,并将错误信息(SQLERRM)存入 p_error_msg 参数。

注意:上面的代码中,我直接调用了 MD5_UTILS_PKG.get_md5_hash(p_input_string),这不对,应该是直接赋值:

-- 正确的调用方式
p_md5_hash := MD5Utils.getMD5(p_input_string); -- 错误,PL/SQL不能直接这样调用
-- 正确的方式是通过一个 PL/SQL 函数或过程来封装,如下面这个函数版本。

让我们创建一个更常用的函数版本,这样在 SQL 语句中也可以使用:

CREATE OR REPLACE FUNCTION

CREATE OR REPLACE FUNCTION get_md5_hash_func (
    p_input_string IN VARCHAR2
) RETURN VARCHAR2
AS
    LANGUAGE JAVA
    NAME 'MD5Utils.getMD5(java.lang.String) return java.lang.String';
/

这个函数版本更简洁,因为它只有一个输入参数和一个返回值,它不便于处理异常,Java 方法抛出异常,整个函数调用会失败。

最佳实践:创建一个包来封装 为了更好地组织代码和处理异常,推荐使用包。

MD5_UTILS_PKG.sql

CREATE OR REPLACE PACKAGE MD5_UTILS_PKG AS
    -- 公开的函数,用于在 SQL 和 PL/SQL 中调用
    FUNCTION get_md5_hash(p_input_string IN VARCHAR2) RETURN VARCHAR2;
    -- 公开的存储过程,带有详细的错误输出
    PROCEDURE compute_md5_hash(
        p_input_string   IN  VARCHAR2,
        p_md5_hash       OUT VARCHAR2,
        p_error_msg      OUT VARCHAR2
    );
END MD5_UTILS_PKG;
/
CREATE OR REPLACE PACKAGE BODY MD5_UTILS_PKG AS
    -- 内部函数,实际调用 Java
    -- 注意:函数名不要和包名冲突
    FUNCTION call_java_md5(p_input_string IN VARCHAR2) RETURN VARCHAR2
    AS
        LANGUAGE JAVA
        NAME 'MD5Utils.getMD5(java.lang.String) return java.lang.String';
    -- 包体实现
    FUNCTION get_md5_hash(p_input_string IN VARCHAR2) RETURN VARCHAR2
    IS
        l_hash VARCHAR2(32);
    BEGIN
        l_hash := call_java_md5(p_input_string);
        RETURN l_hash;
    EXCEPTION
        WHEN OTHERS THEN
            -- 如果发生错误,返回 NULL 或一个特定的错误标识
            RETURN 'ERROR: ' || SQLERRM;
    END get_md5_hash;
    PROCEDURE compute_md5_hash(
        p_input_string   IN  VARCHAR2,
        p_md5_hash       OUT VARCHAR2,
        p_error_msg      OUT VARCHAR2
    )
    IS
    BEGIN
        p_md5_hash := call_java_md5(p_input_string);
        p_error_msg := NULL;
    EXCEPTION
        WHEN OTHERS THEN
            p_error_msg := 'Java Error: ' || SQLERRM;
            p_md5_hash := NULL;
    END compute_md5_hash;
END MD5_UTILS_PKG;
/

第 4 步:调用 Java 存储过程

一切准备就绪,我们可以调用它了!

调用带输出参数的存储过程

DECLARE
    l_input VARCHAR2(100) := 'Hello, Oracle Java!';
    l_hash  VARCHAR2(32);
    l_err   VARCHAR2(4000);
BEGIN
    MD5_UTILS_PKG.compute_md5_hash(
        p_input_string => l_input,
        p_md5_hash     => l_hash,
        p_error_msg    => l_err
    );
    DBMS_OUTPUT.PUT_LINE('Input: ' || l_input);
    DBMS_OUTPUT.PUT_LINE('MD5 Hash: ' || l_hash);
    DBMS_OUTPUT.PUT_LINE('Error: ' || l_err);
    -- 测试一个会触发异常的输入(如果 Java 代码有 bug)
    -- MD5_UTILS_PKG.compute_md5_hash(p_input_string => NULL, p_md5_hash => l_hash, p_error_msg => l_err);
    -- DBMS_OUTPUT.PUT_LINE('Error for NULL input: ' || l_err);
END;
/

预期输出

Input: Hello, Oracle Java!
MD5 Hash: 6f1b2c3a4d5e6f7a8b9c0d1e2f3a4b5c
Error:

调用函数(更简洁)

SELECT 
    'Hello, Oracle Java!' AS input_string,
    MD5_UTILS_PKG.get_md5_hash('Hello, Oracle Java!') AS md5_hash
FROM dual;

预期输出

INPUT_STRING          MD5_HASH
---------------------- ----------------------------------------
Hello, Oracle Java!   6f1b2c3a4d5e6f7a8b9c0d1e2f3a4b5c

(注意:上面的哈希值是示例,实际运行结果会不同)


重要注意事项和最佳实践

  1. 性能:首次调用 Java 方法时,Oracle 需要加载和编译 Java 类,可能会有轻微的性能开销,后续调用会快很多,对于高频调用的简单逻辑,纯 PL/SQL 可能更快。
  2. 调试:调试数据库中的 Java 代码比调试普通 Java 应用困难,可以使用 DBMS_JAVA.SET_OUTPUT(10000); 将 Java 的 System.out.println 重定向到 SQL*Plus 的 DBMS_OUTPUT,但这只适用于简单的调试,复杂的调试通常需要使用专门的工具。
  3. 安全性:将代码加载到数据库(JAVA 权限)和执行外部代码(JAVA 权限)具有很高的权限,应谨慎授予这些权限,并确保你的 Java 代码是安全的,没有 SQL 注入或命令注入的风险。
  4. 依赖管理:如果你的 Java 代码需要外部的 JAR 文件(调用 Apache Commons),你需要使用 loadjava 将 JAR 文件也加载到数据库中。
    loadjava -u scott/tiger@orcl -v -r my-library.jar

    确保你的类路径在 Java 代码中正确设置。

  5. 数据类型映射:了解 Oracle PL/SQL 数据类型和 Java 数据类型之间的映射关系非常重要。
    • VARCHAR2 / CHAR <-> java.lang.String
    • NUMBER <-> java.math.BigDecimal
    • DATE / TIMESTAMP <-> java.sql.Date / java.sql.Timestamp
    • BLOB / BFILE <-> oracle.sql.BLOB / oracle.sql.BFILE
  6. 内存管理:在 Java 存储过程中创建大量对象时,要注意 Oracle 的内存管理,Java 堆和 PGA 内存是共享的,不当的内存使用可能导致数据库性能下降甚至崩溃,确保及时释放不再使用的对象。
分享:
扫描分享到社交APP
上一篇
下一篇