|
外部存储过程
使用外部存储过程
通过引用外部程序可以获得超出 SQL PL 范围的强大功能。通过使用一个外部存储过程(即使用 Java 语言或 C# 等编程语言编写的存储过程),可以处理外部数据源,或者执行数据库之外的外部动作。
与外部 UDF 相似,外部存储过程可以定义为以 NOT FENCED 或 FENCED 模式运行。同样,使用 NOT FENCED 过程的优点是可以提高性能。但是,如果没有适当地编写这种过程,就会出现内存漏洞,这种内存漏洞会覆盖与 DB2 引擎相关联的内存,因此会产生非常恶劣的影响。
创建外部存储过程
清单 25 显示了注册外部存储过程的简化语法图: 清单 25. 注册外部存储过程的简化语法图
>>-CREATE PROCEDURE--procedure-name----------------------------->
>--+--------------------------------------------------------+--->
'-(--+----------------------------------------------+--)-'
| .-,----------------------------------------. |
| V .-IN----. | |
'---+-------+--+----------------+--data-type-+-'
+-OUT---+ '-parameter-name-'
'-INOUT-'
>--*--+-------------------------+--*---------------------------->
'-SPECIFIC--specific-name-'
.-DYNAMIC RESULT SETS 0--------. .-MODIFIES SQL DATA-.
>--+------------------------------+--*--+-------------------+--->
'-DYNAMIC RESULT SETS--integer-' +-NO SQL------------+
+-CONTAINS SQL------+
'-READS SQL DATA----'
.-NOT DETERMINISTIC-. .-CALLED ON NULL INPUT-.
>--*--+-------------------+--*--+----------------------+--*----->
'-DETERMINISTIC-----'
.-OLD SAVEPOINT LEVEL-.
>--+---------------------+--*--LANGUAGE--+-C-----+--*----------->
'-NEW SAVEPOINT LEVEL-' +-JAVA--+
+-COBOL-+
+-CLR---+
'-OLE---'
>--EXTERNAL--+----------------------+--*------------------------>
'-NAME--+-'string'---+-'
'-identifier-'
.-FENCED------------------------.
>--+-------------------------------+--*------------------------->
+-FENCED--*--+-THREADSAFE-----+-+
| '-NOT THREADSAFE-' |
| .-THREADSAFE-. |
'-NOT FENCED--*--+------------+-'
.-EXTERNAL ACTION----. .-INHERIT SPECIAL REGISTERS-.
>--+--------------------+--+---------------------------+--*----->
'-NO EXTERNAL ACTION-'
>--PARAMETER STYLE--+-DB2GENERAL---------+--*------------------->
+-DB2SQL-------------+
+-GENERAL------------+
+-GENERAL WITH NULLS-+
+-JAVA---------------+
'-SQL----------------'
|
这个语法图中的很多子句在之前的一些小节中已经讨论过。本节着重介绍外部存储过程特有的一些主要部分。
LANGUAGE: 表明过程是用什么语言编写的。可用于编写外部过程的语言有 C、Java 语言、COBOL、CLR 和 OLE。
EXTERNAL NAME: 指定用户编写的用来实现所定义过程的代码的名称。该字符串的格式取决于所指定的语言。本教程简要地讨论在使用 C、Java 和 CLR 语言时该字符串的格式。
EXTERNAL ACTION: 决定过程是否可以执行外部动作。
PARAMETER STYLE: 该子句用于指定过程传递参数和返回值的约定。可用的选项有:
DB2GENERAL: 使用为 Java 方法定义的参数传递约定。只有在使用 LANGUAGE JAVA 时,才能指定该选项。
DB2SQL: 当指定该选项时,可以将提供附加诊断信息的附加参数传递给过程。只有在指定 LANGUAGE C、COBOL、CLR 或 OLE 时,才能指定该选项。
GENERAL: 导致过程接收 CALL 语句中指定的参数,而不使用 SQLDA 结构。只有在指定 LANGUAGE C、COBOL 或 CLR 时,才能指定该选项。
GENERAL WITH NULLS: 类似于 GENERAL 选项,但是还将另一个参数传递给过程:该参数是由 null 指示符组成的一个向量,CALL 语句的每个参数对应一个 null 指示符。
JAVA: 导致过程使用遵从 Java 语言和 SQLJ 例程规范的参数传递约定。 以单条目数组的形式传递 IN/OUT 和 OUT 参数,以便于返回值。只有当使用 LANGUAGE JAVA 时,才能指定该选项。
SQL: 当指定该选项时,CALL 语句中包括提供附加诊断信息的附加参数。只有当使用 LANGUAGE C、COBOL、 CLR 或 OLE 时,才能指定该选项。
要了解关于 CREATE PROCEDURE 语句中这些子句和其他子句的更多信息,请参阅 DB2 文档。
EXTERNAL NAME 子句选项
外部存储过程一个最重要的强制性子句就是 EXTERNAL NAME 子句。该子句控制 DB2 可以在哪里寻找编译后的过程代码。在本节中,您将了解在 C、Java 和 CLR 语言中,该子句的字符串是如何工作的。
C 语言
指定的字符串是库名和库中的过程,数据库管理器调用它来执行所创建的过程。在执行 CREATE PROCEDURE 语句时,这个库(以及库中的过程)不需要已经存在。但是,当过程被调用时,这个库和库中的过程必须存在,并且能够从数据库服务器上访问到。对于 C 语言,指定的字符串格式如下:
>>-'--+-library_id-------+--+------------+--'------------------>|
'-absolute_path_id-' '-!--proc_id-'
|
下面分别描述它的不同组成部分:
library_id: 包含该过程的库的名称。数据库管理器按如下方法寻找库:
- 在 UNIX 系统上,如果指定的
library_id 为 myfunc,数据库管理器在 /u/production 目录下运行,当指定 FENCED 时,数据库管理器在 /u/production/sqllib/function/myfunc 中寻找过程库;当指定 NOT FENCED 时,数据库管理器在 /u/production/sqllib/function/unfenced/myproc 中寻找过程库。
- 在 Windows 操作系统上,数据库管理器在
LIBPATH 或 PATH 环境变量指定的目录路径中寻找过程库。
absolute_path_id: 标识包含该过程的文件的完整路径名。
! proc_id: 标识要调用的过程的入口名称。! 是库 ID 与过程 ID 之间的定界符。例如,!proc8 将指示数据库管理器在 absolute_path_id 指定的位置寻找库,并使用那个库中的 proc8 入口。如果这个字符串的格式不对,就会返回错误。
Java 语言
指定的字符串包含可选的 JAR 文件标识符、类标识符和方法标识符,数据库管理器调用它来执行所创建的过程。当执行 CREATE PROCEDURE 语句时,类标识符和方法标识符不需要已经存在。如果指定了一个 jar_id,那么当执行 CREATE PROCEDURE 语句时,它必须已经存在。同样的,当过程被调用时,类标识符和方法标识符必须已经存在,并且可以从数据库服务器上访问到。否则会返回错误。对于 Java 语言,该字符串的格式为:
>>-'--+----------+--class_id--+-.-+--method_id--'--------------><
'-jar_id :-' '-!-'
|
下面分别描述它的不同组成部分:
jar_id: JAR 集合在被安装到数据库中时获得的 JAR 标识符。它可以是一个简单的标识符,例如 myJar,也可以是一个模式限定的标识符,例如 mySchema.myJar。
class_id: Java 对象的类标识符。如果这个类属于一个包,则类标识符部分还必须包括完整的包前缀。例如,如果使用 myPacks.StoredProcs,则 Java 虚拟机将在 .../myPacks/StoredProcs/(对于 Windows,则为 ...\myPacks\StoredProcs\)目录中寻找类。
method_id: 被调用的 Java 类中的方法的名称。
CLR
指定的字符串表示 .NET 组合件(库或可执行文件)、组合件中的类以及类中的方法,数据库管理器将调用它来执行所创建的过程。当执行 CREATE PROCEDURE 语句时,模块、类和方法不需要已经存在。但是,当过程被调用时,模块、类和方法必须存在,并且可以从数据库服务器上访问到,否则会返回错误。对于 .NET 组合件(库或可执行文件),该字符串格式如下:
>>-'--assembly--:--class_id--!--method_id--'-------------------><
|
下面分别描述它的不同组成部分:
assembly: 类所在的 DLL 或其他文件的标识符。这里必须指定文件扩展名(例如 .dll)。如果没有提供完整的路径名称,那么该文件必须在 DB2 安装路径(例如 C:\sqllib\function)的函数目录中。如果该文件在安装路径下的函数目录中的一个子目录中,那么可以在文件名之前提供这个子目录,而不必指定完整的路径。例如,如果安装目录为 C:\sqllib\function\myprocs\mydotnet.dll,那么只需为组合件指定 myprocs\mydotnet.dll。该参数的大小写敏感性与文件系统相同。
class_id: 指定在给定的组合件中,要调用的方法所在的类的名称。如果这个类在一个名称空间中,那么除了类名之外,还需指定完整的名称空间。例如,如果类 EmployeeClass 在名称空间 MyCompany.ProcedureClasses 中,那么必须指定 MyCompany.ProcedureClasses.EmployeeClass。该参数是大小写敏感的。
method_id: 指定在给定类中要调用的方法。该参数是大小写敏感的。
现在,您已经回顾了创建外部存储过程的语法,接下来可以看看创建 Java 存储过程的一个例子。
Java 存储过程例子
我们来看一个创建 Java 存储过程的例子。 清单 26 包含该过程的 Java 源代码: 清单 26. 一个简单的 Java 存储过程的代码
import java.sql.*;
public class Simple
{
public static void SimpleInsert (String input, String[] outMsg) throws
SQLException, Exception
{
int errorCode;
try
{
// get caller's connection to the database
Connection con = DriverManager.getConnection("jdbc:default:connection");
// assumes the following table exists in the database
// CREATE TABLE db2admin.mydata (col1 CHAR(3))
String query = "INSERT INTO db2admin.mydata (col1) VALUES (?)";
PreparedStatement pstmt = con.prepareStatement(query);
pstmt.setString(1, input);
pstmt.executeUpdate();
outMsg[0] = "The update was successful.";
}
catch (SQLException sqle)
{
errorCode = sqle.getErrorCode();
throw new SQLException( errorCode + " FAILED" );
}
}
}
|
该过程的代码包含在一个名为 SimpleInsert() 的 Java 方法中,而这个 Java 方法又包含在一个名为 Simple 的 Java 类中。(因此存放代码的文件名为 Simple.java)。清单 26 中的代码没有什么特别之处,它是标准的含有 JDBC 方法调用的 Java 代码。该过程有两个参数,都是字符串型的。第一个参数是输入参数,第二个参数是输出参数。注意,输出参数被定义为一个 String 数组。该过程用于注册的 PARAMETER STYLE JAVA 属性要求 OUT 和 INOUT 参数定义为数组。还需注意的是该过程继承调用者到数据库的连接的方式,它使用的连接字符串不同于标准的 JDBC 应用程序。
通过使用安装的系统上的或者 DB2 附带的 JDK,可以像下面这样编译该过程:
编译后会生成一个类文件。然后,可以将该文件转移到 sqllib/function 目录(DB2 寻找存储过程可执行文件的默认位置),或者转移到您所选择的其他位置(只要在 CREATE PROCEDURE command 中使用这个定制路径)。对于这个例子,使用前一种目录,将类文件复制到 sqllib/function 目录中。
将类文件放在适当的位置后,便可以使用 CREATE PROCEDURE 语句将该函数注册到 DB2 中,如 清单 27 所示: 清单 27. 注册 Java 存储过程
CREATE PROCEDURE simple_insert (IN input CHAR(3), OUT outMsg VARCHAR(254))
SPECIFIC simple1
DYNAMIC RESULT SETS 0
DETERMINISTIC
LANGUAGE JAVA
PARAMETER STYLE JAVA
FENCED
THREADSAFE
MODIFIES SQL DATA
EXTERNAL NAME 'Simple!SimpleInsert'
|
注册函数之后,就可以调用该函数,如 清单 28 所示。注意,问号(?)用于表示输出参数。当从命令行调用存储过程时,都是用问号表示输出参数。 清单 28. 调用 Java 存储过程
CALL simple_insert('ABC', ?)
Value of output parameters
--------------------------
Parameter Name : OUTMSG
Parameter Value : The update was successful.
Return Status = 0
Result from DB2 sample database:
SELECT * FROM db2admin.mydata
COL1
----------------------------
ABC
1 record(s) selected.
|
这个非常简单的 Java 存储过程实际上也可以很容易地编写成 SQL 存储过程。但是,看过这个例子之后,就应该清楚,如果使用主机编程语言的所有功能和 API,可以编写出多么复杂的外部存储过程。 |