Oracle 8i开始,Oracle数据库就全面引入了实用的Java虚拟机 – Oracle JVMOracleJava之间的这种紧密关系,使得一个非常重要的技术,出现在Oracle开发人员面前:Java存储过程。通过Java存储过程,开发人员在创建数据库应用的时候,就可以充分利用Java提供的各种优势。本文将针对这个越来越受欢迎的技术进行讨论。其目标读者是,初懂OracleJava开发人员和略知JavaOracle PL/SQL开发人员。在强调Java存储过程的好处之后,我将示例说明在应用开发中,如何使用它们。

 

Java存储过程的好处

在很多情况下使用Java存储过程是很有意义的。鉴于Java当前受欢迎的程度,这种情况是完全可能的,即越来越多的开发人员对于Java的熟练程度要好于PL/SQLJava存储过程的出现,使得Java的开发人员可以用自己喜欢的语言来开发存储过程。对于有经验的PL/SQL开发人员,则可以利用Java语言的各种优点,来扩展数据库应用的功能。同时,Java使得编写独立于数据库的代码成为可能。更有意思的是,它允许你重用你已经存在的代码,从而大幅度地提高开发效率。

 

正如你将看到的, PL/SQLJava 可以在同一个应用中和谐共处,因此,大可不必非此即彼地选择其中之一。 PL/SQL是一种针对Oracle数据库,经过高度优化的、优秀的过程语言,Java应用在Oracle数据库中运行也具有很好的扩展性。除此之外,通过OracleJVM来执行Java程序,可以充分利用高效的内存回收技术和线程管理方面的能力。

 

Java存储过程,Step by Step

简单地说,Java存储过程就是Java类,以schema对象的形式存储,通过调用规范(call specifications)OracleSQLPL/SQL可以对其访问。我们将看到,调用规范就是一些简单的PL/SQL声明,这些声明包装(wrap)’了存储在数据库中的Java方法。开发Java存储过程,有四个必须的步骤。下面我们逐一来看看这些步骤。

 

#1. 编写Java

第一步的妙处就是,它基本上和Oracle数据库没有什么关系。你只是简单地用你最喜欢的IDE,比如OracleJdeveloper,去开发一些Java类。如果想被用作存储过程,Java方法必须是publicstatic的。

 

在移入Oracle数据库之前,你可以自由地编写、编译甚至对Java代码进行单元测试。事实上,对于通常的应用,这是一个很好的方式,因为这可以充分利用IDE的特性,诸如调试和代码生成。如果你想用OracleJVM来编译Java代码,后面将要说到的loadjava这个工具,将为你做这些工作。

 

下面的代码列出了一个简单的Java - EmpManager。它包只含了一个简单的,用于向数据库插入一个emp(员工)记录的方法。

 

import java.sql.*;

import oracle.jdbc.*;

 

public class EmpManager {

 

   //Add an employee to the database.

   public static void addEmp(int emp_id, String emp_f_name,

      String emp_l_name,float emp_salary, int dept_id) {

 

      System.out.println("Creating new employee...");

 

      try {

         Connection conn =

            DriverManager.getConnection("jdbc:default:connection:");

 

         String sql =

            "INSERT INTO emp " +

            "(emp_id,emp_f_name,emp_l_name,emp_salary,dept_id) " +

            "VALUES(?,?,?,?,?)";

         PreparedStatement pstmt = conn.prepareStatement(sql);

         pstmt.setInt(1,emp_id);

         pstmt.setString(2,emp_f_name);

         pstmt.setString(3,emp_l_name);

         pstmt.setFloat(4,emp_salary);

         pstmt.setInt(5,dept_id);

         pstmt.executeUpdate();

         pstmt.close();

         }

      catch(SQLException e) {

         System.err.println("ERROR! Adding Employee: "

           + e.getMessage());

         }

   }

}

 

到目前为止一切如常。在这个方法中,数据库链接URL"jdbc:default:connection:"。在写要在Oracle数据库中运行的Java代码时,可以利用一个特殊的服务器端的JDBC驱动程序。这个驱动程序使用用户缺省的链接,并提供对数据库的最快访问速度。

 

#2. 加载Java

我们的Java类将要编程一个真正的schema对象,所以必须将它移入数据库。为此,Oracle提供了一个命令行工具 – loadjava。这个loadjava工具提供了一个SQL CREATE JAVA等语句必要的接口,并且也可以用于将Java相关的文件加载到数据库中。

 

