Oracle8i Java开发指南

 

一、Oracle8i中的JAVA支持介绍

本章向Oracle用户介绍JAVA语言。Oracle PL/SQL开发者习惯于开发与SQL数据紧密结合服务器端应用。现在JAVA开发者也拥有这个世界了,同时也获得了Oracle的规模可扩展性和性能。本章从Oracle8i JAVA文档出发,然后检查Oracle的全部JAVA策略,该策略被设计用来交付企业级Java应用开发的计算解决方案。该策略的关键元素是Oracle扩展JAVA服务器平台—JServer的引入。有了这个背景之后,我们探索JAVA语言以便进入下章的JServer实现规范的讨论。

Oracle8i JAVA文档总览

本指南是Oracle8iJAVA开发者的起点。Oracle8iJServer的一个关键目标就是提供一个工业标准的JAVA实现,并达到Oracle用户期望的健壮性、规模可扩性、性能。本指南摘要介绍Oracle8i中JAVA编程独有的特性,包括的Oracle JVM概貌,解释JAVA程序如何从这些特性中获益。使用JAVA和Oracle8i,可以建立并发布强大的基于服务器的为所有客户共享的JAVA应用。这些应用可以轻易通过网络分发,并在任何支持JAVA的计算机上提供对Oracle8i数据的访问。

Oracle8iJAVA开发者应该熟悉Oracle8i的JDBC产品,因为它为JAVA程序提供基本的SQL数据存取,尽管Oracle8i规范扩展了JAVA标准。另外,可以开发使用内嵌SQLJ语句访问数据的JAVA应用更加容易。SQLJ使用一个预处理器,JAVA写的:-),来翻译内嵌SQL语句为标准的JDBC风格的程序。JDBC和SQLJ都是工业标准。Oracle8i的JDBC开发指南和参考及Oracle8i SQLJ开发指南与参考文档记载了这些工业标准和Oracle8i规范增强的细节。Oracle8i JPublisher用户指南记录了JPublisher,它提供一个简单方便的工具建立JAVA程序已经存在的Oracle8i关系数据库表。

如果你是PL/SQL程序员想探索JAVA,那么你应该读Oracle8i JAVA Stored Procedure Developer's Guide。 一个JAVA存储是用JAVA写的在服务器上执行的程序,正如PL/SQL存储过程一样。你可以使用SQL*PLUS之类的产品直接引用它,也可以用一个触发器间接引用,并且可以从任何Net8客户访问它—OCI、PRO*、JDBC或SQLJ。Oracle8i JAVA存储过程开发者指南解释了如何用JAVA编写存储过程 ,如何从PL/SQL中访问它们及如何从JAVA中访问PL/SQL函数。当然,你可以独立于PL/SQL使用JAVA开发强有力的程序,Oracle8i提供了JAVA语言和虚拟机的完全兼容的现实。

Oracle8i企业豆和CORBA开发者指南与参考描述了如何从企业豆的开放分布对象技术中获益,该技术包括它的Aurora/ORB和EJB功能。Aurora/ORB和EJB搭建起强大的基于标准的框架和工具帮助你建立可扩张JAVA应用,这些应用在Intranet或Internet上提供对Oracle数据的无缝事务访问。

Oracle的JAVA策略概览

Oracle为企业应用开发者建立了end-to-end的JAVA解决方案—— 一个高性能、可扩张的基础以发布JAVA应用和与该基础捆绑在一起的一大套JAVA工具。这个total solution由客户端和服务器端编程接口、支持JAVA开发的工具和最为重要的—JVM与ORACLE数据库服务器集成而成。所有这些产品提供了一个生产效率极高的编程模式、优化过的性能和可扩张性、100%与JAVA标准兼容。总而言之,它们便利ORACLE8I及其JAVA服务器平台—JSERVER—成为Internet计算的数据库和JAVA平台。

编程接口

ORACLE向JAVA开发者提供两种不API以存取SQL数据—JDBC和SQLJ。两种API在客户和服务器端都可用,这样就可以在任何一个地方发布相同的代码。

JDBC驱动

JAVA核心类库只提供一个JDBC API。而JDBC被设计成允许供应商提供驱动程序以提供对特定数据库的必要个性化。ORACLE交付JSERVER时带有三种不同的JDBC驱动。

可以使用JDBC THIN DRIVER编写100%纯JAVA应用或小应用来访问ORACLE SQL 数据。JDBC瘦驱动特别适合与基于浏览器的应用程序和APPLET,因为你可以从网页动态下载该驱动,正如其它任何JAVA Apllet。作为一个替代,你可以使用JDBC OCI驱动,它在客户端或中间层存取ORACLE特有的本地代码库,相对JDBC提供更为丰富的功能和更好的性能,代价是明显更大的尺寸。最后,JSERVER引入了一种JDBC SERVER DRIVER,供在ORACLE8I服务器内执行的JAVA代码使用。它允许在服务器的JVM里执行的JAVA应用程序通过JDBC访问本地定义的数据(即,在同一台机器和同一个进程)。由于直接使用底层ORACLE RDBMS库,且在JAVA代码与SQL数据间没有网络连接干预,所以能使性能进一步提高。通过在服务器上支持相同的JAVA-SQL接口,ORACLE8I让你在发布时无须修改代码。

SQLJ—内嵌在JAVA里的SQL

JDBC提供了从JAVA访问SQL数据的低层API。它引入了映射SQL等价物的一些JAVA类。ORACLE与包括IBM、TANDEM、SYBASE、SUN在内的其它供应商一道开发了一种直接在JAVA程序中嵌入SQL语句的标准方法——SQLJ。该工作为比JDBC更简单更高生产效率的API制定了一个新的标准(ANSI X.3.135.10-1998)。用户用这种高层API编写应用程序,然后使用一个标准的预处理器将程序翻译成标准的使用JDBC调用的JAVA源代码。SQLJ提供了一种简单但强大的方式来开发客户端和中间层JAVA数据库应用。可以在存储过程、触发器、JServer环境中的方法或EJB/CORBA中使用。另外,可以在程序混合使用JDBC和SQLJ。

SQLJ翻译器是一个JAVA程序,将JAVA源代码中内嵌的SQL语句翻译成基于JDBC的纯JAVA代码。因为JServer提供一个完全的JAVA环境,你不仅可以在客户端编译SQLJ程序以在JServer上执行,也可以直接在服务器上编译它们。ORACLE8I坚持Internet标准允许你选择适合自己的开发风格。

JServer环境

除了上面讨论的服务器端API支持之外,JServer环境包括:

ORACLE8I服务器上的JVM 执行 JAVA程序。在本章和第2章,我们将讨论什么是JVM的一些细节及为什么ORCLE的Aurora JVM提供了最具可扩张性的企业级实现。

JAVA的一个吸引人的地方是它的通用性和不断增长的JAVA程序员数目。现存的基于ORACLE的服务器应用几乎都只使用PL/SQL。ORACLE8I允许你用JAVA来编写并执行各种程序,诸如存储过程、触发器和对象类型方法,也允许从JAVA中引用已有的PL/SQL程序或从PL/SQL中引用JAVA程序。这个方案保护并撬动现有投资,同时揭示了基于JAVA的Internet计算的好处与机会。

在对传统RDBMS存储过程支持之外,JServer带有内建CORBA2.0兼容ORB(Inprise VisiBroker for Java 3.2)及对EJB的支持。ORB允许用任何语言开发的程序通过IIOP直接与Oracle8i数据库通信,该协议由OMG定义。对于100%纯JAVA应用,EJB是在JServer上发布基于构件的支持事务与安全的应用的标准架构。CORBA和EJB允许你将JAVA构件和应用逻辑在客户端、中间层和数据库服务器上进行分布。

开发工具

在Oracle8i服务器中引入JAVA允许你使用许多JAVA集成开发环境。JServer坚持JAVA兼容性和Internet开放标准与协议,确保你的100%纯JAVA程序发布到Jserver后能正常工作。Oracle8i交付JServer时带有许多工具,均用JAVA写成,以简化JAVA服务器应用的开发与发布。Oracle的JDeveloper还带有许多专门设计来简化JAVA存储过程与EJB发布的特性。

作为编程语言的JAVA

JAVA在早许多年前就已做为一种OOP语言出现。该语言是在SUN开发的,其语言设计者综合多种其它之前已有的语言形成了JAVA。例如,虚拟机的概念,用以实现平台独立性的根基,是Smalltalk和LISP早已倡导的。自动存储管理技术,最常见的是自动垃圾收集,也是LISP和Smalltalk的特性。不仅借用这些OOP先行者的成果,JAVA设计者也选择了与来自于C的语法和强制类型保持一致。这使得该语言易于被已有的大量C程序学习同时真正保持了应用程序的OO和率。

JAVA与OOP技术

从一般与JAVA语言上对OOP的细节讨论放在本书后部。除了完整的语言规范,还有许多优秀的文本可以从书店或Internet中得到。事实上,参考材料的增加和JAVA类库与框架的迅速发展正是它成功的关键因素。

译者注:以下是对Java与OOP的讨论,以前看过一点,俺就懒得写了,有兴趣去看Thinking in Java好了,中译本叫JAVA编程思想。

虚拟机

与任何其它高级语言一样,JAVA编译成低级机器指令。在JAVA中,这些指令称为字节码。许多其它语言,如C,编译成机器相关的指令;即Intel Petium或HP处理器的特有指令。JAVA编译成一种标准的平台无关的虚拟机字节码集合。虚拟机是作为特定平台优化的单独的程序编写的。当你在一台SUN工作站上的Netscape浏览器里使用JAVA,虚拟机—一个为solaris平台编写的可执行程序—执行JAVA字节码。JServer的JVM是用C写的。

