如何通过 PL/SQL 调用 Java 方法

Posted by mingfer on September 23, 2019

前言

当我们加载并发布了使用 Java 实现的存储过程(包括过程和函数)之后,我们可以通过 SQL 语句调用它们。本文将说明如何在各自的上下文中 SQL 如何去调用 Java 实现的存储过程,以及在 SQL 中如何处理 Java 存储过程中产生的异常。

相关文章参见:

Hello World

我们首先通过一个简单的打印 Hello World 字符串的过程和函数来说明如何使用 Java 存储过程。在下面的 Java 代码中我们实现了两个不同的用于打印 Hello World 的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloWorld {

    // 过程
    public static void sayHelloWorld() {
        System.out.println("Hello World.");
    }

    // 函数
    public static String sayHelloWorld(String text) {
        System.out.println("Hello World.");
        return text;
    }

}

注意:所有的 Java 存储过程方法都应该是 static 修饰的静态方法。

重定向输出

在 Oracle 数据库中默认的输出设备是一个 trace file 而不是用户屏幕。也就是说 Java 中 System.out 和 System.err 的输出会输出到当前的 trace file 中。我们可以重定向输出到当前屏幕,并通过 dbms_java.set_output() 设置缓冲区大小:

1
2
SQL> SET SERVEROUTPUT ON
SQL> CALL dbms_java.set_output(2000);

缓冲区的默认大小和最小值均为 2000 字节,最大值为 1,000,000 字节。我们可以通过如下方式更改缓冲区的大小:

1
2
SQL> SET SERVEROUTPUT ON SIZE 5000
SQL> CALL dbms_java.set_output(5000);

配置之后当存储过程运行完毕输出将会打印到屏幕上面。

上面是使用 sqlplus 命令行工具的示例,如果使用 Oracle SQL Developer 工具,可以在当前连接下输入上面的代码并执行这些代码:

1569207655476

如果需要打印 System.out 和 System.err 输出的内容,需要将 【查看】–> 【DBMS 输出】连接到当前数据库。

1569216787385

注意:所有过程或函数需要在 【SQL 工作表】下执行,才会打印 System.out 和 System.err 输出的内容到【脚本输出】和【DBMS 输出】。

加载 Java 存储过程

可以使用 loadjava 工具将 Java 类源码,编译后的 .class 文件或资源文件加载到数据库实例中,它们将被存储为 Java Schema Object。当我们在命令行或应用中运行 loadjava 命令的时候,可以指定解析器在内的一些可选项。

在下面的例子中,loadjava 在命令行使用 JDBC OCI 驱动连接 Oracle 数据库实例,它需要我们在连接的时候指定用户和密码。默认情况下,导入的 Java 过程函数将存储在登录用户的 schema 下面。

1
2
3
$ javac HelloWorld.java
$ loadjava -user HR HelloWorld.class
Password: password

当我们调用 Java 存储过程中提供的方法时,解析器会去查找支持的 Java 类。默认的解析器会现在当前用户的 schema 中查找,然后在系统的 schema (包含所有 Java 的核心库)中查找。必要情况下,我们可以指定需要的解析器。

如果使用 Oracle SQL Developer ,直接在 Java 目录导入 Java 文件即可:

1569209720935

发布 Java 存储过程

一般来说,无返回的 Java 方法发布为 PL/SQL 过程,有返回的 Java 方法发布为 PL/SQL 函数。在下面的例子中,我们通过 sqlplus 连接到 Java 存储过程所在的用户,并发布一个 Java 方法。

1
2
3
4
5
6
SQL> connect HR
Enter password: password

SQL> CREATE FUNCTION say_hello_world RETURN VARCHAR2
2 AS LANGUAGE JAVA
3 NAME 'HelloWorld.sayHelloWorld(java.lang.String) return java.lang.String';

如果使用 Oracle SQL Developer ,可以选择在过程和函数目录发布过程或函数,也可以在 Package 目录同时发布过程或函数。

1569217725527