由于现在还没有编译EmpManager.java,我们也可以让loadjava在加载的过程中顺便做完编译工作,这个可以通过打开 –resolve开关来做到。

 

$ loadjava –u scott/tiger –v –resolve EmpManager.java

–resolve 开关外, -v 将指示loadjava输出详细的(verbose)反馈, -u 用于指定数据库用户和密码。因为我们让loadjava编译源文件,因此源文件和类文件都将是SCOTT schema的成员。

 

我们可以通过一个对USER_OBJECTS的查询来验证编译和加载的状态,如果正确,那么状态将是’VALID’的。

 

SELECT object_name, object_type, status

FROM user_objects WHERE object_type LIKE ‘JAVA%’;

 

object_name object_type status

 

EmpManager JAVA CLASS  VALID

EmpManager JAVA SOURCE VALID

 

反之,如果编译失败,可以在视图USER_ERRORS中看到具体的错误。

 

如果选择用IDE编译,只需要简单将编译后的class文件加载即可,源文件可以保存在版本控制工具的文件系统中。Loadjava工具接受后缀为.sqlj (sqlj 源文件).properties.ser.jar.zip等文件。当后缀为.jar zip的文件时,Oracle将自动解压,并将各成员存储成单个的schema对象。

 

在继续讨论之前,加载过程中有一个至关重要的组件值得一提:Oracle JVM resolver。典型地,一个JVMclasspath来定位Java类,以便被其他程序使用。在数据库中加载Java时,resolver来完成这样的任务。

 

可以简单地认为resolver就是Oracle版本的classpathOracle将核心Java类存储与PUBLIC schema中。PUBLIC,就像你自己的schema一样,将被自动地引入缺省的resolver中。不过,如果需要引用另外一个schema中的类,那就必须提供你自己的’resolver规则(spec)’,这个可以通过加上-resolver开关来做到。比如,loadjava –u scott/tiger@test –resolve –resolver “((* SCOTT) (* PUBLIC) (* ADMIN))” ,指明了,在决定class依赖关系时,SCOTT schemaPUBLIC ADMIN3schema将被搜索。

 

#3. 发布(publish)Java

3步是发布Java类。Java类必须发布,以便可以直接从SQL或者PL/SQL直接访问。通过为其创建、编译一个调用规则来发布一个Java类。调用规则,通常被称为call spec或者PL/SQL包装器(wrapper),它将Java方法的参数、返回值类型映射成Oracle SQL的数据类型。下面给出的是addEmp方法的调用规则:

 

CREATE OR REPLACE PROCEDURE add_emp (emp_id NUMBER,emp_f_name VARCHAR2,

  emp_l_name VARCHAR2, emp_salary NUMBER, dept_id NUMBER)

  AS LANGUAGE JAVA

  NAME 'EmpManager.addEmp(int, java.lang.String, java.lang.String,

                          float, int)';

/

 

add_emp过程为Java EmpManager.addEmp方法提供了一个SQL接口。Java方法,如果有相关包名,则必须使用包含包名的全名,并且,在开发一个调用对则时,Java对象如String也必须使用全名。

 

作为一般性的规律,一个Java方法如果没有返回值,则被封装成过程,反之,则被封装成函数。现在,在EmpManager中,我们考虑加入第2Java方法,用于查询一个指定部门的员工人数:

 

// 查询一个指定部门的员工人数

public static int getEmpCountByDept(int dept_id) {

 

   Connection conn =

      DriverManager.getConnection("jdbc:default:connection:");

 

   String sql = "SELECT COUNT(1) FROM emp WHERE dept_id = ?";

   int cnt = 0;

 

   //Code here to add ResultSet value to cnt, trap SQLException, etc.

 

   return cnt;

}

 

它的调用规则指定其返回类型为NUMBER

 

CREATE OR REPLACE FUNCTION get_emp_count_by_dept (dept_id NUMBER)

   RETURN NUMBER AS LANGUAGE JAVA

   NAME 'EmpManager.getEmpCountByDept(int) return int';

/

 

缺省地,就像标准的PL/SQL 过程一样,这些代码(译注:指上面的调用规则)只要有INVOKER权限就可以运行他们,换言之,当前用户有权执行它们。通过增加关键字AUTHID DEFINER,可以让其他用户以创建者的身份来执行它们。

 

一旦执行,调用规则,则将其他文件作为SCOTT schema的成员加入数据库。

 