当你开发一个JAVA程序时,会使用以JAVA语言写成的预定义核心类库。JAVA核心类库从逻辑上分成包,包提供某个领域使用公用的函数,如基本语言支持(java.lang)、i/o(java.io、网络访问(java.net)等等。总而言之,虚拟与核心类库提供了一个平台,JAVA程序员可以在平台上进行开发并相信任何硬件任何操作系统只要支持Java都能运行写好的程序。即一次编写,到处运行。

SUN建立了JAVA语言和虚拟机的公用规范。另外SUN提供了一个虚拟机实现兼容性测试集以判断它们是否遵循规范,这就是JCK。Oracle的虚拟机实现完全遵守JCK。

Java Server术语

支持Java的Oracle8i数据库称为Oracle JServer平台。我们称Oracle8i的JVM为Aurora。作为执行JAVA代码的用户,你必须在服务器上建立一个会话(session)。这里使用的“会话”这个字与标准的Oracle(或其它数据库服务器)用法是一样的。一个会话典型是,尽管不是必须的,在一段时间里将一个用户连接到服务器上。当用户在一个会话里引起一段JAVA代码执行时我们称之为一个调用(call)。可以用多种方法初始一个调用。它可以在一个SQL客户程序执行一个顶极JAVA存储过程时开始。你可以通过引用一个CORBA对象的方法或通过PL/SQL程序调用一些JAVA代码来初化它。在所有情况下,一个调用开始,一些由JAVA、SQL、PL/SQL组合成的代码被执行完毕,调用结束。

ORACLE8I坚持标准的JAVA语言语义,包括运行时动态类加载。在一个调用中,可以将对象保存在不同类的静态字段里,并且可以指望这个状态在下次调用时有效。事实上,JAVA程序的整个状态是私有,并在整个会话期里有效。

在绝大多数JAVA开发环境里,JAVA源代码、二进制码和资源是作为文件系统的文件存储的。在这种情况下人们通常将源码称为.java文件而编译过的JAVA(二进制)为.class文件。资源文件是任何数据文件如.properties文件,运行时被装入或使用的这些文件是由文件系统内部处理的。 当执行JAVA时,规定一个CLASSPATH,它是一组包含java文件的文件系统的目录。java也支持用Zip或Jar文件组合这些文件。

在JServer环境中,源码、类和资源是保留在Oracle8i数据库里的。我们称之为Java Schema Object,有三种:source, class, resource。服务器上没有什么.java/.class/.properies文件,只有JAVA模式对象。代替CLASSPATH,你可以使用一个resolver以描述一个或多个schema供定义源/类/资源Java模式对象。

我们用术语call和session来个总结。尽管这些术语不是JAVA术语,它们是用于Oracle8i JServer平台的服务器术语。Aurora内存管理器通过你的会话安排Java程序状态。JServer使用Oracle数据库的Java模式对象保存Java源/类/资源。可以用一个resolver描述当在服务器上执行时java如何定位源/类/资源。

会有些示例使用所有这些概念。参考file:///C:/Oracle/Ora81/doc/java.815/a64682/03writed.htm#。

2、企业级JAVA平台

本章讨论使得JAVA语言合适企业应用开发的特性,焦点开始放在语言的一般特性上,然后描述JServer实现的特殊之处。

JAVA语言的关键属性

如下:

简单。JAVA是一种简单的语言,比用于服务器应用开发的其它语言都简单,这是由于它的对象模型的强制一致性。大量的标准类库集给所有平台的JAVA开发者带来强有力的工具。

可移植性。JAVA由于它的跨平台可移植性而广泛存在。在JAVA中编写平台无关代码不仅可能的,而且简单。服务器应用,天生不支持GUI,使其比一般JAVA应用能跨越更多实际平台。

自动存储管理。JVM在程序执行期间自动进行所有的内存分配和收回。JAVA程序员不可以显示alloc或free内存。相反,得依赖虚拟机执行这些bookkeeping操作,当建立新对象时分配内存,当对象不再被引用时回收内存。后者也被称为垃圾收集。

强类型。在使用JAVA变量之前,必须申明其保存的对象的类。

无指针。尽管JAVA保留了大量C语法风味,但它不支持直接指针或指针操纵。所有参数以传引用方式传递,而不是传值。JAVA没有提供C的低级直接指针取存,从而消除了主要的BUG、内存破坏和内存泄漏问题源。

异常处理。异常处理是JAVA语言的特性……

……

真无聊,还是去看点有意思的吧。

3、在Oracle8i里编写Java应用程序

本章描述编写、安装、发布JServer应用的基本步骤。

JServer基础

JServer是内嵌在RDBMS内的,这引入了大量新概念。

这一节介绍在ORACLE8I里建立并发布JAVA应用的基本知识。然后是一个简单的Java应用程序的例子。

调用与会话

参见第一章的介绍。

JServer的sessions与传统ORACLE的完全一样,除了他们限于JAVA的对象内存。一个SESSION包含JAVA静态变量引用的所有对象的生命周期,所有这些对象引用的对象的,……。从C/S交互角度看,每个JServer会话拥有自己的私有对象内存。从你的角度看,它表现得象每一个Session有一个分离独立的JVM,然而本实现则比表面暗示的效率高得多。

会话的创建和生命周期是与编写应用时使用的API相关的。Oracle8i最初的发行版本提供了三种API——JAV存储过程,CORBA分布对象和EJB。另外我们演示了一个简单的HTTP服务器,展示了Oracle8i如果在众所周知的Oracle SQL*Net之外的多种协议上打开数据库。在Oracle8i的后续发布里,我们将公开和归档用于编写可扩张服务器的基于JAVA的框架——与Aurora/ORB所用的相同的框架。

从JAVA开发者的角度看,存储过程会话的生命周期与它所嵌入到的SQL会话是一样的。这个概念对PL/SQL用户来说是熟悉的。JAVA中的任何确定状态都将持续RDBMS会话的生命周期,简化了编写存储过程、触发器和Oracle抽象数据类型方法的过程。在一个会话中对JAVA代码的一个单独引用称为调用。一个调用可以被某些SQL调用初始化。

CORBA和EJB提供了更加面象对象风格的客户/服务器间消息传递,尽管在通用的N-tier应用中客户的区别是模糊的。甚至在CORBA和EJB情况下,客户必须隐含或显式地在服务器上建立一个会话。你从客户传递到服务器端对象的每条消息初始化(建立)一个调用。参考Oracle8i Enterprise JavaBeans and CORBA Developer's Guide and Reference以得到详细的规范。但注意调用和会话的概念在Oracle8i中到处使用。

模式与权限

定义一个JAVA应用的类保存在Oracle8i RDBMS内其拥有者的SQL SCHEMA里。一旦安装,则无须再存取文件,因为这些类和其它资源将由RDBMS根据支持一个应用运行的需要而动态加载。默认情况下,驻留在用户模式里的类由于安全的关系不能被其它用户执行。当然,适当条件可以允许所有其它用户访问本地定义的类。

JSERVER也提供两个类运行的权限模型。默认时,类运行在调用时所在的会话的有效身份与权限——引用者权限。也可以指定类只能在其所有者的身份与权限下运行——定义者权限。比如,可以使用定义权限以限事定只有代码定义者能够访问私有RDBMS表或其它服务器私有资源,或者将这类存取权限扩展给一个较大范围的用户群。详情参考Oracle8i Java 存储过程开发指南。

用调用规格说明(call specifications)导出JAVA方法

如果想编写将被SQL DML或PL/SQL直接或间接引发的触发器调用的JAVA存储过程,则必须通过一个规模说明描述什么方法可被访问,及如何访问。JAVA程序天生就是由许多类的许多方法组成的,通常只有少数静态方法用调用规格说明导出(只有少量“gateway”调用必须导出给SQL用户)。另一方面,如果你是在做CORBA和EJB开发,则你可能从来不必直接使用调用规格说明。Oracle8i和EJB实现支持标准的CORBA和JAVA风格的按名字导出对象,包括对这些对象接口的一致的CORBA和JAVA风格说明。对于调用规范及其使用的细节,请参考EJB发布描述器与CORBA IDL的相关概念。

main()在哪里?

客户端的JAVA应用建立一个单独的顶级main()静态方法,该方法定义了应用程序的总体轮廓。然而,对于现在的applets、服务器端应用程序无须拥有这样一个"内部循环"(inner loop),相反,被逻辑上分离的客户所驱动。理论上,这些客户拥有有main(),要么显式的在客户端JAVA代码里,要么由与服务器交互的客户端程序自然实现。客户初始化交互(sessions),只使用顶级入口点调用它们的服务器逻辑,最后结束其会话。这些入口点是在服务器环境之下的,它们隐藏起所容纳的JAVA服务器代码中与会话、网络、状态或其它共熟资源管理的内容。这个约定比传统的客户虚拟机明显复杂得多,但是,这意味着你只需要编写JAVA服务器逻辑,而不是服务器本身!如果合适,你可以命名你的顶级入口点为main()并为它编写一个调用规范,不过你不是必须这样做。事实上,你可能会发现为存储过程导出更恰当的命名方法更易理解,正如在PL/SQL中。

安全性基础

Oracle8i在服务器上装配了所有的核心JAVA类库,包括与用户界面表示相关的那些(如,java.awt和java.applet)。然而,服务器端执行代码试图在服务器上弹出或实例化一个用户界面是不恰当的。要理解这种不恰当性,想像一下世界范围内的成千上万用户使用一个Internet应用,而这个应用在某些情况下会在服务器硬件上弹出一个需要有人点击的对话框。

可以编写引入并使用java.awt类的JAVA程序,然而不要企图实际建立(materialize)一个用户界面。实际上,这是你建立JAVA applet代码的正确方式。使用java.awt和平台相关的等价实现建立applets并测试之。平台相关的等价实现是一套类用以支持特定的窗口系统。当用户下载applet时,它动态加载相应客户的对等库,而用户将在客户端的操作系统或窗口系统中看到正确的显示。我们提供了一个Oracle相关的对等实现,如果在Oracle8i服务器上执行的JAVA代码企图实际建立一个用户界面它就会抛出一个oracle.aurora.awt.UnsupportedOperation异常。

注意Oracle8i中对materializing用户界面的短缺,意味着我们没有JCK (1.1.6)对java.awt、java.awt.manual和java.applet的测试。在Oracle RDBMS中,所有用户界面只在客户端应用中支持,尽管它们可能与服务器在相同的物理硬件上显示。因为在服务器上支持用户界面没有意义,所有我们把它排除在JAVA兼容性测试之外。在嵌入设备或掌上设备中的JAVA(即个人JAVA)也存在有类似的问题。JAVA和JAVA兼容性测试集的未来版本将提供用户界面支持的进一步分解,以便JAVA服务器平台的供应者可以更好的指出这个问题。

JAVA源码、二进制码和依赖性

所有的JAVA应用都是从JAVA源代码的某个地方开始的(通常是.java文件)。然而,JAVA二进制标准允许你在建立应用程序时使用第三方的类库。这意味着你可以开发一个应用程序,该应用程序使用了不能得到源码的JAVA代码。

正如第2章所讨论的,在JAVA应用程序中执行的实际代码是在运行时动态决定的。比如,在SUN的JDK中JAVA类装入在文件系统中CLASSPATH指定的目录下找到的二进制(.class文件)码。Oracle8i增加了一个单独的步骤,在这一步中,内部的类引用将根据指定的定位器(resolver)定位得到。定位器是对CLASSPATH的直接模拟,它允许你说明类所在模式(schemas)。Oracle8i从类加载器中分离出这个定位步骤,使得你能够确信运行时加载的代码是预先确定的和可重用的。

一但拥有JAVA源码或二进制码,就得在服务器上加载它们。Oracle提供了一个工具—loadjava—来完成这项任务。参考Oracle8i JAVA存储过程开发者指南,以获取loadjava的细节信息。loadjava工具也使你可以成组的加载源码或二进制码文件到服务器上,或按名字挨个加载,或使用一个包含它们的.java文档。在后面的情形里,loadjava在后台会把.java里的各个构件分别加载到服务器上。驻留在服务器端的代码并不懂得.jar文件,尽管它可能是从那里面加载的。

注意,Oracle的JServer不要求存取建立应用程序的JAVA源码,尽管你可以加载JAVA源码到服务器上并用JServer的Java源码编译器编译之。因此,用标准的JAVA二进制形式交付并安装整个应用是完全可能的,也可能完全用源码形式或结合两种形式。

依赖性管理

JServer提供了复杂的依赖性管理,并自动提供当程序依赖的源码或二进制程序被修改时透明重编译的简易性(facility)。考虑以下的情形:

   public class A
   {
      B b;
      public void assignB () {b = new B()}
   }
   public class B
   {
      C c;
      public void assignC () {c = new C()}
   }
   public class C
   {
      A a;
      public void assignA () {a = new A()}
   }

系统在类层次上跟踪依赖性。在上面例子中,可以看到类A,B,C依赖于另一个类。如果改变了类A 的定义,比如增加了一个字段,则Oracle8i中的依赖性机制会标记类B和C是无效。在能够使用这其中的任何一个类之前,Oracle8i会根据必要试着重新定义并编译它们。注意,只有源码存在于服务器上时才能重新编译。

依赖性系统使得你可以依赖Oracle8i管理类之间的依赖性并自动重编译和定义。你只有在进行开发时想尽早找出问题的情况下才需要自己编辑、定位。loadjava工具还提供强制编辑、定位的简易性,如果不想依赖性管理工具替你处理这些时。

引用JAVA程序

我们已经浏览了在ORACLE8I中编写和发布JAVA应用程序的基本知识,但如何实际引用JAVA程序呢?通常JAVA开发者这样做:

java myprogram

其中myprogram是包含一个main()方法的类。在JAVA存储过程的情况下,代码的执行通常是在触发器或DML调用等数据库操作的副产物。在所有这些情况下,必须使用调用规格说明发布可被引用的JAVA方法。在SQL*PLUS中引用已经发布的方法的简单方式是:

call myprogram.staticMothodName();

另一种方式是通过EJB或CORBA引用JAVA程序,这允许你易于在客户和服务器之间分布程序逻辑。

我们将会在后面的例子中计论这两种技术。

一些简单的例子

我们建议你用每一步所学到的东西增量式完成Oracle8i中的JAVA开发。目前不要求了解CORBA和EJB的使用,但有点基本的理解将使你在时机适当时能够利用这些强大的工具。

首先,你得掌握编写简单的JAVA程序然后发布到服务器的过程。必须理解作为导出顶级入口点的一种方式的调用格式说明的使用。学习下面这个一个显示"Hello world"的简单JAVA存储过程时要同时注意这几个方面。

其次,你得理解如何从JAVA中访问并操纵SQL数据。绝大多数JAVA服务器程序,确切讲是在ORACLE8I上执行的JAVA程序,将与驻留在数据库中的数据交互。完成该任务的两个标准API是JDBC和SQLJ。因为JDBC构成SQLJ的基础,两者是怎样工作的都得理解,即使你在代码中只使用SQLJ。我们将在第二个例子中聚焦JDBC和SQLJ。每个JAVA开发乾都必须浏览并理解这个领域。

最后,如果你想在客户和服务器间或一个N-tier结构中分布JAVA逻辑,那么就得理解Oracle8i中的CORBA和EJB是如何工作的。CORBA和EJB提供了这个复杂总是最简单的解决方案,用一种基于Internet标准的方式,使得你可以为事务型应用使用基于构件开发。此外,EJB和CORBA建立在Oracle8i的JAVA存储过程或JDBC的简易性之上,能够简化你的工作。

在我们对EJB和CORBA的讨论的最后,我们将瞄一眼被称为session shell的工具,一个使用JAVA存储过程和CORBA完全由JAVA写成的工具的例子。它允许你与驻留在服务器上、通过CORBA而在会话中可见的对象交互,就象Unix的Shell命令。session shell是Oracle8iEJB与CORBA开发者指南说明了很有用的工具的。然而,它也是在Oracle8i服务器上执行JAVA的能力为Oracel开发者打开新天地的一个重要的例子。它演示了你可以使用相当复杂的CORBA建立工具以使开发者和最终用户的生活更容易一些。

Hello World—对JAVA存储过程的介绍

这里有一个编写"Hello world"的例子。定义一个类,Hello,它有一个方法,Hello.world(),该方法返回字符串"Hello world":

public class Hello
{
    public static String world()
    {
       return "Hello world";
    }
}

在客户端工作站上编译这个类。如果使用SUN的JDK,按下面这样引用Java编译器(javac):

javac Hello.java

通常,在javac命令行上指明CLASSPATH是个好主意,特别是在Shell命令脚本或make文件里时。JAVA编译器生成一个JAVA二进制文件——这里是Hello.class。现在必须使用loadjava把类加载到Oracle8i服务器上。

loadjava -user scott/tiger Hello.class

这个最简单的例子使用默认的oci8数据库连接。必须指明用户名和口令。默认情况下,登录的模式(这里是"scott")将包含Hello类。

在SQL*Plus里,连接到数据库,为Hello.world()定义一个顶级调用规格说明:

connect scott/tiger

create or replace function HELLOWORLD return VARCHAR2 as

  language java name 'Hello.world() return java.lang.String':

myString varchar2;

call HELLOWRLD() into :myString;

pring myString;

“call HELLOWORLD() into :myString”这一句是在Oracle8i中建立顶级调用的新方法。Oracle独有的select HELLOWORLD from DUAL同样也能工作。注意SQL和PL/SQL在用JAVA、PL/SQL或任何其它语言写成的存储过程里看起来没有差别。调用规格说明提供了一种将语言内部调用绑定到统一方式的方法。再说一次,对调用规格说明不要害怕或有心理负担,因为只对被触发器或SQL或PL/SQL调用引用的入口点,它们才是必须的。此外,如果你愿意,JDeveloper可以自动完成调用规格说明编写的任务。最后,如果你使用CORBA和EJB开发服务器端JAVA代码,则无须使用调用规格说明。

JAVA存储过程、在触发器中使用JAVA、调用规格说明、权限模式、语言内部调用的更多信息请参考Oracle8i Java存储过程开发者指南。请继续往下读以了解使用SQLJ和JDBC访问SQL数据库总览和获取CORBA、EJB教程。

用JDBC和SQLJ存取和操纵SQL数据

JDBC是一个工业标准API,由SUN公司开发,使你能够嵌入SQL语句作为JAVA方法的参数。JDBC是是基于X/OPEN SQL CALL LEVEL INTERFACE的,它兼容SQL92 ENTRY LEVEL标准。每个供应商,比如ORACLE,通过实现SUN公司的java.sql包而建立自己的JDBC实现。如第一章所讨论的,ORACLE提供三种JDBC驱动程序,实现以下标准接口:1)JDBC THIN驱动,100%纯JAVA解决方案,可以用在客户端应用程序或applets,无须安装ORACLE客户端;2)JDBC OCI驱动(OCI8或OCI7),可用于客户端应用程序,需要安装ORACLE客户端;3)嵌入在ORACLE8I服务器里的服务器端JDBC驱动。

