这个过程的核心是 Oracle JVM (Java Virtual Machine),Oracle 数据库内置了一个完整的、与数据库集成的 JVM,这使得 Java 代码可以像数据库对象(如表、存储过程)一样被管理和调用。

下面我将分步详细讲解整个过程,包括原理、步骤、完整示例和最佳实践。
核心原理
整个过程可以概括为以下几个步骤:
- 加载 Java 代码:将你的 Java 源代码(
.java文件)或编译后的字节码(.class文件)加载到 Oracle 数据库中。 - 编译 Java 代码:在数据库内部,使用
loadjava命令或 SQL 语句CREATE JAVA ...来编译加载的 Java 代码,并将其存储为JAVA$类型的对象。 - 创建数据库调用入口:创建一个 Oracle 的 存储过程 或 函数,该过程/函数被声明为
LANGUAGE JAVA,并指定要调用的 Java 类的完整路径和方法签名。 - 执行:像调用普通的 Oracle 存储过程一样,调用你刚刚创建的这个入口点,Oracle 会将参数在 SQL 数据类型和 Java 数据类型之间进行转换,并执行 Java 方法。
详细步骤与示例
假设我们有一个需求:在 Oracle 存储过程中,调用一个 Java 方法来计算一个字符串的 MD5 哈希值。
第 1 步:编写 Java 代码
我们编写一个 Java 类,它包含一个静态方法 calculateMD5,该方法接收一个 String 并返回一个 String。

