前言
当我们加载并发布了使用 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 工具,可以在当前连接下输入上面的代码并执行这些代码:
如果需要打印 System.out
和 System.err
输出的内容,需要将 【查看】–> 【DBMS 输出】连接到当前数据库。
注意:所有过程或函数需要在 【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 文件即可:
发布 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 目录同时发布过程或函数。
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】下面查看:
导入 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
子类异常的方式。