对于开发者来说,使用JDBC是个逐步过程:为需要的SQL操作建立某种语句对象,给需要绑定的SQL操作的局部变量赋值,执行SQL操作。这个过程对许多应用来说是高效的,但对任何修兼容语句都适用。

SQLJ提供了一种工业标准方式用直接在JAVA代码,SQL操作在简单的一步里完成,不象JDBC那样需要许多独立的步骤。(动态的SQL—运行时才知道要执行的操作—必须使用JDBC。然而,在典型的应用中,这种SQL操作所占数量很少。)ORACLE SQLJ兼容ANSI标准 X3H2-98-320。

SQLJ由一个翻译器和一个运行时构件组成。翻译器是一个预处理器,你将SQLJ源码保存在.sqlj文件里,然后用这个翻译器处理,翻译会将SQLJ源码翻译成标准的JAVA代码,SQL操作会被转换成运行时JDBC调用。在ORACLE SQLJ实现中,翻译器引用Java编译器编译JAVA源码。当你的ORACLE SQLJ应用程序运行时,SQLJ运行时环境调用JDBC来与数据库通信。

JDBC代码 vs. SQLJ代码

下面是一个简单操作的例子,首先是JDBC代码,然后是SQLJ代码。

JDBC:

//(假定已经有一个JDBC连接对象conn)

//定义JAVA变量

String name;
int id=37115;
float salary=20000;
//建立JDBC Prepared语句。

PreparedStatement pstmt = conn.prepareStatement
    ("select ename from emp where empno=? and sal>?");

pstmt.setInt(1,id);
pstmt.setFloat(2,salar);

//执行查询;提取name并赋给JAVA变量
ResultSet rs=pstmt.executeQuery();
while(rs.next()){
   name=rs.getString(1);
   System.out.println("Name is: " + name);
}
//关闭结果集和语句对象
rs.close();
pstmt.close();

头三行定义了JAVA变量:name,id和salary。下一行则定义了一个称为已准备语句(这假定已经建立了到数据库的连接,这样可以使用连接对象的prepareStatemnet()方法)。当SQL语句里的值需要动态设定时使用已准备语句(可以以不同的参数值使用同一个已准备语句)。语句在已准备语句中的问号是JAVA变量的占位符,并被代码中的pstmt.setInt()和pstmt.setFloat()两行赋值。第一个“?”被设为int型变量id(当时值为37115)。第二个“?”被设为float型变量salary(当时值为20000)。最后,感兴趣的数据(name)从结果集中提取出来并显示。一个结果集通常包含多行数据,尽管本例子中只有一行。

现在,作为对比,来点SQLJ代码执行同样的任务。注意,所有的SQLJ语句,定义和执行语句,都使用#sql符号开头。

SQLJ:

String name;
int id=37115;
float salary=20000;
#sql {select ename into :name from emp where empno=:id and sal>:salary);
System.out.println("Name is: "+name);

SQLJ,除允许直接在JAVA代码嵌入SQL语句之外,还支持在SQL语句中直接使用Java host expressions(也被称为绑定表达式)。在最简单情况下,一个宿主表达式以“:”开头;本例中使用了JAVA宿主表达式name,id和salary。在SQLJ中,由于它的宿主表达式支持,当只返回一行数据时不需要使用一个结果集或等价物。

SQLJ也允许你在运行之前捕捉SQL语句中的错误。纯JAVA的JDBC代码中则是直接编译,编译并没有SQL的知识,所以它意识不到任何SQL错误。做为对比,当你SQLJ代码时,翻译器分析嵌入的SQL语句的语法和语义,在开发期间捕捉SQL错误而不是让最终用户在运行这个程序时抓信它们。

完整的SQLJ例子

这一节展示一个完整的简单SQLJ程序例子:

import java.sql.*;
import sqlj.runtime.ref.DefaultContext;
import oracle.sqlj.runtime.Oracle;
#sql iterator MyIter (String ename, int empno, float sal);

public class MyExample
{
   public static void main (String args[]) throws SQLException
    {
      Oracle.connect
        ("jdbc:oracle:thin:@oow11:5521:sol2","scott","tiger");

      #sql { insert into emp(ename, empno, sal)
         values ('SALMAN', 32, 20000) };
      MyIter iter;

      #sql iter={ select ename, empno, sal from emp };
      while (iter.next()){
         System.out.println
              (iter.ename()+"    "+iter.empno()+" "+iter.sal());
      }
   }
}

第一条SQLJ语句定义了一个迭代类iterator。SQLJ使用JDBC结果集的强类型版本,称为iterator。主要的区别在于iterator有一定数目的特定数据类型的列。必须首先定义自己的iterator类型,正如本例中的:

#sql iterator MyIter (String ename, int epno, float sal);

这个定义的结果是在SQLJ中建立了一个迭代器类MyIter。MyIter型的迭代器可以存储结果集,其第一列映射为Java String,第二列映射为Java int,第三列映射为Java float。这个定义也将三个列命名为ename,empno和sal,与数据库表里的列名字一致。(MyIter是一个命名迭代器,参见Oracle8i SQLJ开发者指南与参考第三章了解无须为列命名的位置迭代器)