#4. 调用过程

我们已经开发、装载并发布了Java类。最后一步就是执行他们。缺省地,Java的输出被写入跟踪文件(trace files)DBMS_JAVA包,Oracle提供的用于管理服务器端Java的工具,可以将输出重定向到SQL*Plus

 

SQL> SET SERVEROUTPUT ON

SQL> CALL dbms_java.set_output(2000);

 

现在,只要执行,Java的输出会显示在SQL *PLUS中,

 

SQL> EXECUTE add_emp(1,'Joe', 'Smith',40000.00,1);

Creating new employee...

 

PL/SQL procedure successfully completed.

 

正如你所看到的,从调用者的角度,调用Java存储过程和调用PL/SQL的存储过程或存储函数,并没有明显的区别。

 

VARIABLE x NUMBER;

CALL get_emp_count_by_dept(1) INTO :x;

Getting Number of Employees for Dept...

 

Call completed.

 

PRINT x

 

X

----------

1

  

SQLException类有getErrorCode() getErrorMessage()两个方法用于报错处理。Java存储过程中的任何未被捕获的异常将产生ORA-29532告警,Java调用也会被随之终止。至于如何处理异常,则不同的应用可能采用不同的方式。addEmp方法简单地捕获并显示异常。视图插入一个员工到一个无效的部门,将会收到一个错误消息。

 

SQL> execute add_emp(2,'Tom', 'Jackson', 45000.00,2);

Creating new employee...

ERROR! Adding Employee : ORA-02291: integrity constraint

                         (OPS$AK4353.FK_DEPT_ID) violated -

                         parent key not found

 

由于有从PL/SQL来调用Java的需要,自然就会想到也有从Java调用PL/SQL的需要,这个可以通过在Java方法中使用CallableStatement对象,而轻易做到。

 

CallableStatement cstmt = conn.prepareCall("{my_plsql_proc}");

 

因此,可以创建一个无缝的环境,使得PL/SQL轻易调用Java,反之亦然。

 

一种使用方案

理解Java存储过程越好,将对你的开发时间越有帮助。一个通常的方法就是,在需要考虑数据库访问效率时(译注:建立数据库连接很耗时间和资源),就会使用PL/SQL。现在Java就可以更加轻松地做到这点。开发各个类,遵照必要的调用规则即可。

 

或许有这样的可能,比如一个数据库应用需要与操作系统文件和目录进行交互。Oracle UTL_FIL这个包提供访问操作系统文件的功能极为有限,而Java则有丰富得多的File IO能力,允许开发人员删除文件,增加目录等等。所以为什么不利用它呢? 再比如,命令行PL/SQL程序的使用者,也可能将job参数存放在一个配置文件中,你可以编写Java方法来读取这些参数:

 

   public static String readFile (String usrFile)  {

     String fileStr = new String();

     try {

        File file = new File(usrFile);

        FileReader fr = new FileReader(file);

        LineNumberReader lnr = new LineNumberReader(fr);

        ...

        ...

     }

     catch(Exception e) {

        ...

     }

     return fileStr;

   }

 

然后,PL/SQL包会为你写的这个或者任何其他FILE IO方法定义调用规则。

 

CREATE OR REPLACE PACKAGE my_java_utils IS

 

FUNCTION read_file (file VARCHAR2) RETURN VARCHAR2;

 

END my_java_utils;

/

 

CREATE OR REPLACE PACKAGE BODY my_java_utils IS

 

FUNCTION read_file (file VARCHAR2) RETURN VARCHAR2

  AS LANGUAGE JAVA

  NAME 'MyJavaUtils.readFile(java.lang.String) return java.lang.String';

 

END my_java_utils;

/

 

PL/SQL过程可以调用read_file这个Java存储过程, 而它则以一个文件的数据作为输入。同时使用两个世界最好的方法,开发人员可以轻易开发出鲁棒的数据库应用。必须注意到,在这个特定的方案中,需要有访问文件系统的一定权限。更进一步的信息,可以查询OracleJava安全方面的文档。

 

总结

Oracle提供了很多Java存储过程方面的文档(译注:可以到OTN上找)。如果需要,你可以下载本文示例代,以便做更加细致的试验。

 

在这篇文章中,给出了有关Java存储过程的总的看法,同时示例说明了如何实现它们。Java存储过程是一种很强大的技术,值得一个Oracle开发人员在构建数据库应用时,考虑使用之。


Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