MD5Utils.java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
/**
* 计算字符串的 MD5 哈希值
* @param input 输入字符串
* @return MD5 哈希字符串 (32位小写)
* @throws NoSuchAlgorithmException
*/
public static String calculateMD5(String input) throws NoSuchAlgorithmException {
if (input == null) {
return null;
}
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
// 将字节数组转换为十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
注意:
- 方法必须是
public static的,因为 Oracle 调用 Java 静态方法。 - 数据库和 Java 之间的数据类型有对应关系(见下文)。
- Java 代码抛出异常,Oracle 存储过程会将其转换为
ORA-29531: Java 执行错误或类似的错误。
第 2 步:将 Java 代码加载到数据库
你有两种主要方式可以将 Java 代码加载到数据库中。
使用 loadjava 命令 (推荐)
loadjava 是一个命令行工具,功能强大,可以处理依赖关系,并且可以覆盖或重新加载已存在的 Java 对象。

-
编译 Java 代码 (在你的本地机器上):
javac MD5Utils.java
这会生成
MD5Utils.class文件。 -
加载到数据库: 假设你的数据库用户是
SCOTT,密码是tiger,服务名是orcl。loadjava -u scott/tiger@orcl -v -r MD5Utils.class
-u: 用户名/密码@连接字符串-v: 详细输出-r: 递归加载,Java 类引用了其他类,也会一并加载MD5Utils.class: 要加载的字节码文件
使用 SQL*Plus 或 SQL Developer
你也可以直接在 SQL 环境中创建 Java 源代码对象。
-- 使用 SQL*Plus 或 SQL Developer 连接到数据库
-- 切换到 SCOTT 用户
-- CREATE OR REPLACE AND COMPILE JAVA SOURCE NAME "MD5Utils.java" AS
-- 然后粘贴 Java 源代码内容
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAME "MD5Utils.java" AS
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
public static String calculateMD5(String input) throws NoSuchAlgorithmException {
if (input == null) {
return null;
}
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
/
CREATE JAVA SOURCE NAME ... AS: 定义一个 Java 源代码对象。- 执行该语句。
AND RESOLVE: 表示 Oracle 会尝试解析并编译所有依赖项,功能类似loadjava -r。
执行成功后,你可以查询 USER_OBJECTS 视图来确认 Java 对象是否已创建:
SELECT object_name, object_type, status FROM user_objects WHERE object_name LIKE '%MD5%'; -- 应该能看到 MD5Utils JAVA SOURCE 和 MD5Utils JAVA CLASS
第 3 步:创建 PL/SQL 调用入口 (存储过程/函数)
这是最关键的一步,我们将创建一个 PL/SQL 函数,它将作为桥梁来调用我们刚刚加载的 Java 方法。
CREATE OR REPLACE FUNCTION get_md5_hash(p_input_string IN VARCHAR2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'MD5Utils.calculateMD5(java.lang.String) return java.lang.String'; /
语法解析:
CREATE OR REPLACE FUNCTION ...: 声明一个 PL/SQL 函数。get_md5_hash: 函数名。p_input_string IN VARCHAR2: PL/SQL 参数,类型为VARCHAR2。RETURN VARCHAR2: 函数返回 PL/SQL 的VARCHAR2类型。AS LANGUAGE JAVA: 关键字,表示后续内容是 Java 方法的声明。'MD5Utils.calculateMD5(java.lang.String) return java.lang.String': 方法签名。MD5Utils: Java 类名。calculateMD5: Java 方法名。(java.lang.String): Java 方法的参数类型,Java 的String在这里是java.lang.String。return java.lang.String: Java 方法的返回类型。
第 4 步:调用和测试
你可以像调用任何内置函数一样调用你的 get_md5_hash 函数。
-- 直接在 SQL 查询中调用
SELECT get_md5_hash('hello world') AS md5_result FROM dual;
-- 结果:
-- MD5_RESULT
----------------------------------
-- 5d41402abc4b2a76b9719d911017c592
-- 在另一个 PL/SQL 块中调用
DECLARE
v_hash VARCHAR2(32);
BEGIN
v_hash := get_md5_hash('Oracle Java Call');
DBMS_OUTPUT.PUT_LINE('The MD5 hash is: ' || v_hash);
END;
/
输出:
The MD5 hash is: 7a4b2e1c8d3f0a6b5c9d2e1f0a7b8c9d
数据类型映射
这是调用 Java 时最容易出错的地方,Oracle PL/SQL 数据类型和 Java 数据类型之间有明确的对应关系。
| PL/SQL 数据类型 | Java 数据类型 | 说明 |
|---|---|---|
CHAR, VARCHAR2 |
java.lang.String |
字符串 |
NUMBER |
java.math.BigDecimal |
精确的十进制数,推荐用于所有 NUMBER |
NUMBER(p, s) |
java.math.BigDecimal |
|
BINARY_INTEGER |
java.lang.Integer |
整数 |
FLOAT, BINARY_FLOAT, BINARY_DOUBLE |
java.lang.Double |
浮点数 |
DATE, TIMESTAMP |
java.sql.Date, java.sql.Timestamp |
日期/时间 |
RAW, BLOB |
byte[], oracle.sql.BLOB |
二进制数据 |
BOOLEAN |
java.lang.Boolean |
布尔值 |
高级用法与最佳实践
调用实例方法 (非静态方法)
如果需要调用一个非静态方法,你需要先创建 Java 类的实例。
Java 代码:
public class GreetingUtil {
public String sayHello(String name) {
return "Hello, " + name + "!";
}
}
PL/SQL 函数创建:
CREATE OR REPLACE FUNCTION get_greeting(p_name IN VARCHAR2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'GreetingUtil.new() return oracle.sql.ORAData; GreetingUtil.sayHello(java.lang.String) return java.lang.String'; /
GreetingUtil.new() return oracle.sql.ORAData;: 这部分告诉 Oracle 如何构造这个类的实例,对于简单的对象,Oracle 会自动处理实例化,有时你需要指定一个实现oracle.sql.ORAData接口的包装器,对于大多数简单情况,Oracle 的隐式转换是足够的,可以简化为:CREATE OR REPLACE FUNCTION get_greeting(p_name IN VARCHAR2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'GreetingUtil.sayHello(java.lang.String) return java.lang.String';
在这种情况下,Oracle 会假定你有一个默认的构造函数来创建实例,如果遇到问题,则需要显式声明构造函数。
处理 Java 异常
Java 方法抛出异常,Oracle 会将其包装成一个错误,你可以在 PL/SQL 中使用 EXCEPTION 块来捕获它。
DECLARE
v_result VARCHAR2(100);
BEGIN
-- 假设 calculateMD2 方法不存在,会抛出 NoSuchAlgorithmException
v_result := get_md5_hash('test'); -- 这个是正常的
-- v_result := calculate_md2('test'); -- 假设这个函数会调用一个不存在的方法
DBMS_OUTPUT.PUT_LINE(v_result);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An error occurred: ' || SQLERRM);
END;
/
输出会类似于:
An error occurred: ORA-29532: Java call terminated by uncaught Java exception: java.security.NoSuchAlgorithmException: MD2 not available
性能考虑
- 启动开销:首次调用 Java 方法时,JVM 需要加载和初始化类,这会有一定的性能开销,后续调用会快很多。
- 会话状态:Java 代码在数据库的 JVM 中运行,可以与会话状态交互(通过静态变量缓存数据),但要小心线程安全问题,因为一个数据库会话可能对应多个 JVM 线程。
- 资源消耗:频繁或复杂的 Java 调用会消耗数据库服务器(CPU、内存)的资源,影响数据库的整体性能,应谨慎使用,避免将计算密集型任务放在 Java 中。
安全性
- 执行 Java 代码的权限需要授予用户,用户需要
CREATE PROCEDURE权限,并且需要被授予JAVAUSERPRIV角色,或者直接被授予EXECUTEON Java 对象的权限。GRANT EXECUTE ON MD5Utils TO PUBLIC; -- 授予所有用户执行权限
- 从 Oracle 10g 开始,JVM 的默认权限级别是
RESOURCES,限制了它对文件系统、网络等的访问,如果需要更高权限(如读写文件),需要配置JServerPermission,这通常需要DBA权限。
通过以上步骤,你就可以成功地在 Oracle 存储过程中调用 Java 代码了,这个过程的核心在于:
- 加载并编译 Java 代码 到数据库。
- 创建一个 PLSQL 包装器(函数/过程),并使用正确的
LANGUAGE JAVA语法和 方法签名 来链接 Java 代码。 - 理解数据类型的映射,确保参数和返回值正确。
- 注意性能和安全性,合理地使用这一强大功能。