main方法的第一条语句是:

Oracle.connect
("jdbc:oracle:thin:@oow11:5521:so12","scott","tiger");

Oracle SQLJ建立ORACLE类,其connect()方法完成三件重要的事情:1)它注册SQLJ用以连接数据库的Oracle JDBC驱动程序;2)它用指定的模式(user scott,password tiger)在指定的URL(host oow11, prot 5521, SID so12, "thin" JDBC driver);3)它建立这个连接作为SQLJ语句的默认数据库连接,或可选说明一个不同的连接。

第一条执行的SQL语句插入一行到emp表里,如下:

#sql {insert into emp (ename, empno, sal) values ('SALMAN', 32, 20000)};

紧接在迭代器变量定义之后的下一条SQLJ语句执行一个查询。这将实例化并填充该迭代器:

MyIter iter;
#sql ter={select ename, empno, sal from emp};

一旦迭代器被填充,就可以如下存取它了:

while (iter.next()) {
    System.out.println(iter.ename()+" "+iter.empno()+" "+iter.sal());
}

next()方法是所有迭代器通用,扮演与JDBC结果集的next()方法一样的角色,如果还有行则返回true并移到下一条记录。可以通过调用iterator accessor方法访问每一行里的数据,该方法与列的名字是一样的(这是所有命名iterator的特性)。本例中,使用ename(),empno()和sal()方法访问数据。

SQLJ强类型范例

前面的iterator例子和讨论指出了SQLJ的一个关键好处。它对强类型的使用,允许你的SQL指令在翻译期间根据数据库被检查。翻译器将验证它们是否匹配,允许你在翻译期间捕捉SQL错误。此外,如果以后对模式做了修改,你可以简单的通过重运行翻译器来判断它是影响你的应用程序的运行。

翻译一个SQLJ程序

象Oracle JDeveloper这样的集成开发环境,可以在开发时为翻译、编译和定制你的SQLJ程序。如果不使用IDE,那么你可以使用前端SQLJ工具,sqlj。可以在运行sqlj时指定许多选项,但最简单情况下可以如下运行它:

%sqlj MyExample.sqlj。

当.sqlj文件翻译期间,SQLJ翻译器检查SQL操作的语法和语义。另外可以打开在线检查以在数据库检查你的操作。要做到这一点,必须在翻译器的选项里指定一个数据库模式。对于程序最终运行所在的模式不必拥有同样的数据,但里面的表必须有相应名字和数据类型的列。使用翻译器的user选项打开在线检查,例如:

%sqlj -user=scott/tiger@jdbc:oracle:thin:@oow11:5521:so12
MyExample.sqlj

在服务器上运行SQLJ程序 vs. 在客户端运行

许多SQLJ程序运行在客户端;然而SQLJ及其简单的语法也给运行在服务器上的存储过程的编制带 来部份好处。

编写客户端SQLJ程序与服务器端SQLJ程序几乎没有区别。SQLJ运行时包在服务器上自动可用,只有如下的少量代码需要考虑(SQLJ开发者指南与参考有进一步的讨论):

对于在服务器上运行的代码没有显式的数据库连接,只有一个单独的隐式连接,这样你就无需使用普通的连接代码。(不过,如果你要稳植一个客户端程序,那么并不需要去掉连接代码,它将会被忽略)。

KPRB驱不支持自动提交功能。使用SQLJ语法自动提交或回滚事务。

在服务器上,默认的输出设备是一个追踪文件,而不是用户屏幕。通常这只开发时的一个小问题,因为不可以在发布到服务器上的程序里输出到Sys.output。

要在服务器上运行一个SQLJ程序,假定你在client上开发代码,有两选择:

在客户端翻译SQLJ源码并将各个构件(JAVA类和资源)加载到服务器上。这种情况下,把它们包装到一个.jar文件里最容易。

将你的SQLJ源码加载到服务器上,使用内置的翻译器翻译。

两种情况下,你都使用Oracle的loadjava工具加载文件或文件组到服务器上。SQLJ开发者指南与参考的第11章也是讨论本问题的。

将客户端程序转换到服务器上运行

将已有的客户端程序转换到服务器上运行是相当简单和直接的。假定这是一个已经在客户端翻译过的程序:

1、为应用程序构件建立一个.jar文件。

2、使用loadjava工具加载.jar文件到server上。

3、在服务器上为应用建立一个SQL封装。例如,要服务器上运行前边的MyExample应用:

create or replace procedure SQLJ_MYEXAMPLE as language java
name 'MyExample.main(java.lang.String[])';

然后就可以象其它任何存储过程一样执行SQLJ_MYEXAMPLE了。

与PL/SQL交互

所有的ORACLE JDBC驱动才能与ORACLE SQL和PL/SQL无缝通信。注意到SQLJ与PL/SQL互操作是十分重要的。你可以开始使用SQLJ而无须重写任何PL/SQL存储过程。ORCALE SQLJ包括了调用PL/SQL存储过程的语法,也允许将PL/SQL匿名块嵌入到SQLJ执行语句里,正如SQL操作。

CORBA和EJB分布对象

JAVA为编写存储过程带来了一种极妙,简单,但通用的语言。JDBC和SQLJ则是令JAVA能够存取SQL数据的胶水。这种以JAVA语言形式存在的胶水支持SQL操作与概念,JAVA与SQL类型之间的变量绑定,并且支持将JAVA类映射到SQL类型。可以编写可移植的JAVA代码,这种代码可以在客户或服务器端运行而无须修改。使用JDBC和SQLJ,客户与服务器的分界线通常是显而易见的,SQL操作发生在服务器外,而应用程序逻辑驻留在客户端。

通常,逻辑是分布的程序,其体系结构将会选用三层:客户、中间层、数据库服务器。客户层通常只限于显示中间层提供的信息,中间层执行业务或应用逻辑,存取驻留在第三层数据库上的数据。ORACLE8I消除了许多应用对物理上的中间层的需求——这些应用要求快速的存取数据库。ORACLE8I不定期维护一个逻辑的三层结构,但将中间层与数据库服务器绑定在一起,物理上的体系结构是两层。这种体系结构带来的灵活性对ITERNET应用来说是非常理想。这类应用的客户端通常在WEB BROWSER里表达信息,通过网络与服务器交互。这些服务器可以在其客户-服务器交互联合中联合、协作以为intranet/internet应用的基于WEB的客户提供信息。

当你编写更复杂的JAVA程序时,你会发现控制程序逻辑运行的地点以获取性能与可扩张性的倾向。你会想将网络传输降到最少,尽可能酵引用SQL数据。JDBC和SQLJ特别是与ORACLE8I中支持JAVA的新的JSERVER一道,建立了完成这些目标的方式。然而,因为你谋略变革你的JAVA应用程序的对象模型,JAVA执行时将会花费明显要多的时间,期望存取操纵SQL数据。

理解并说明INTERNET应用中的JAVA对象驻留执行在什么地方变得更重要。那么,现在你成了转向CORBA和EJB世界的选手了。

CORA和EJB的关键特性是使用标准来描述构件。CORBA用户接口定义语言IDL用一种语言无关的方式描述如何存取和使用一组被称为构件的对象。EJB进一步扩展这个概念:在JAVA类定义中申明构件的接口,及RMI风格定义的发布脚本以定义在一个带有事务支持的安全的应用中该构件被如何对待。CORA和EJB是一种补充;JSERVER在CORBA的支持与服务之上实现了EJB1.0规范。

可以通过一种名字服务访问构件,名字服务形成一棵树,类似于文件系统,可以用名字把对象保存在其中。JAVA名字与目录接口(JNDI)包提供了名字服务的统一接口,JNDI的部份提供了对文件系统存以的平台无关性抽象,面文件系统访问是极度平台相关的。当把一个CORBA对象放到名字空间里去时,就是在发布它。尽管这与通过调用规格说明发布入口点有点类似,但它实际上要强大得多。使用EJB,可以发布接口被完全申明的构件,包括其事务处理形式、使用时的安全限制等。使用JAVA存储过程与调用规格说明,你被限定使用SQL数据类型做为参数类型或返回值类型(更确切的讲是限于使用表达这些SQL数据类型的JAVA类集合)。使用CORBA和EJB,你在完全面象对象的基于消息的世界里操作。方法使用对象作为参数并返回对象。这些对象在你的会话期间保持对象标识。这意味着客户服务器间的通信协议是面向对象的并无缝集成到JAVA语言中。

Oracle8i与每个客户交互就象它在服务器上拥有整个虚拟机。因为这种体系结构,在JSERVER中没有服务多个客户请求的单个ORB。相反,JSERVER利用ORACLE8I的多线程服务器体系结构MTS,为每个会话提供了一个ORB。不象客户通过SQL*NET通信的会话,得通过IIOP访问CORBA和EJB会话,它们具有服务多个客户连接的能力。尽管可扩张性应用通常为每个客户服务器交互提供一个会话,相对于SQL*NET,服务多个客户的能力仍然极大的扩展了会话的活性,并允许在你的分布式通信中使用回调和loop back。这种能力是对CORBA和一般JAVA程序的重要要求。

如果你是一个CORBA程序员,那么你对bootstrapping你的应用程序是很熟悉的。你的客户端JAVA代码必须取得实际上驻留在服务器上的对象的句柄才能操纵它。这些对象通过前边提过的名字服务而可被访问。ORB通常提供名字服务,名字服务假定当你试图定位引导客户程序所有的服务器对象时ORB正在运行。没有每个会话的ORB进程,相反,JSERVER基于CORBA的CosNaming提供一种激活服务。我们为CosNaming提供一个JNDI接口,这样你可以在会话中使用基于URL的名字来引用和激活CORBA对象。实际上,所有的引导是通过与JSERVER建立一个会话并使用对象完成的,这些对象始终可从ORACLE8I数据库得到,并由标准的JAVA JNDI和CORBA CosNaing使之对你可见。你无须关心多数CORBA程序可能用到的基于平面文件的ORB间引用IOR。

EJB使得基于构件的编程比CORBA容易。一个EJB程序员简单的编写业务逻辑、构件的接口,发布工具——deployejb——会处理其它细节。无须IDL、事务处理实现或安全实现的知识。这种可移植的基于JAVA的框架为基于JAVA的多层应用提供了一个快速、可扩张的容易的解决方案。

建立并发布EJB

完整的讨论见EJB与CORBA开发指南。这里只让你瞄一眼以熟悉片断与过程。下面讲解的是指南中用来人ORACLE RDBMS中查找一条雇员记录的EmployeeBean的例子。步骤如下:

1、建立HOME接口。HOME接口将驻留在服务器 ,使你能够在服务器上创建该EJB的实例。HOME接口是继承EJBHome而得的JAVA接口。HOME接口是唯一通过JNDI正常发布到客户可见的名字空间的对象。

2、建立远程接口。REMOTE接口描述了你在EJB中实现的方法,如可以从客户引用的方法等。一个远程接口是扩展EJBObject而得的JAVA接口。作为一个接口,它只是用来描述以后会在BEAN里实现的方法的。

3、实现BEAN类和远程接口定义的方法。换句话说,你必须实际实现BEAN类的方法。在EmployeeBean的例子中,只有一个方法:getEmployee()。你通过建立一个实现SessionBean接口的类来编写一个BEAN,这里是EmployeeBean。(在8.1.5 以前JSERVER不支持实体豆)

