天天看點

利用Java存儲過程簡化資料庫操作

 利用Java存儲過程溝通SQL、XML、Java、J2EE和Web服務。

存儲過程(stored procedure)允許将運作于資料庫層中的持久性邏輯與運作于中間層中的商務邏輯有效地分離開來。這種分離可以降低整個應用程式的複雜性,并提供其重用性、安全性、性能和可伸縮性。

但是,妨礙存儲過程廣泛采用的一個主要障礙是不同資料庫廠商使用各種專有的、且依賴于資料庫的實作語言。使用基于Java的存儲過程可以解決這一問題。Oracle已經實作了ANSI标準,這些标準規定了從SQL中将靜态Java方法作為過程或函數進行調用的能力。這種實作被簡單地稱作"Java存儲過程"。

在本文中,你将了解基于Java的存儲過程如何幫助簡化商務邏輯、提高其性能,并擴充資料庫的功能。本文将介紹Oracle如何在資料庫内啟用基于Java的存儲過程。還會介紹Java存儲過程如何通路資料,以及如何建立基本Java存儲過程。

選擇PL/SQL還是Java

在考慮Oracle存儲過程時,你可能會想到PL/SQL。不過,從Oracle8i開始,Oracle已經在資料庫中支援Java,進而為存儲過程提供了不同于PL/SQL的開放式和可移植的方法。我可以聽到"$64 000問題":"我如何在PL/SQL和Java之間做出選擇?我是否應當忘記已經學習的所有PL/SQL相關知識,而變為一個Java天地的新手?"

兩種語言都适用于資料庫程式設計,都有自己的優點和弱點。在決定選擇哪一種語言時,可以參考下面根據經驗得出的通用規則:

  • 對于要求與SQL進行無縫內建的資料庫中心來說則邏輯使用PL/SQL,進而完成對資料庫對象、類型和特性的通路。
  • 出于與資料庫的無關性考慮時,可以選擇Java作為開放式的語言來取代PL/SQL,同時也為了內建和溝通SQL、XML、J2EE和Web服務等各個領域。

OralceJVM使得Java可以運作在資料庫中

從Oracle8i版本1(Oralce8.1.5)開始,Oracle便提供緊密內建的Java虛拟機(JVM),JVM支援Oralce的資料庫會話期結構。任何資料庫對話期都可以在第一Java代碼調用時啟動一個虛拟上專用的JVM,後續的使用者可以使用這一已經存在的支援Java的會話期。事實上,所有會話共享同一JVM代碼并保持"僅靜态"的私有狀态,而垃圾則收集在單個對話期空間内,進而為各個Java對話期提供了和SQL操作相同的對話期隔離和資料完整性能力。這裡,不需要為了資料完整性而進行單獨的Java支援的過程。這一基于對話期的結構提供了較小的記憶體占用率,并使OracleJVM具有與Oracle資料庫一樣的線性SMP可伸縮性。

建立Java存儲過程

要将Java方法轉換為Java存儲過程需要幾個步驟,包括:用loadjava實用程式将Java類加載到資料庫中,利用調用規範(Call Spec)釋出Java方法,将Java方法、參數類型和傳回類型映射到其SQL的對應部分。下面部分說明如何完成這些步驟。

我将使用一個簡單的Hello類,它有一個方法Hello.world(),傳回字元串"Hello world":

public class Hello

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

Loadjava 實用程式

Loadjava是加載Java源檔案、Java類檔案和Java資源檔案的實用程式,它可以用來驗證位元組碼,并将Java類和JAR檔案布置到資料庫中。它既可以通過指令行調用,也可以通過包含于DBMS_JAVA類中的loadjava()方法調用。為了加載我們的Hello.class示例,輸入:

loadjava -user scott/tiger Hello.class
      

從Oracle9i版本2開始,loadjava允許通過為包含在被處理的類中的方法建立相應的Call Specs來自動将Java類釋出為存儲過程。Oracle為開發、測試、調試和布置Java存儲過程提供了Oracle9i JDeveloper。

The Resolver Spec

基于JDK的JVM在列于CLASSPATH中的目錄中查找類引用,并對其進行解析。因為Oracle資料庫類存在于資料庫模式中,是以OracleJVM利用資料庫解析器(resolver)通過列于Resolver Spec中的模式查找并解析類引用。與CLASSPATH不同(CLASSPATH可以應用于所有的類),Resover Spec根據每類的情況進行應用。預設解析器首先在加載類的模式中搜尋類,然後在公共同義詞(public synonyms)中搜尋。

loadjava -resolve <myclass>
      

你可能需要指定不同的解析器,也可以在使用loadjava時強制進行解析,進而在布置時确定可能在以後運作時發生的任何問題。

loadjava -resolve -resolver "((* SCOTT) (foo/bar/* OTHERS) 
(* PUBLIC))"
      

Call Spec和存儲過程調用

為了從SQL中調用Java方法(以及從PL/SQl和JDBC中調用),必須首先通過Call Spec釋出公共靜态方法,它為SQL定義方法采用的參數以及傳回的SQL類型。