Package 实现:

CREATE OR REPLACE 
PACKAGE PACKAGE_HELLO_WORLD AS 

  /* TODO enter package declarations (types, exceptions, methods etc) here */ 
  PROCEDURE say_hello_world;
  
  FUNCTION say_hello_world(TEXT IN VARCHAR2) RETURN VARCHAR2;
  
END PACKAGE_HELLO_WORLD;

-- package body
CREATE OR REPLACE
PACKAGE BODY PACKAGE_HELLO_WORLD AS

  PROCEDURE say_hello_world AS
    LANGUAGE JAVA NAME 'HelloWorld.sayHelloWorld()';

  FUNCTION say_hello_world(TEXT IN VARCHAR2) RETURN VARCHAR2 AS
    LANGUAGE JAVA NAME 'HelloWorld.sayHelloWorld(java.lang.String) return java.lang.String';

END PACKAGE_HELLO_WORLD;

过程实现:

CREATE OR REPLACE PROCEDURE PROC_SAY_HELLO_WORLD AS 
LANGUAGE JAVA NAME 'HelloWorld.sayHelloWorld()';

函数实现:

CREATE OR REPLACE FUNCTION FUNC_SAY_HELLO_WORLD 
(
  TEXT IN VARCHAR2 
) RETURN VARCHAR2 AS 
LANGUAGE JAVA NAME 'HelloWorld.sayHelloWorld(java.lang.String) return java.lang.String';

使用 Jar 包中的方法

既然可以使用 Java 中的方法,那么打包成 Jar 包的 Java 方法也是可以被 SQL 调用的。但是需要注意,不同版本 Oracle 支持的 JDK 版本是不一样的。

功能 Oracle 版本
JDK1.5 11.1+
JDK1.6 12.1+
JDK1.7 12.1+
JDK1.8 12.2+

导入 Jar 包

导入 Jar 包或导入 Java 源码,.class 文件都需要使用 loadjava 工具。该工具在 Oracle 完整版本的安装包中有提供(注意不是 instant client),下载地址参见 Oracle 11g Realease 2 Client 。

使用下面的命令导入 .java,.jar 或 .class 文件:

1
$ loadjava -u user/password@database -r -o -verbose xxx.jar

注意:

  • -u 指定的 database 名称可以在 tnsnames.ora 中取得
  • 指定了 -r 选项或去解析每一个类的依赖情况,如果没有提前导入其依赖的类会导致报错。
  • -o 是使用本地的 Oracle OCI 驱动连接 Oracle 数据库。

已经导入的内容可以使用 dropjava 进行卸载:

1
$ dropjava -u user/password@database -o -verbose xxx.jar

查看导入的内容

使用 sqlplus 可以通过下面的 SQL 语句进行查看:

1
SQL> select * from user_java_classes

在 Oracle SQL Developer 中可以通过上述语句查看,也可以刷新数据库之后在 【Java】下面查看:

1569316044152

导入 jar 包之后,将 jar 包里面需要使用的存储过程映射成对应的 PL/SQL 过程或函数即可。这部分内容在上面的教程中已经详细说明了,这里不再赘述。

异常处理

Java 异常是一个包含名称和继承关系的对象,也就是说我们可以把子类异常对象转换为他的父类异常。所有的 Java 异常都实现了 toString() 方法,该方法会返回异常类的全限定名称和相关的异常信息,该信息通常在异常的构造函数中填写。

当在 SQL 语句中运行 Java 存储过程的时候,所有该存储过程抛出的异常都应该是 java.sql.SQLException 的子类。这个类提供了 getErrorCode() 和 getMessage() 方法,用于返回 Oracle 数据库的错误码和错误信息。

如果在 SQL 或 PL/SQL 调用的 Java 存储过程抛出了未被抓取的异常,Oracle 将提示下面的错误信息:

1
ORA-29532 Java call terminated by uncaught Java exception

这就是提示所有未抓取的异常,包括非 java.sql.SQLException 子类异常的方式。