4、建立发布脚本。发布脚本描述了BEAN的属性,包括其事务处理属性和安全性处理。你描述这些属性,deployejb工具确认服务器执行它们。(enforce them)

5、发布EJB。当你发布EJB时,deployejb工具将你的HOME接口和EJB放到服务器上,将HOME接口发布到名字空间里。它生成必要的服务器端JAVA代码以处理你在发布脚本里描述的事务处理与安全性。最后,它生成并返回存根接口,该接口向客户提供对BEAN的远程函数的访问。

使用一个EJB

一旦你建立并发布一个EJB,你将会想在客户程序中使用它。你也可以在多层应用的服务器间使用EJB,这时一个服务器的客户可能是其它客户的服务器。在你的客户端代码中必须完成以下步骤:

1、定位驻留在服务器上的HOME接口对象。使用JAVA标准的JNDI查找机制定位该对象。

2、在服务器上认证客户身份。EJB和CORBA客户与其它任何ORACLE客户一样使用数据库会话。要初始化一个会话,必须让服务器知道你是一个合法用户。可以通过多种安全形式达到这一目标。

3、激活BEAN的实例。因为你用JNDI定位到的对象是一个HOME接口,所以你得使用它的create()方法返回一个EJB的活动实例。

4、引用BEAN的方法。当你引用一个BEAN的方法时,方法实际是在服务器上执行的,适当的参数与返回对象是通过IIOP连接透明传递的。所有的EJB返回对象必须是可串行化的——它们必须实现ava.io.Serializale。

详见指南。JDEVELOPER之类的JAVA IDE会自动处理发布与脚本的处理。

SESSION SHELL

SESSION SHELL给服务器提供了一个类似于SHELL的界面。这个SHELL允许用户用类似UNIX命令,诸如mkdir,ls,rm等,操纵会话的名字空间。另外,SESSION SHELL建立了一个在服务器上运行JAVA程序的保留方式:使用java命令。正如客户端JAVA程序执行一个JVM,SESSION SHELL 的java命令接收类的名字及任何用户输入的参数。SESSION SHELL会调用该类的静态方法main(String[]),在服务器上运行该JAVA程序。System.out和System.err被捕捉并直接重定向到用户的控制台上。

SESSION SHELL是一个如何JSERVER的CORBA支持建立JAVA客户程序直接引用服务器端JAVA函数的极好例子。它演示了当你精通JAVA和CORBA/EJB之后配合JSERVER平台使用这些技巧可以达到的简化和能力。

//我觉得ORACLE8I的文档实在太喜欢自吹自擂了,隔个几行就会来一句ORACLE8I/JSERVER如何如何好。不象技术文档,倒是有偿新闻。

4、JSERVER环境细节

初始化与配置一个支持JAVA的数据库

ORACLE典型安装时建立的初始数据库是支持JAVA的,能够运行JAVA存储过程,JDBC,SQLJ和CORBA/EJB对象。如果手工建立数据库实例则须运行ORACLE_HOME/javavm/install里的initjvm.sql。该脚本为CORBA名字空间初始化SQL表,用调用规格说明发布一些顶级入口点。initjvm.sql脚本将近4000个JAVA类装入到数据库中。所以可以说,JSERVER的大部份是用JAVA写成的。这些初始类集包括:

标准JRE(如java.lang, java.io, java.net)

嵌入式字节码验证器与优化器

JAVA和SQLJ编译器

JDBC

Aurora/ORB与EJB运行时 一些附加的支持类,诸如DbmsJava,描述在本章的Package DBMS_JAVA节。

在多数机器上运行initjvm.sql在不支持JAVA的数据库上进行初始设定需要约十分钟。它将类装到SYS模式里,并为它们创建所有用户可以访问的公开synonyms。initjvm.sql脚本替换这些类中的一部份为以定义者权限运行以支持CORBA callouts。参考JAVA存储过程开发指南的定义者权限与JAVA类的信息。

初始化一个支持JAVA的数据库需要约50MB的SHARED_POOL_SIZE和足够的回滚段。如果脚本因为某个原因失败,如资源不足,则需要调整必要的资源并重新运行initjvm.sql。参考/javavm/README.txt文件以获取最新的init.ora配置参数与要求信息。

另外,使能EJB和CORBA通信还有些特殊要求。典型和自定安装的JSERVER对于起步是足够了。详情可参见EJB与CORBA开发者指南。

PACKAGE DBMS_JAVA

当初始化JSERVER时,initjvm.sql创建PL/SQL包DBMS_JAVA。其中的一些入口点是用得着的,另一些目前只供内部使用。相应的JAVA类DbmsJava提供了一个"grab bag"以便从JAVA访问RDBMS功能。此外,它的许多静态方法也在DBMS_JAVA包里通过调用规格调用发布并变得可用。DBMS_JAVA包提供以下入口点:

FUNCTION longname (shortname VARCHAR2) RETURN VARCHAR2

返回JAVA模式对象的长名字。因为JAVA类与方法可能会有超过SQL标识符最大字符长度限制的名字,Aurora在内部使用缩略名字以进行SQL存取。这个函数简单的返回任何缩节名字的原始JAVA名字。下面的例子就是显示所有无效类的全名:

select dbms_java.longname(object_name) from user_objects
where object_type='JAVA CLASS' and status = 'INVALID';

参考JAVA存储过程开发者指南。

FUNCTION get_compiler_option(what VARCHAR2, optionName VARCHAR2)
PROCEDURE set_compiler_option(what VARCHAR2, optionName VARCHAR2, value VARCHAR2)
PROCEDURE reset_compiler_option(what VARCHAR2, optionName)

这三个入口点控制JSERVER JAVA与SQLJ编译器的选项。JAVA存储过程开发指南与SQLJ开发指南都有选项与入口点的文档。

PROCEDURE set_output(buffersize NUMBER)
该过程将JAVA存储过程和触发器的输出到DBMS_OUTPUT包。详见本章的“服务器上的输出转向”。

安全性

作为一个全兼容性虚拟机实现,JSERVER支持标准的JAVA安全性机制。初始时,Aurora安装一个java.lang.SecurityManager的实例,且JLS规定不能安装另一个实例。 绝大多数策略是装上的极光(Aurora)安全管理强制依赖动态ID,它是与决定JAVA类能执行哪些SQL操作的同一个ORACLE用户。决定动态ID的方法与你所在的会话类型有关。通常动态ID只是会话拥有者,但CORBA和EJB遵循特殊的规则。你可以用定义者权限调用类的方法或用某些直接操作调用类的方法。

在多数情况下,SecurityManager检查动态ID是否被授与了JAVASYSPRIV,这是安装JAVA时极光建立的常规数据库角色。你可以赋与或收回JAVASYSPRIV就象你对其它任何数据库角色可以做的一样。一些操作只要求取得JAVAUSERPRIV。多数情况下,如果用户执行代码的动态ID没有被赋与JAVASYSPRIV且试图执行禁止的操作,则一个java.lang.SecrityException将被抛出。下一节中,“安全管理器”,讨论角色定义的细节及操作限制。

安全管理器

下表列出了安全管理器在极光虚拟机中执行的检查。通常,安全管理器允许只影响当前会话的操作不受限制。在SQL操作中与外部世界正常交互的操作要求JAVAUSERPRIV,它允许你执行驻留在JServer中的具有相同权限或更低权限的JAVA,这是SUN的JDK强加的。由于你的代码将会在服务器上执行,而服务器可能为许多用户执行关键任务,所以Oracle建议你小心授权。另外,提供对服务器端文件系统或其它操作系统资源的访问将会引起安全性和数据完整性方面的风险。

下表列出了java.lang.SecurityManager的检查方法,并说明了需要哪种角色。如果没有必要的角色而调用这些方法,那么将会抛出一个SecurityException。除了一种情况检查是在动态ID上进行的,在那唯一的例外情况下检查是在静态ID上执行的。即调用checkLink的类必须被载入到一个拥有必要权限的模式里。
Check Methods Roles
checkAccess   none  
checkAwtEventQueueAccess   none  
checkConnect   JAVAUSERPRIV  
checkCreateClassLoader   none  
checkDelete   see IO  
checkExec   JAVASYSPRIV  
checkExit   none  
checkLink   JAVASYSPRIV(static id)  
checkListen   JAVASYSPRIV  
checkMemberAccess   none  
checkMulticast   none  
checkPackageAccess   none  
checkPrintJobAccess   none  
checkPropertiesAccess   none  
checkPropertyAccess   none  
checkRead   see IO  
checkSecurityAccess   none  
checkSetFactory   JAVASYSPRIV  
checkSystemClipboardAccess   none  
checkTopLevelWindow   none  
checkWrite   see IO  

I/O操作

I/O检查的策略分为两部份:

1、如果动态ID拥有JAVASYSPRIV,那么安全管理器将允许操作进行。

2、如果动态ID拥有JAVAUSERPRIV,则安全管理器将许可它执行,并遵循与PL/SQL的FILE_IO包相同的规划。此外,文件必须在utl_file_dir参数指定的目录或其子目录里。

调试

参考Debugging in the JServer节。

要调用调试代理,调用者必须拥有JAVADEBUGPRIV,它暗含JAVASYSPRIV。尽管你应该可以无须JAVASYSPRIV而调试自己的代码,但这一限制已经被放在调试器的当前发布(8.1.5中的预发行)。调试器提供了对服务器上的代码和数据的更广泛访问,但此时,我预计它只限于开发环境中使用。

受保护的包

你不可以替换系统类(那些由CREATE JAVA SYSTEM安装的,数据库初始化时在initjvm.sql中引用)。另外,系统类所在的任何包或子包中的类只能在SYS模式中建立。受保护的包包括以以下字串开头的包:

java
javax
oracle.aurora
oracle.jdbc
oracle.rdbms
oracle.sqlj
oracle.sqlnet
oracle.vm
oracle.weak
sun.tools
sun.misc
sun.net
com.visigenic.vbroker
com.sun.jndi
org.omg
sqlj

本地编译代码

ORACLE交付JSERVER时带有所有核心的JAVA类库,并且ORACLE提供JAVA代码本地编译器以便获取更快的执行速度。JAVA类是做为ORACLE_HOME下的/javavm/admi目录里的共享库面存在的。每个共享库负责一个JAVA包;例如,SOLARIS系统中的libjox8java_lang.so和WINDOWS/NT系统下的libjox8java_lang.dll存有java.lang类。包与类命名规范在不同平台上可能是多样的。极光虚拟机内部使用这些文件,在运行时必要的情况下打开它们。

本地编译可提供比字节码解释快2到10位速度。确切的速度增长取决于多个因素,包括:

通常,本地编译代码比解释字节码消耗更多的内存,与两到三个因素有关。在自适应优化技术中的caching产生类似的消耗。当JAVA服务器为数千用户执行独立的代码或存储过程时这就更加真实。使用JSERVER的way-ahead-of-time来达到交付本地编译JAVA代码提供巨大持久的性能提高而又不必在意用户数或他们在服务器上经过的代码路径的目的。

JAVA本地编译技术将会在下一次发行中对你的代码可用(译注:8.1.6,目前2000.8. oracle已经发布了8.1.7,所以别怕)。

