Java 存储过程
Java 存储过程,也称为 Java 存储函数/过程,是指将 Java 类的方法发布为 Oracle 数据库中的一个存储过程、函数或包,数据库本身充当了一个 Java 运行时环境,可以编译并执行你的 Java 代码。

主要用途
- 复用现有 Java 代码:如果你的业务逻辑已经用 Java 实现,无需重写,可以直接部署到数据库中。
- 访问外部资源:Java 代码可以访问文件系统、网络(HTTP 请求、调用 Web Service)、发送邮件等,这是纯 PL/SQL 难以做到的。
- 复杂计算和算法:对于科学计算、图像处理、加密解密等复杂算法,使用 Java 通常比 PL/SQL 更高效和方便。
- 集成第三方库:可以轻松引入各种成熟的 Java 开源库(如 Apache Commons, Log4j 等)。
- 提高性能:对于某些计算密集型任务,在数据库内部执行 Java 可以避免数据在应用服务器和数据库之间的网络传输。
实现步骤
创建一个 Java 存储过程通常遵循以下四个步骤:
- 编写 Java 源代码
- 编译 Java 源代码
- 将 Java 类加载到 Oracle 数据库
- 在数据库中创建调用该 Java 方法的 PL/SQL 封装器(Wrapper)
详细示例:创建一个简单的 Java 存储过程
我们将创建一个 Java 方法,它接收一个字符串,然后返回该字符串的 MD5 哈希值,由于 PL/SQL 本身没有内置的 MD5 函数,这是一个非常典型的使用场景。
步骤 1: 编写 Java 源代码
创建一个 Java 类,这个类必须是 public 的,并且你希望调用的方法也必须是 public 和 static 的。
Md5Generator.java

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Md5Generator {
/**
* 计算字符串的 MD5 哈希值
* @param input 输入字符串
* @return MD5 哈希值的十六进制字符串
* @throws NoSuchAlgorithmException
*/
public static String generateMd5(String input) throws NoSuchAlgorithmException {
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 class Md5Generator: 定义一个公共类。public static String generateMd5(String input): 定义一个公共静态方法,这是我们将在 Oracle 中调用的方法。throws NoSuchAlgorithmException: 方法声明可能抛出的异常,PL/SQL 可以捕获这个异常。
步骤 2: 编译 Java 源代码
你需要使用 JDK 的 javac 编译器来编译这个 .java 文件。
# 确保你的环境变量中配置了 JDK javac Md5Generator.java
编译成功后,会生成 Md5Generator.class 文件。
步骤 3: 将 Java 类加载到 Oracle 数据库
这一步是关键,你需要使用 Oracle 的 loadjava 实用工具将编译好的 .class 文件上传到数据库中。

loadjava 命令格式:
loadjava -user <username>/<password>@<database_connection_string> -resolve <path_to_class_file>
-user: 你的数据库用户名、密码和连接字符串。-resolve: 这是一个重要选项,它不仅加载类,还会立即编译并解析 Java 类的依赖项,确保其可以正常执行。强烈建议始终使用-resolve。
示例:
loadjava -user scott/tiger@localhost:1521/XE -resolve Md5Generator.class
执行后,你可以查询 USER_OBJECTS 视图来确认 Java 类是否已成功加载。
SELECT object_name, object_type, status FROM user_objects WHERE object_name = 'MD5GENERATOR';
你应该能看到类似下面的结果:
OBJECT_NAME OBJECT_TYPE STATUS
-------------------- -------------------- --------
MD5GENERATOR JAVA CLASS VALID
步骤 4: 在数据库中创建 PL/SQL 封装器
我们创建一个 PL/SQL 存储过程或函数,作为 Java 方法的“代理”,这个代理负责在 Java 和 PL/SQL 世界之间进行数据类型转换。
我们将创建一个函数,因为 Java 方法返回一个值。
create_md5_function.sql
CREATE OR REPLACE FUNCTION get_md5_hash(p_input_string IN VARCHAR2) RETURN VARCHAR2 AS -- 声明一个外部引用,指向我们加载的 Java 类和方法 LANGUAGE JAVA NAME 'Md5Generator.generateMd5(java.lang.String) return java.lang.String'; /
PL/SQL 封装器语法解析:
CREATE OR REPLACE FUNCTION get_md5_hash(...): 创建一个名为get_md5_hash的函数。p_input_string IN VARCHAR2: 定义一个名为p_input_string的输入参数,类型为VARCHAR2。RETURN VARCHAR2: 定义函数返回值的类型为VARCHAR2。AS: PL/SQL 代码块开始。LANGUAGE JAVA: 指明这是一个外部语言调用。NAME '...': 这是核心部分,它定义了 Java 方法的完整签名。'Md5Generator.generateMd5(java.lang.String) return java.lang.String'Md5Generator: Java 类名。.generateMd5: Java 方法名。(java.lang.String): Java 方法的参数类型。VARCHAR2在 PL/SQL 中会自动映射到java.lang.String。return java.lang.String: Java 方法的返回类型。VARCHAR2会自动映射到java.lang.String。
调用 Java 存储过程
你可以像调用任何普通的 PL/SQL 函数一样调用它了。
-- 调用函数
SELECT get_md5_hash('Hello, Oracle!') AS md5_hash FROM dual;
预期输出:
MD5_HASH
----------------------------------
6dd1379013f964b6a9d5e855f9a4b9c3
更复杂的示例:带有 IN OUT 参数和异常处理
让我们再看一个例子,演示如何处理 IN OUT 参数和 Java 抛出的异常。
Java 代码
StringFormatter.java
public class StringFormatter {
public static String formatText(String text, int maxLength, String suffix) {
if (text == null) {
throw new IllegalArgumentException("Input text cannot be null");
}
if (suffix == null) {
suffix = "...";
}
if (text.length() <= maxLength) {
return text;
}
return text.substring(0, maxLength) + suffix;
}
}
加载到数据库
loadjava -user scott/tiger@localhost:1521/XE -resolve StringFormatter.class
创建 PL/SQL 封装器(过程)
这次我们创建一个 PROCEDURE,因为它会修改一个传入的字符串。
CREATE OR REPLACE PROCEDURE format_string_proc (
p_text_in IN VARCHAR2,
p_max_len IN NUMBER,
p_suffix IN VARCHAR2,
p_text_out OUT VARCHAR2
)
AS
LANGUAGE JAVA
NAME 'StringFormatter.formatText(java.lang.String, int, java.lang.String) return java.lang.String';
/
注意,PL/SQL 的 NUMBER 类型会映射到 Java 的 int 或 java.math.BigDecimal,对于简单整数,int 即可。
调用并处理异常
DECLARE
v_original_text CLOB := 'This is a very long string that needs to be truncated for display purposes.';
v_formatted_text VARCHAR2(4000);
BEGIN
-- 正常调用
format_string_proc(v_original_text, 20, '...', v_formatted_text);
DBMS_OUTPUT.PUT_LINE('Formatted: ' || v_formatted_text);
-- 演示异常处理
BEGIN
format_string_proc(NULL, 10, '...', v_formatted_text);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Caught an exception: ' || SQLERRM);
END;
END;
/
预期输出:
Formatted: This is a very long...
Caught an exception: java.lang.IllegalArgumentException: Input text cannot be null
数据类型映射表
在编写 PL/SQL 封装器时,了解 PL/SQL 和 Java 之间的数据类型映射非常重要。
| PL/SQL 类型 | Java 类型 | 说明 |
|---|---|---|
VARCHAR2, CHAR |
java.lang.String |
最常用的映射。 |
NUMBER |
java.math.BigDecimal |
精确的十进制数。 |
NUMBER(p, s) |
java.math.BigDecimal 或 double |
p 是精度,s 是小数位数。 |
BINARY_INTEGER, PLS_INTEGER |
int |
整数。 |
DATE |
java.sql.Date |
日期部分。 |
TIMESTAMP |
java.sql.Timestamp |
日期和时间。 |
BLOB |
oracle.sql.BLOB |
二进制大对象。 |
CLOB |
oracle.sql.CLOB |
字符大对象。 |
BFILE |
oracle.sql.BFILE |
外部二进制文件。 |
RECORD, %ROWTYPE` |
N/A | 不能直接映射,需要创建一个 Object Type 来对应 Java 的类。 |
重要注意事项
- 权限:执行
loadjava的用户需要有JAVAUSERPRIVILEGE权限,要创建外部过程,用户还需要CREATE PROCEDURE权限。 - 性能:对于非常简单的逻辑,直接使用 PL/SQL 会更快,因为避免了 Java 解释的开销,Java 存储过程最适合处理复杂逻辑和外部交互。
- 状态管理:Java 代码在数据库中是无状态的,每次调用都是独立的,不要依赖静态变量来保存会话状态。
- 调试:调试 Java 存储过程比调试 PL/SQL 复杂,通常需要查看数据库的 Java 日志或使用专门的调试工具。
- 安全性:在数据库中执行 Java 代码会带来安全风险,确保你加载的 Java 代码来源可靠,并遵循最小权限原则。
通过以上步骤和示例,你应该能够掌握在 Oracle 数据库中创建和使用 Java 存储过程的基本方法,这是一种非常强大的技术,可以极大地扩展 Oracle 数据库的能力边界。