在我們的例子中,我們将利用SQL*Plus連接配接到資料庫,并為Hello.world()定義一個頂級Call Spec:

SQL> connect scott/tiger
SQL> create or replace function helloworld return
VARCHAR2 as language java name 'Hello.world () return
java.lang.String';
 /
Function created.
      

可以像下面這樣調用Java存儲過程:

SQL> variable myString varchar2[20];
SQL> call helloworld() into :myString;
Call completed.
SQL> print myString;

MYSTRING
---------------------
Hello world
      

Java存儲過程可以通過其Call Spec從以下各項中進行調用:SQL DML語句(INSERT, UPDATE、DELETE、SELECT、CALL、EXPLAIN PLAN、LOCK TABLE和MERGE)、PL/SQL塊、子程式、程式包以及資料庫觸發器。Call Spec的美妙之處在于存儲過程實作可以從PL/SQL轉換為Java,反之亦可,這一點對于請求者是透明的。

Call Spec從實作語言中(PL/SQL或Java)中抽象出調用界面,因而使之能夠在原有應用程式和新的基于Java/J2EE的應用程式之間共享商務邏輯。但是,在從Java客戶程式調用在資料庫駐留的Java類時,你可能不希望通過PL/SQL包裝器(wrapper)。在以後的版本中,Oracle計劃提供一種機制,它可以使開發人員略過Call Spec。

進階資料通路控制

Java存儲過程可用于控制和限制對Oracle資料的通路,其方法是隻允許使用者通過存儲過程管理資料,而存儲過程在其調用者的權限内執行,而不能對表本身進行通路。例如,你可以在特定時間内禁止更新資料,或者使管理者隻具有查詢工資資料的權利,而不能進行更新,或者記錄所有的通路并通知某一安全機構。

原有應用程式與J2EE應用程式之間的資料邏輯共享

因為原有應用程式與J2EE應用程式都通過Call Spec調用存儲過程,是以J2EE和非J2EE應用程式可以共享相同的資料邏輯。由于有了Call Spec,是以不用考慮所用的是何種實作語言(無論是PL/SQL還是Java),該資料邏輯都可以共享。

為BMP實體Bean自動生成主關鍵字

在對EJB實體bean應用BMP時,一個bean執行個體可以由自動生成的與新插入的資料相關聯的主關鍵字惟一确定,它是ejbCreate()的傳回值。可以利用一個插入相應資料的存儲過程在一個資料庫操作中檢索ejbCeater()中的該值,并檢索或計算主關鍵字。作為另一種方法,也可以利用JDBC3.0的RETURN_GENERATED_KEYS特性,以一個SQL語句插入該資料并檢索相應的關鍵字(或ROWID)。但是,存儲過程方法在各個JDBC驅動器版本和資料庫之間更具可移植性。

可以用以下三個步驟實作這一模式:

  1. 建立一個Java存儲過程,在公共GenPk類中定義一個公共靜态Java方法insertAccount()。此方法将插入資料、計算惟一的關鍵字(通過發出一個序列号),并傳回計算出的關鍵字作為主關鍵字。
  2. 定義Call Spec
    CREATE OR REPLACE PROCEDURE insertAccount(owner IN
    varchar, bal IN number, newid OUT number)
    AS LANGUAGE JAVA NAME 'GenPK.insertAccount(
    java.lang.String [])';
    /
          
  3. 在ejbCreate()内調用存儲過程
    Public AccountPK ejbCreate(String ownerName, int balance) throws CreateException
    {
       try {
         CallableStatement call = conn.prepareCall{ 
         "{call insertAccount(?, ?, ?)}"};		
               return new AccountPK(accountID);
       }
    }
          

為CMP實體Bean定制主關鍵字查找器

查找器方法(Finder methods)用于檢索已存在的EJB實體bean執行個體。主關鍵字查找器使你能夠檢索惟一辨別的EJB執行個體。對于CMP實體bean,EJB容器根據聲明描述,自動生成主關鍵字查找器findByPrimaryKey()方法。但是,在某些情況下,可能需要更多的控制,例如可能需要專門的查找器,如findByStoredProcKey()。在這些情況下,你可以結合使用Java存儲過程和對象關系架構(如Oracle9i應用伺服器[Oracle9iAS] TopLink)來實作定制的主關鍵字查找器方法。在将EJB查找器定義為REDIRECT或NAMED查找器後,TopLink将生成一個SQL查詢用于檢索bean執行個體。

資料驅動的EJB調用

在資料驅動體系結構中,商務邏輯調用可以作為資料庫操作(如插入、更新或删除)的結果來觸發。實作該資料邏輯的Java存儲過程可以被聲明為資料庫觸發器,用以調用運作于中間層J2EE應用伺服器的EJB。EJB的調用既可以采用J2EE1.3相容的伺服器通過Interoperable Inter-ORB Protocol(IIOP)标準遠端方法調用(remote method invocation,RMI)實作,也可以通過銷售商特定的傳輸協定(如Oracle9iAS/Oc4J的ORMI,或者通過BEA WebLogic的T3)用RMI來實作。每個應用伺服器提供商在提供基于IIOP的RMI,以提供互操作性的同時,都有其自己優化的協定。Oracle9iAS同時支援基于IIOP的RMI調用和基于ORMI協定的RMI調用。