JAVA内存使用与数据库配置

典型与定制数据库安装过程安装的数据库已经被配置为支持开发时的各种JAVA用法。意识到运行时使用JAVA通常取决于发布的特定应用的系统资源使用情况,而开发时使用的资源则更多的依赖于你在做什么。

基础的init.ora参数中能够影响JAVA使用及性能的share_pool_size和java_pool_size。share_pool_size由loadjava临时性使用。在数据库初始化过程中,要求shared_pool_size设为50M以便为超过4000个类加载JAVA进制码并解决它们。当你建立调用规格说明时也要消耗Shared_pool_size资源,及运行时系统动态加载JAVA类时也是。

极光的内存管理器在运行时从总数java_pool_size的内存时分配其它所有的JAVA状态。这个内存包括表达JAVA方法与类定义的内存内共享表示,及在调用结束时融合进会话的JAVA对象。在正式场合,你将与所有其它JAVA用户共享内存开销。后来,在MTS下,你必须根据每个会话中保存在静态变量中的状态总数调整java_pool_size。

你可以调整init.ora中另外两个与JAVA相关的参数:

1、java_soft_sessionspace_limit——当一个用户的会话期JAVA状态超过这个大小,极光在RDBMS的.trc文件中生成一个警告。默认值是1MB。你得理解所发布的应用程序的内存要求,特别是当他们与会话空间使用相关时。这个参数允许你给一个会话中可用的JAVA内存设一个“软限制”,即出状况时向你报警。

2、java_max_sessionspace_size——当一个用户的会话期JAVA状态试图超过这个尺寸时,极光用一个内存耗尽失败杀死该会话。默认值是4GB。这个限制显然要设成通常不可能用到的巨大值。如果一个在服务器执行的用户可调用JAVA程序可以用一种不自我限制内存使用的方式使用,那么这个设置可能是有用的,它可以对程序可见总会话空间设一个硬限制。

JSERVER的统一内存管理工具和共享的只读产品(如字节码)使得HelloWorld每多在一个会话中执行只增加35K字节的内存。多数具有丰富状态的服务应用,诸如CORBA和EJB应用使用的Aurora/ORB,每个会话增加的内存只要求约200KB。这些应用需要在多个调用之间使用静态变量保留大量的状态信息。参考下面的“END-OF-CALL MIGRATION”获取更多的信息以便理解和控制调用结束时静态变量的融合。

调用结束融合

为使能同时执行你的JAVA程序的用户达到最多,最小化一个会话的足迹是很重要的。特别是要达到最大可扩张性,一个不活动的会话应该尽可能少的内存。一个最小化足迹的简单技术是在调用结束时释放巨大的数据结构。可以在另一个调用再次需要时重建许多数据结构。由于这个原因,极光JAVA虚拟机有一个机制使得会话在即将失活时调用特定的JAVA方法,比如在调用结束时。

决定是否在调用结束时放空数据结构然后每次调用时重新创建是典型时间/空间平衡。重建结构需要额外的时间,但因在调用间不保留结构省了大量空间。另外,在对象被融合进会话空间后访问昂贵得多时得考虑一下时间。差异来自基于会话空间还是基于调用空间的对象的表达。

合适这种优化的数据结构例子:

缓冲区或CACHE

静态域,如数组,初始化后在整个程序期间都不再改变

任何在调用间表示更省空间而在调用内表示速度更快的动态建立的数据结构。

最后一项可使得程序精巧而复杂,使得它难于维护,因此你要认清这一点,只有测试过省下来的空间值得处理这个麻烦时才使用它。

极光对调用结束优化的特别支持

Oracle在oracle.aurora.memoryManager包里安装了对实现调用结束优化的支持——EndOfCallRegistry类和回调接口。

Class oracle.aurora.memoryManager.EndOfCallRegistry and the Callback Interface

这个类维护一个thunk/value对组成的表,称为调用结束回调。在调用结束时,极光为表中的每个对调用thunk.act(value)。回调调用结束后它就从表中删除。如果调用结束也是会话的结束,那么回调不会被调用。

注意到记录调用结束回调的是一张脆弱的表是重要的。如果如果JAVA程序不调用到thunk或value,则那么都会被从表中删除。这也意味着登记一个回调并不能防止垃圾收集器回收那个对象。而且,如果你需要那个回调,那么必须自己保存回调——不能依赖从表中取回它。

你使用EndOfCallRegistry的方法依赖于是否处理保存在静态域或实例域中的对象。对于静态域,你得使用EndOfCallRegistry清除与整个类关联的状态。这时thunk应该保存在一个私有静态域中,任何要求访问调用间被删除掉的被CACHED数据的代码必须通过一个访问器方法lazily建立或重建被缓存数据。这里有一个例子:

import oracle.aurora.memoryManager.Callback;
import oracle.aurora.memoryManager.EndOfCallRegistery


class Example{
   static Object cachedField = null;
   private static Callback thun = null;
   static void clearCachedField() {
   // clear put both the cached field and thunk so they don't
   // silt up session space
   cachedField = null;
   thun = null;
}

private static Object getCachedField(){
   if(cachedField == null){
     //save thun in static field so it doesn't get reclaimed
     // by garbage collector
     thunk =
        new Callback(){
           public void act(Object obj){
                Example.clearCachedField();
           }
        }
     //register thunk to clear cachedField at end of call,
     EndOfCallRegistry.registerCallback(thunk);
     // finally, set cached field
     cachedField = createCachedField();
     }
     return cachedField;
}
private static Object createCachedField(){
...
}
}

你也可能想用EndOfCallRegistry清除保存在实例域中的数据结构;例如与类的每个实例关联的状态。这时,每个实例有一个域为实例在必要时保存被缓存状态并填冲被缓存域。存取该被缓存域是通过一个访问者方法,该方法可确保状态可被缓存。类实现了Callback接口,并在被缓存域被填写时登录实例。这一目标确保回调周期中thunk是与实例的访问周期一致的,因为它们是同一个对象。

import oracle.aurora.memoryManager.Callback;
import oracle.aurora.memoryManager.EndOfCallRegistry;

class Example2 implements Callback{

   private Object cachedField = null;

   public void act(Object obj){
   // clear cached field
   cachedField = null;
}

//our accessor method

private static Object getCachedField(){
    if(cachedField == null ){
       // if cachedField is not filled in then we need to
       // register self, and fill it in.
       EndOfCallRegistry.registerCallback(self);
       cachedField=createCachedField();
    }
    return cachedField;
}

private Object reateCachedField(){

...

}
}

You can write this method to do whatever is appropriate for the particular purposes of the callback.

Threads

This section describes the Oracle Aurora Java virtual machine threading model. It discusses the important distinctions between the thread model the Java Language Specification describes, Oracle's implementation of Java language-level threads, and concurrent sessions running in the JServer.

The Java Language Thread Model

The Java Language Specification (JLS) describes two different thread models--cooperative and preemptive. A JLS-compliant Java virtual machine can implement either of these two models. Both have advantages and disadvantages.

To allow for flexibility in virtual machine implementations, certain aspects of the thread model are purposely vague in the JLS, such as when a thread can be interrupted and what operations may cause a thread to block.

Cooperative Threading

In a cooperative threading model, the Java virtual machine runs all Java threads on a single operating system thread, scheduling them in a round-robin fashion and switching between them only when they invoke the Thread.yield() method or when they invoke a service that blocks until ready, such as a wait on a network socket.

This thread model is portable and simple to program; it is also efficient to implement in the virtual machine because a thread switch does not require any system calls. It is also safer, as the virtual machine can detect a deadlock that would hang a preemptive virtual machine and can then raise a runtime exception. However, this thread model does not exhibit significant concurrency and will not take advantage of SMP hardware.

Preemptive Threading

In a preemptive model, the virtual machine assigns each Java thread to a separate operating system thread. Preemptive threads are scheduled by the operating system and provide better concurrency and throughput than cooperative threads. (This assumes that the operating system provides for real concurrency).

With preemptive threads, multiple concurrent operations can be outstanding, and the programmer need not be concerned with manually determining and coding yield points; but threaded programs are tricky to write, easy to deadlock, and amazingly difficult to debug in practice. A preemptive virtual machine cannot detect deadlock but can take advantage of SMP hardware.

True Concurrency

There is an added consideration for preemptive threads when the hardware supports multiple processors, and the operating system supports symmetric multiprocessing. In this case, the virtual machine can assign preemptive threads to separate processors, resulting in true concurrency. The advantage of this is that work is actually done in parallel on the separate processors; the disadvantage is that deadlock and concurrency errors are far more likely and are extraordinarily subtle and difficult to debug.

Java in the Server

A typical use of Java in the server is to create middle-tier application logic between the client application front-end and the database back-end. A server implemented fully in Java would require the application programmer to write both the application logic and the server logic to support it. The server logic that implements the socket listeners, thread scheduling, transaction model, and database access dominates the cost of the server development and dwarfs the actual application logic. Moreover, the same server logic is often duplicated over multiple server applications.

A major problem with implementing your own Java server is Java's lack of scalability. Because of the interpreted nature of Java bytecodes and the large memory footprint of the typical Java virtual machine, it is possible to run only a handful of simultaneous transactions before overwhelming the supporting hardware.

Java's thread-safe libraries also detract from the scalability of Java servers. Because most calls to Java collection classes are synchronized, regardless of whether the program actually uses multiple threads, there is significant overhead in the normal Java virtual machine for each call to a synchronized object. In a preemptive virtual machine, such synchronized calls require several expensive calls to operating system services to perform the synchronization.

The Oracle Java Threading Model

The Oracle server solves both the speed problem and the memory footprint problem. The Oracle server supports a highly scalable architecture external to the Java virtual machine. In contrast to the 6- to 8-MB memory footprint of the typical Java virtual machine, the Oracle server can create thousands of Java virtual machines, with each one taking less than 40KB each. Moreover, because each Java session runs completely independently of other sessions, Aurora uses a platform's SMP capabilities with nearly 100 percent efficiency.

Oracle's native compilation handles the speed problem. The Java server program, as well as all of the supporting Java classes, are compiled to C. The C programs are then compiled into shared libraries and are dynamically linked into the server kernel for execution.

Rather than implementing your own server in Java, the Oracle server provides the scalable framework for your application logic. The Oracle Java virtual machine complies with the JLS by supporting the cooperative threading model. Although this may seem to be disadvantageous, there is no need to use threads within the application logic because the Oracle server preemptively schedules the individual virtual machines. If you must support hundreds or thousands of simultaneous transactions, simply start each one in its own virtual machine. This is exactly what happens when you create a session on the JServer.

The normal transactional capabilities of the Oracle database server accomplish coordination and data transfer between the virtual machines.

The Oracle Thread Synchronization Mechanism

The Oracle Java virtual machine handles synchronized objects in an efficient way without any operating system calls. Every object header contains two bits--the locked bit and the contended bit. In addition, a hash table maps objects to monitor instances.

To acquire a monitor, the thread checks obj.locked. If it is not set, then the thread sets it, allocates a new monitor in the monitor table and continues. If obj.locked is set, then the thread checks whether it is the thread already holding the monitor. If it is, it simply increments a reference count; if not, the thread must sleep until the thread currently holding the monitor exits. Prior to sleeping, the thread can throw a deadlock exception if a circular wait condition occurs. Otherwise, the thread sets obj.contended, modifies its waiting_for slot to point at obj, and goes to sleep.

To release a monitor, the thread first decrements the reference count. If the count is zero, then it clears obj.locked. If obj.contended is set, it then finds all threads waiting for obj, and wakes them up. If no threads are waiting for obj, then obj.contended is cleared. This efficiency is in stark contrast to the numerous system calls that a preemptively Java threaded implementation must make.

Sockets, Remote Method Invocation (RMI), and O/S Resources

Operating system (O/S) resources are a limited commodity on any computer. Because Java is targeted at providing a computing platform, not just a programming language, it contains platform-independent classes and frameworks for accessing extremely platform-specific resources, such as files and sockets. The most basic O/S resource is memory. Aurora's memory manager manages it internally, allocating and freeing memory as you create new objects and as you no longer need them. As discussed elsewhere, automated storage reclamation, or garbage collection, is a key feature of the Java language. The language and class library support no direct means to allocate and free memory. On the other hand, Java contains classes that represent other O/S resources, such as files and sockets. Instances of these classes themselves hold on to operating system constructs, such as file handles, so it is important for you to understand how they interact with JServer's idea of call and session. Java's Remote Method Invocation (RMI) support makes use of Sockets, so we have included some discussion of how call and session also affect it.

Session Lifetime

When you connect to Oracle8i, you start a database session. The first time during this session that you invoke a Java method, a session-private Java virtual machine is created for the session. In reality, the Oracle implementation shares almost all of the code, infrastructure, and metadata of the active virtual machines between users. The appearance to each session, however, is that it has a completely private Java virtual machine. All Java objects created while a Java session is active are private to the session. All information static variables store (and by extension, any Java objects these static variables reference) remain valid for the lifetime of the session. A session ends when one of the following events occurs:

  1. The oracle.aurora.vm.OracleRuntime.exitSession() method is invoked.

  2. The session times out (optional for CORBA/EJB sessions).

  3. The user takes some action outside of Java code to end the database session.

Call Lifetime and Threads

You can invoke Java methods multiple times during a session. Each top level Java method invocation is a call. At the beginning of a call, one Java thread is executing. In most cases, this single thread will be the only thread that is necessary. In the single thread of execution case, the call ends when one of the following events occurs:

  1. The thread returns to its caller.

  2. An exception is thrown and is not caught in Java code.

  3. The System.exit(), oracle.aurora.vm.OracleRuntime.exitCall(), or oracle.aurora.vm.oracleRuntime.exitSession() method is invoked.

If the initial thread creates and starts other Java threads, then the rules about when a call ends are slightly more complicated. In this case, the call ends by one of the following two ways:

  1. The main thread returns to its caller, or an exception is thrown and not caught in this thread, and all other non-daemon threads complete execution. Non-daemon threads complete either by returning from their initial method or because an exception is thrown and not caught in the thread.

  2. Any thread invokes the System.exit(), oracle.aurora.vm.OracleRuntime.exitCall(), or oracle.aurora.vm.oracleRuntime.exitSession() method.

When a call ends because of a return and/or uncaught exceptions, Aurora throws a ThreadDeathException in all daemon threads. The ThreadDeathException essentially forces threads to stop execution.

When a call ends because of a call to System.exit(), oracle.aurora.vm.OracleRuntime.exitCall(), or oracle.aurora.vm.oracleRuntime.exitSession(), Aurora ends the call abruptly, terminating all threads, and throwing no ThreadDeathExceptions.

Refer to the previous discussion in "Threads" for more background on threading models and the specific implementation used in the Aurora Java virtual machine.

During the execution of a single call, a Java program may recursively cause more Java code to be executed. For example, your program may issue a SQL query using JDBC or SQLJ that in turn causes a trigger written in Java to be invoked. All of the preceding remarks regarding call lifetime apply to the top-most call to Java code, not to the recursive call. For example, a call to System.exit() from within a recursive call will exit the entire top-most call to Java, not just the recursive call.

O/S Resource Access

By default, a Java user has no direct access to most operating system resources. A system administrator may give a Java user access by granting the user JAVASYSPRIV. An alternative way to provide privileged access at a class level of granularity is to create the class using the -definer option to loadjava and specify a schema that has JAVASYSPRIV. Refer to the discussion in "Security" for more information on these roles predefined in the Java-enabled seed database and their use in restricting access to O/S resources.

O/S Resource Lifetime

You access O/S resources using the standard core Java classes and methods. Once you access a resource, the time that it remains active (usable) varies according to the type of resource. The system closes all files left open when a database call ends. As described previously, all threads are, by definition, terminated when a call ends.

Oracle provides socket class libraries to allow you, in certain circumstances, to use sockets across calls. Those circumstances depend somewhat on whether you are communicating with JServer through IIOP (you are using CORBA to communicate between client and server) or through the traditional Oracle TTC protocol. The latter case would apply to normal stored procedures, SQLJ, and JDBC, although of course, you can use SQLJ and JDBC to access SQL data within CORBA and EJB applications.

As described in the Oracle8i Enterprise JavaBeans and CORBA Developer's Guide, IIOP is implemented in JServer using a Presentation. In networking, the presentation layer is responsible for making sure data is represented in a format the application and session layers can accommodate. In Oracle, the term Presentation can refer to a service protocol that accepts incoming network requests and activates routines in the database kernel layer or in the Aurora Java virtual machine to handle the requests.

Earlier versions of the Oracle database server had a single service--the two-task common (TTC) layer. This service handled incoming Net8 requests for database SQL services from Oracle tools (such as SQL*Plus) and customer-written applications (using Forms, Pro*C, or the OCI).

Presentation-based services handle requests over TCP/IP routed to the presentation entrypoint by the listener and dispatcher. The IIOP services Oracle delivers with JServer are capable of starting, controlling, and terminating Oracle8i database sessions in the same way that an incoming TTC request from a tool such as SQL*Plus is capable of starting and terminating a database session.

Presentations form the basis for support of many additional protocols beyond IIOP. The Web server discussed in the section "Http Protocol Support and Web Server Demonstration" uses Presentations to support http protocol. In the current JServer release, Presentations are not available for your use. However, because they are implemented completely in Java, it is possible to allow user-defined protocols to communicate with JServer. For purposes of this discussion, however, it is important to understand that in the current JServer release, support for sockets that can be used across calls is available only when writing Java programs invoked through IIOP.

Sockets are created in one of the following ways:

Aurora uses the RDBMS virtual circuit facility for both client and server Sockets and VirtualCircuits when you execute Java code inside of a Presentation. Outside of a Presentation, Sockets use the underlying operating system sockets directly. You can only use VirtualCircuits inside of Presentations. ServerSockets use operating system sockets directly, both inside and outside of a Presentation.

Regardless of how you implement them, client sockets and all virtual circuits remain open until the session terminates. When the session is running in a dedicated server process, ServerSockets also remain open until the end of a session. However, because Presentations do not use dedicated processes, ServerSockets you create when running in a Presentation are closed at the end of a database call. Oracle encourages implementors to use server virtual circuits rather than ServerSockets when running inside of Presentations. Server virtual circuits remain open as long as you desire in a session. You should make direct use of operating system sockets only where scalability is not of concern. The Oracle8i server's ability to service many client requests is an integral part of VirtualCircuit support. Therefore, Oracle encourages developers to use Presentations to write Java code that functions as a server. Refer to the "Challenges In Developing a Scalable Java Platform" section in Chapter 2, "Java Platform for the Enterprise", for more information on the difference between writing a server in Java, as opposed to executing Java code in a server.

Java Objects as Resources

Each O/S resource the preceding section describes (files, threads, and sockets) has a Java class that represents it. Regardless of the usable lifetime of the object, the Java object associated with it is potentially valid for the duration of the session. This can occur, for example, if the Java object is stored in a static class variable or a class variable references it directly or indirectly. If you attempt to use one these Java objects after its usable lifetime is over, then Aurora throws an exception. For example, if an attempt is made to read from a java.io.FileInputStream that was closed at the end of a previous call, then java.io.IOException is thrown. Similarly, java.lang.Thread.isAlive() will be false for any Thread object running in a previous call and still accessible in a subsequent call.

Remote Method Invocation

JServer fully supports Java Remote Method Invocation (RMI). All RMI classes and java.net support are in place. In general, however, RMI is not useful or scalable in JServer applications. CORBA and EJB are the preferred means to invoke methods of remote objects. The RMI Server that Sun supplies does function on the JServer platform. However, because it uses operating system sockets and is not accessible through a Presentation, it is useful only within the context of a single call. It relies heavily on Java language level threads. By contrast, the Aurora/ORB and EJB rely on the Oracle8i server to gain scalability. Refer to the section "Challenges In Developing a Scalable Java Platform" in Chapter 2, "Java Platform for the Enterprise", for more information and to the section "Threads" in this chapter. Of course, you could efficiently implement an RMI server as a presentation, and certainly nothing prevents JServer customers from doing so; however, this purpose seems already reasonably well served by CORBA and EJB.

Java Native Interface (JNI) Support

The Java Native Interface (JNI) is a standard programming interface for writing Java native methods and embedding the Java virtual machine into native applications. The primary goal of JNI is to provide binary compatibility of native method libraries across all Java virtual machine implementations on a given platform. JNI was added to Java with the 1.1 release.

Oracle does not support the use of JNI in JServer applications. If you use JNI, then your application will not be 100% pure Java, and the native methods will require porting between platforms. Native methods have the potential for crashing the server, violating security, and even corrupting data.

Debugging Server Applications

JServer furnishes debugging capability in a pre-release form but does not formally support it. The debugging capability is useful for developers who can use the JDK's jdb debugger for debugging server-side code. Independent IDE vendors, as well as Oracle's JDeveloper, will be able to integrate their own debuggers with JServer using the capabilities present in the current release.

A key goal of Oracle8i is adherence to open Internet and Java standards. In keeping with that goal, the Aurora virtual machine includes an implementation of the sun.tools.debug.Agent Java debugging protocol. Note that some of the awkward aspects of usage of the pre-release debugging capabilities are due to our adherence to the limited capabilities available through this Sun-provided protocol. Vendors may implement extensions to the basic protocol. All such extensions available in the JServer environment Oracle provides will be publicly specified for use by all vendors.

Overview

In a non-client-server environment, you would be debugging a Java application executing on a virtual machine resident on your own workstation. Sun's jdb debugger operates on this principal. The debugger communicates with the executing virtual machine through a protocol. Both the debugger and the virtual machine understand the protocol, allowing you to display information about the state of the running Java program in the debugger. In the JServer, your Java program executes on a server. The server may reside on the same physical machine, but it will typically reside on a separate machine from the one on which you are executing the debugger.

To maintain the illusion that you are executing your local debugger, such as jdb, against a local virtual machine, we provide a DebugProxy Java class. The DebugProxy allows a standard debugger that supports the sun.tools.debug.Agent protocol to connect to it as if the program being debugged resided locally. The proxy forwards to the server the standard requests the protocol supports and returns the results to the debugger.