資料驅動的消息傳送

Oracle9i資料庫嵌入了Advanced Queuing(AQ,進階排隊),它是一種內建的、穩定、可靠、安全、可擴充和事務處理式的消息排隊架構。Oracle通過标準的Java消息傳送系統(Java Messaging System,JMS)API為Java開發人員提供AQ功能。Java存儲過程可以通過JMS接口調用AQ操作,進而能夠實作快速、在會話期内、可擴充的、資料驅動的消息傳送。

Java存儲過程可以利用JMS調用AQ操作。可以用以下4個步驟實作這一模式:

  1. 建立并啟動JMS Queue(為此,可以将以下一些操作嵌入SQL腳本内):
    execute dbms_aqadm.create_queue_table(queue_table =>
    'queue1', queue_payload_type =>
    'SYS.AQ$_JMS_TEXT_MESSAGE', comment => 'a test queue', 
    multiple_consumers => false, compatible => '8.1.0');
    
    
    execute dbms_aqadm.create_queue( queue_name  => 'queue1',
     queue_table => 'queue1' );
    
    execute dbms_aqadm.start_queue(queue_name => 'queue1');
          
  2. 建立Java存儲過程(代碼摘錄如下):
    public static void runTest(String msgBody)
    {
     try
        {
      // get database connection
         ora_drv = new OracleDriver();
    
         db_conn = ora_drv.defaultConnection();
    
      // setup sender (cf online code sample)
        ..
      // create message
         s_msg = s_session.createTextMessage(msgBody);
    
      // send message
         sender.send(s_msg);
         s_session.commit();
    
      // receive message
         r_msg = (TextMessage) receiver.receive();
         r_session.commit();
    
      // output message text 
         String body = r_msg.getText();
         System.out.println("message was '"+body+"'");
      ..}
    } 
    
          
  3. 建立Call Spec:
    create or replace procedure jmsproc (t1 IN VARCHAR) 
    as language java name 'jmsSample.main (java.lang.String[])';
    /
          
  4. 調用存儲過程:
    call jmsproc('hello');
          

資料庫輔助的Web釋出(緩沖失效)

各應用程式結構必須面對的一個共同問題是如果可靠地将資料庫資訊進行緩存,以提高整個系統的性能。JCACHE是一種即将公布的标準規範(JSR 107),它可以解決這一問題。它說明了一種對Java對象臨時在記憶體中進行緩存的方法,包括對象的建立、共享通路、假脫機(spooling)、失效、各JVM的一緻性等。它可被用于緩存JSP内最經常讀取的資料,如産品目錄和價格清單。利用JCACHE,多數查詢的反應時間會因為有緩存的資料而加快(内部測試表明反應時間大約快15倍)。

為了跟蹤原始資料的所有變化,并重新整理已緩存的資料,Java存儲過程會作為一個觸發器被附加在一個表上。這個表的任何變化都會自動調用該存儲過程,後者再調出一個已定義的JSP使JCACHE對象失效,該對象将其狀态映射到該資料庫表。在失效時,緊跟其後的查詢将強制緩存器根據資料庫的資料進行更新。

擴充資料庫的功能

在資料庫中直接運作Java代碼的一個妙處就在于要實作新的功能,隻需要簡單地加載代碼或庫,并利用Call Spec制作可用于SQL、PL/SQL、Java、J2EE和非Java API的進入點(公共靜态方法)。Oracle9i資料庫使用者可以很容易地擴充資料庫功能。Oracle自己利用這種能力來獲得新的應用程式和工具包,如XML Developer Kits(XDKs)。

溝通SQL、PL/SQL、Java、J2EE、.NET和XML

Oracle XDK是用Java編寫的,并将其公共方法可用作Java存儲過程,進而擴充了資料庫的XML可程式設計能力。SQL、PL/SQL、Java、J2EE和非Java(.NET)商務邏輯都能夠通路XML分析器、XSLT處理器、XPath引擎和XML SQL Utility(XSU)。

XML分析器可以通過xmlparser和xmldom包進行通路。XSU是一種Java實用程式,它可以由SQL查詢結果或JDBC ResultSet生成XML文檔,并将XML文檔中的資料寫入資料庫表或視圖中。利用XSU,XML輸出可以輸出為文本、Dom樹或DTS。通過dbms_xmlquery和dbms_xmlsave包,XSU即可用于PL/SQL。

結論

Oracle資料庫與Java VM的內建可以建立可移植、功能強大和與資料庫無關的資料邏輯和持續性邏輯(persistence logic)。運作于中間層的商務邏輯和運作于資料庫層的資料邏輯之間的分離提高了應用程式的可擴充性、靈活性和可維護性。