The steps for debugging are, using jdb as the example:

  1. Start the DebugProxy. The DebugProxy waits for a DebugAgent that executes in the server to make connections.

  2. Connect to the server (starting a session) and boot the DebugAgent on the server. In this prerelease version, there is no way to cause server-resident code to execute and "break"; that is, execute and remain indefinitely in a halted mode. Instead, when you start the DebugAgent, you must specify a timeout period for the DebugAgent to wait before terminating. When the DebugAgent starts, the DebugProxy displays a password to use when connecting a debugger.

  3. Start jdb using the password provided by the DebugProxy when the DebugAgent connected to it. Use jdb to suspend all threads of the Java program executing in the server. Proceed with debugging using jdb facilities.

The remaining discussion in this section provides examples and details of these steps.

Starting the Debug Proxy

The debug proxy is shipped as part of the aurora_client.jar file. In addition, other tools, such as the loadjava utility, are included in this jar file. The DebugProxy class serves as a bridge between Aurora virtual machines running in the Oracle8i server and client-side debuggers running on your workstation.

The first step in server debugging is to start a debug proxy, which will wait for connections from debug agents. Assuming the aurora_client.jar file is part of your CLASSPATH, you would invoke the DebugProxy as:


java DebugProxy 

You can also specify a particular port to wait on:

java DebugProxy -port 666

The proxy prints out its name, its address, and the port it is waiting on:

Proxy Name: yourmachinename
Proxy Address: aaa.bbb.ccc.ddd
Proxy Port: 666

Starting, Stopping, and Restarting the Debug Agent

Once a proxy is running, you can boot a debug agent to connect to the proxy from SQL*Plus. You must specify the IP address or URL for a machine running a debug proxy, the port the proxy is waiting on, and a timeout in seconds:

SQL> call dbms_java.start_debugging('yourmachinename', 666, 66);

The call will wait until the timeout expires before it completes, so give yourself enough time get your debugger running, but not so much as to delay your session if for some reason you cannot connect a debugger.

If an agent is already running, then Aurora stops it and starts a new agent. You can stop the debug agent explicitly as well:

SQL> call dbms_java.stop_debugging(); 

Once a debug agent starts, it runs until you stop it, the debugger disconnects, or the session ends.

You can restart a stopped agent, with any breakpoints still set. The call will wait until the timeout expires before it completes. You can also restart a running agent just to buy some seconds to suspend threads and set breakpoints.

SQL> call dbms_java.restart_debugging(66); 

Note that the preceding dbms_java calls are simply published entry points to static methods that reside in oracle.aurora.debug.OracleAgent class. In addition, you can start, stop, and restart the debug agent in Java code using the class oracle.aurora.debug.OracleAgent directly:

public static void start(
   String host, int port, long timeout_seconds);
public static void stop();
public static void restart(long timeout_seconds);

Connecting a Debugger

Each time a debug agent connects, the proxy starts a thread to wait for connections from a debugger. The thread prints out the number, name and address of the connecting agent, the port it is waiting on, and the port encoded as a password. Here, a specific port and password are provided for illustration only:

Agent Number: 1
Agent Name: servername
Agent Address: eee.fff.jjj.kkk
Agent Port: 2286
Agent Password: 3i65bn

You can then pass the password to a jdb-compatible debugger (JDK 1.1.6 or later):

jdb -password 3i65bn

Probably the first thing you should do in the debugger is suspend all threads. Otherwise, your start_debugging call might time out and complete before you get your breakpoints set.

If your code writes to System.out or System.err, then you may also want to use the dbgtrace flag to jdb, which redirects these streams to the debugging console:

jdb -dbgtrace -password 3i65bn

Just-in-Time" Debugging

Aurora takes any arguments to DebugProxy beyond the optional port as a command to execute. The agent port password is appended to the command. For instance, on Windows/NT, you can cause the proxy to execute jdb in a new console each time an agent connects to the proxy with:

java DebugProxy -port 666 start jdb -password

Redirecting Output on the Server

System.out and System.err print to the current trace files. To redirect output to the SQL*Plus text buffer, use this simple workaround:

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

The minimum (and default) buffer size is 2,000 bytes; the maximum size is 1,000,000 bytes. In the following example, the buffer size is increased to 5,000 bytes:

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

Output prints at the end of call.

For more information about SQL*Plus, see the SQL*Plus User's Guide and Reference.

Class.forName() on the JServer

The Java Language Specification provides the following description of Class.forName():

Given the fully-qualified name of a class, this method attempts to locate, load, and link the class. If it succeeds, then a reference to the Class object for the class is returned. If it fails, then a ClassNotFoundException is thrown.

The difference between the JDK implementation and JServer's implementation is tied to the words "attempts to locate". As discussed previously, JServer uses a resolver to define how it locates a class. The JDK uses the set of directory tree roots the environment variable CLASSPATH specifies. When you execute a method that calls Class.forName(), the resolver of the currently executing class (this) is used to locate the class. This behavior is generally convenient and results in what you would normally expect. However, if you specified different resolvers for different classes, you may not get the results you want.

The following Java code demonstrates how you can use the resolver of a specific class to resolve the class you would name in a call to Class.forName(). It is useful because Class.forName() does a lookup using the resolver of the class that calls it. In the JDK, a similar operation would use the class loader associated with another class; however, in the JServer, this does not work because the system class loader loads all classes, even when they use different resolvers.

import oracle.aurora.rdbms.ClassHandle;
import oracle.aurora.rdbms.Handle;
import oracle.aurora.rdbms.Resolver;

public class ForName {

		/**
		* The class that lookups are relative to.
		*/
		Class from ;

		/**
		* The resolver of from
		*/
		Resolver resolver;

		/**
		* Constructor.
		* @param from the Class that lookups should be relative to
		*/
public ForName(Class from) {
   this.from = from;
   ClassHandle fromHandle = ClassHandle.lookup(from) ;
   this.resolver = fromHandle.resolver() ;
   }

/**
* Lookup a name relative to the class supplied to the constructor.
	* Looking up relative to a class means using that class' resolver.
* @param name the fully qualified name of a class to be looked up
* @return the Class. 
* @exception ClassNotFoundException if the lookup fails to find a
* class. 
*/
public Class lookup(String name) throws ClassNotFoundException {
ClassHandle h = Handle.lookupClass(name, resolver);
if ( h == null ) {
   throw new ClassNotFoundException(name + 
            " not found relative to " + from);
      }
      return h.loadClass();
   }


/**
* Lookup the a name relative to the class.
* @param name the fully qualified name of a class to be looked up
* @param from the Class that the lookup is relative to.
* @return the Class found in the lookup. 
* @exception ClassNotFoundException if the lookup fails to find a
* class. 
*/
public static Class lookup(String name, Class from) 
      throws ClassNotFoundException 
   {
      return (new ForName(from)).lookup(name) ;
   }

/**
* A top level main to test ForName. It expects two arguments
* The name to be looked up and the name of a class that the
* lookup is to be relative to.
*/
public static void main(String[] argv) {
      System.out.println("looking up " + argv[0] 
            + " relative to " + argv[1]);
      try {
         Class cl = lookup(argv[0], Class.forName(argv[1]) );
	         ClassHandle h = null ;
         if ( cl != null ) {
            h = ClassHandle.lookup(cl);
         } 
         System.out.println("result is " + h ) ;
      } catch ( ClassNotFoundException ex ) {
            System.out.println(ex.toString());
      }
   }
}

The usage of the ForName class should be evident. To return the class known as "mypackage.myclass" using the resolver of ExistingClass, invoke:

Class myClass = (new ForName (ExistingClass)).lookup("mypackage.myclass")

The sample code also provides a top level main method to test the ForName class.

ByteCode Verifier and Resolvers Containing "-"

According to the Java Virtual Machine Specification, .class files are subject to a process called verification before the class they define is available in a virtual machine. In JServer, the verification process occurs at class resolution. If the verifier determines that the class is malformed, then the verifier does not mark it valid. When the verifier rejects a class, it issues an ORA-29545 error (badly formed class). The loading tool will report the error. In normal use, you will never see this error. It might happen, for example, if the contents of a .class file are not the result of a Java compilation or if the file has been corrupted.

In some situations, the verifier will allow a class to be marked valid but will replace bytecodes in the class to throw an exception at runtime. In these cases, the verifier issues an ORA-29552 (verification warning), which loadjava will report. loadjava issues such a warning when the Java Language Specification would require an IncompatibleClassChangeError be thrown. JServer relies on the verifier to detect these situations, supporting the proper runtime behavior the JLS requires.

Another situation in which the verifier issues a warning is the use of resolvers containing "-". This type of resolver marks your class valid regardless of whether classes it references are present. Because of inheritance and interfaces, you can and often want to write valid Java methods that use an instance of one class as if it were an instance of a superclass or of a specific interface. When the method being verified uses a reference to class A as if it were a reference to class B, the verifier must check that A either extends or implements B. For example, consider the potentially valid method below whose signature implies a return of an instance of B but whose body returns an instance of A:

B myMethod(A a) { return a; }

The method is valid if and only if A extends B or A implements the interface B. If A or B have been resolved using a "-" term, then the verifier does not know that this method is safe. It will replace the bytecodes of myMethod by bytecodes that throw an Exception if myMethod is ever called. Use of other resolvers, for example the default -oracleresolver, ensure that the class definitions of A and B are found and resolved properly if they are present in the schemas they specifically identify. The only time you might consider using the alternative resolver would be if you must load an existing jar file containing classes that reference other non-system classes not included in the jar file. Never use a resolver containing "-" if you later intend to load the classes that were causing you to use such a resolver in the first place. Instead, include all referenced classes in the schema before resolving. Refer to the Oracle8i Java Stored Procedures Developer's Guide for more information and to Appendix A, "Tools", of this Guide for a detailed discussion of resolvers.

How To Tell You Are Executing in the Server

It may be desirable for some reason to write Java code that executes in a certain way in the server and another way on the client. In general, Oracle does not recommend this. In fact, JDBC and SQLJ go to some trouble to allow you to write portable code that avoids this problem, even though the drivers used in the server and client are different.

If you must determine whether your code is executing in the server, then use:

System.getProperty ("oracle.server.version")

It will return a String when running in the server ("8.1.5" for the initial JServer release on Oracle 8i). Otherwise, it will return null.

Http Protocol Support and Web Server Demonstration

The ORACLE_HOME/javavm/demo.tar (or zip) file contains several code samples and demonstrations, including a demonstration Java Web Server running on Oracle8i. This is an example of a forthcoming feature that will allow you to define your own TCP/IP based protocols to communicate with the JServer. When you run the Web Server demo, you are communicating directly with Oracle8i through HTTP, not SQL*Net. Aurora uses the same flexible protocol support infrastructure to deliver IIOP and RMI-over-IIOP for use with the Aurora/ORB and EJB.

The Web Server demonstration runs portions of Sun's Java Web Server backend code, supporting Servlets and Java Server Pages directly within Oracle8i. You can write Servlets that use the JDBC server-side driver to access SQL data. Web browsers connect directly to Oracle8i with absolutely no intervening middle tier and can execute the Servlets and Java Server Pages.

The demonstration Web server uses a crude artifact to tie each Web browser to its own database session. It sends "Keep-Alive" headers in the HTTP replies to ask the Web browser to keep the connection to the server open. This kind of approach contributes to the Web Server's status as a "demo"--not for production use. For the current release, Oracle does not support the Web Server. Additionally, it is worth noting that it is not secure because all Servlets run as SYS.

Please see the README.TXT in the world-o-books directory in demo.tar for more information.