程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 將存儲過程封裝為EJB組件的方法

將存儲過程封裝為EJB組件的方法

編輯:關於JAVA

集成 Web 應用服務器和數據庫管理 (DBMS) 技術是很多新型商業應用的常見需求。在本文中,我們將討論該集成的一個方面:如何在會話 Enterprise JavaBeans (EJB) 組件中設計與開發封裝或調用現有 DBMS 存儲過程的方法。您應該熟悉 EJB 技術、結構化查詢語言 (SQL) 和 Java 數據庫連接 (JDBC) 的基本知識,以便充分理解本文。

如果您正致力於需要訪問或修改在 DMBS 中數據的 Web 應用程序開發,那麼可能已經在向基於 EJB 的設計轉移。您可能會發現,通過使會話 EJB 組件利用 DBMS 存儲過程,可以減少編碼和維護工作,並可能提高數據訪問性能。

一些公司多年來一直在使用存儲過程(stored procedure),很大程度上是因為它們可以幫助減少網絡通信量,並提高分布式計算環境中的性能。通常,這些過程包含涉及多數據庫操作的重要業務邏輯。遠程應用程序調用這些過程,在 DMBS 服務器上執行它們所包含的 SQL 語句。當然,過程結束時,所有結果都返回給應用程序。

這些舊有存儲過程對 Web 應用通常是有用的。與其在 EJB 組件中復制這些邏輯,為什麼不將這些過程作為方法封裝在會話 bean 中呢?這樣可以避免 DBMS 服務器和 EJB 組件中的冗余代碼 -- 在考慮開發、調試和維護開銷時,冗余代碼將損耗開發效率。這還可能帶來提高性能的好處。調用存儲過程可以減少 EJB 組件原本不得不發出的 SQL 語句數量,從而減少與遠程 DBMS 的通信開銷。

入門

現在明白為什麼要從會話 bean 調用存儲過程了吧,下面我們看看如何開始。首先,需要使用適當的開發環境,該環境應該包括一個帶有內置 EJB 支持的 Java 開發工具,一個 Web 應用服務器和一個關系 DBMS。我的參考配置包括 VisualAge for Java 企業版 3.0.2,WebSphere Application Server 高級版 3.0.2.1,以及 DB2 V7.1,所有這些都安裝在一個 Windows NT 系統上。有關如何配置該環境以支持本文所述工作的詳細信息,請參閱 "Leveraging DBMS Stored Procedures through Enterprise JavaBeans"(位於參考資料中)或參考產品手冊。

有了正確的軟件環境,就可以開始了。雖然我們要討論的編碼模式可能適合於無狀態會話(stateless session) bean,但它也可使用有狀態會話(stateful session) bean 組件。但是,因為無狀態會話 bean 比有狀態會話 bean 消耗的系統資源更少,而且涉及的代碼也更少,所以通常建議使用無狀態會話 bean。

首先要考慮的設計問題是如何在存儲過程和 EJB 組件之間映射數據。存儲過程可能需要多個輸入、輸出和輸入/輸出參數,並返回一個或多個結果集(代表數據行)。除非要對不同類型的過程使用不同的編碼模式,您需要編寫 EJB 組件以便處理所有這些可能性。

處理輸入(或者輸入/輸出)參數很簡單:將存儲過程需要的每個參數映射成 EJB 組件的輸入參數。但是,處理存儲過程的輸出比較棘手。可能有多個輸出參數和多個結果集要傳回調用程序,需要將這些作為一個可序列化的對象返回,以符合 EJB 規范。可以編寫自己的類,使其可以將這些數據打包成一個對象,並在該對象中包括所有必須的元數據。(該元數據將描述對象的內部結構,以便客戶機知道如何處理。)但這需要大量工作。

如果正在使用 VisualAge for Java 和 WebSphere,那麼,有個更好的選項:使用它們的數據訪問 Bean (DAB) 庫。該庫包含一些提供位於基本 JDBC 之上的函數層的類。可能會發現 com.ibm.db.CallableStatement 類特別方便,因為它允許創建一個可序列化的對象,該對象包含所有從存儲過程返回的輸出,包括多個結果集(如果有的話)和相關元數據。還有一個好處是,該庫設計成可以支持任何支持 JDBC 的數據源,因此,它可以使 bean“與 DBMS 無關”。有了 DAB 庫,就可以用一個編碼模式在會話 EJB 組件中封裝任何存儲過程。甚至可以在 EJB 客戶機中使用一個通用的編碼模式,來處理任何從封裝器方法返回的結果。

回顧開發任務

我們來討論一下使用通用編碼模式,來集成 EJB 組件和 DBMS 存儲過程的步驟:

確定要將哪個存儲過程封裝成 EJB 方法。如果該過程不存在,則遵循 DBMS 標准過程來創建和調試。

確定要使用哪個無狀態會話 EJB 組件。如果該 EJB 不存在,則遵循 Java 開發環境的標准過程來創建和調試。

擴展 EJB 組件的遠程接口,以包括用於封裝存儲過程的新方法。

擴展 EJB 組件的實現,以包括封裝存儲過程的新方法的邏輯。連接到數據庫、調用存儲過程、處理所有結果集和處理所有異常是後面要解決的問題。

通過構建一個客戶機應用或 Servlet,來調用 EJB 組件封裝器方法,以測試所做的工作。

頭兩項是基本編程任務,您可能已經熟悉。根據所用產品的不同,個別步驟可能會略有不同,但是大多數產品都有工具來提供幫助。例如,如果正在使用 VisualAge for Java 和 DB2,則可以利用“存儲過程構建器”來完成步驟 1,以及利用 EJB 開發特性來完成步驟 2。本文將不集中講述頭兩步。但是,其余三步需要詳細講解。本文將在一個實際示例的環境下討論這些步驟中的每一步。

浏覽應用方案

假設需要構建一個應用,該應用支持一家公司的市場營銷分部,該分部維護面向金融的 Web 站點。該站點允許人們注冊為客戶,跟蹤他們的投資總額,以及在電子公告板上發表意見。另外還假設,支持該站點的數據存儲在 DB2 的表中。以下代碼樣本顯示如何創建這些表。

在 DB2 中創建樣本表的 SQL 語句

create table client (
id int not null primary key,
name varchar(30),
email varchar(30),
phone varchar(12),
regdate date,
mktg char,
constraint check1 check (mktg in (‘y‘, ‘Y‘, ‘n‘, ‘N‘))
)
create table portfolio (
id int not null,
clientID int not null references client,
ticker varchar(10) not null,
cost decimal (9,2),
qty int,
date date,
primary key (id, clientID, ticker)
)
create table boards (
msgno varchar(15) not null primary key,
subject varchar(40),
date date,
clientID int not null references client
)

該數據庫還包含一個特別重要的存儲過程。過程 CLIENTREPORT 提供注冊站點用戶的綜合概要,包括用戶投資和他們在公告板上討論的問題。這個報告還包括客戶名稱和電子郵件地址,以便在用戶提出有關有潛在價值的附加產品或服務方面的建議時,市場營銷人員可以與這些用戶聯絡。要在會話 EJB 組件中封裝的就是這個過程。

因為此過程可能用多種語言(包括 Java 編程語言)編寫,這裡就不顯示它的完整內容。不管怎麼說,源代碼確實不那麼重要,因為您不能假設總是可獲得它們。但是,為了告訴您存儲過程有什麼內容,這裡顯示了它包括的三個 SELECT 語句:

CLIENTREPORT 存儲過程中的 SQL 語句:

select name, e-mail from client where id = ?

select id, ticker, cost, qty, date from portfolio where clientid = ?

select msgno, subject, date from boards where clientid = ?

問號表示,該語句將依賴運行時來自調用程序的輸入,在這種情況下,調用程序必須提供一個代表感興趣的客戶標識的有效數據值。通過這些語句,您可以猜出,存儲過程將需要一個輸入參數(用於客戶標識),返回兩個輸出參數(用於客戶名稱和電子郵件地址),並返回兩個結果集(一個包含有關客戶投資總額的數據,另一個包含有關客戶公告板發表內容的數據)。

修改 EJB 組件的遠程接口

現在我們來開始 EJB 組件代碼工作。

既然要使封裝器方法對 EJB 組件客戶機可用,我們需要擴展 bean 的遠程接口。將使用一個名為 Analysis 的無狀態會話,並包括一個 lookupClient 方法,以用於存儲過程封裝器。該方法需要一個整數作為輸入,以代表要報告的客戶標識,它返回一個 DAB CallableStatement 對象(位於 com.ibm.db.* 包中)。將把該過程返回的任何異常轉換成 RemoteExceptions(這適用於與 EJB 1.0 兼容的會話 bean)。

以下編碼示例顯示了 EJB 組件遠程接口的修改部分。

EJB 組件遠程接口

// Enterprise JavaBean Remote Interface for Analysis session bean
public interface Analysis extends javax.ejb.EJBObject {
// remote interface for our lookupClient method
com.ibm.db.CallableStatement lookupClient(java.lang.Integer clientId)
throws java.rmi.RemoteException;
. . .
}

請注意,如果使用 VisualAge EJB 組件向導,則無需對此進行手工編碼。替代方法是,在 bean 的實現類中對此方法編碼之後,通過菜單項來將該方法提升(promote)到 bean 的遠程接口,然後,將自動添加必需的代碼。

編碼存儲過程封裝器方法

現在可以集中講述 bean 實現類本身,將在該實現類中包括調用存儲過程的代碼,並將其所有輸出作為 com.ibm.db.CallableStatement 對象返回。包含調用 CLIENTREPORT 存儲過程的 lookupClient(...) 方法的完整實現。將在後續章節中詳細講解每個代碼塊(參考代碼中的注釋)的邏輯,以便您更好地理解如何為自己的存儲過程實現類似的方法。

連接到數據庫

讓我們更詳細地查看此代碼的各部分。

在調用存儲過程之前,需要建立一個到 DBMS 的連接。有兩種方法做得到:使用 1.0 樣式的連接,或者使用 JDBC 2.0 樣式的 DataSource。在 WebSphere 環境中,通常選用後者,因為它提供連接池(connection pooling),這可以更有效地使用系統資源。出於這種原因,我們的編碼模式使用 DataSource。

除了確定要建立的連接類型之外,還應該考慮要將連接邏輯放在 bean 中的什麼地方。有多個選擇:

直接放在封裝器方法(wrapper method)中

放在私有輔助方法(helper method)中

放在 ejbCreate() 方法中(並將相應的斷開邏輯放在 ejbRemove() 方法中)

這些方法的利弊超出了本文的范圍。為簡單起見,樣本代碼將所有連接/斷開邏輯直接放在方法中。

代碼塊 1 顯示了在使用 VisualAge for Java 3.0.2 和 WebSphere 3.0.2.1 時,如何使用 DataSource 進行連接。我們創建了一個散列表,在其中填充適合於 WebSphere 環境的值,然後建立一個 InitialContext。代碼的以下幾行利用該初始上下文和 Java 命名和目錄接口 (JNDI) 服務,來獲得期望的 DataSource 的索引,我們以前在 WebSphere 中用“管理控制台”創建了該 DataSource。本例中的 DataSource 名為 LocalDB2Sample。下一步,使用該 DataSource 來獲得一個連接,並向其傳遞合適的數據庫用戶標識和口令。從連接池獲得連接之後,可以將該信息提供給 DAB DatabaseConnection 對象,來設置它所需的連接規范。最後,將 autoCommitMode 設置成 false,因為 EJB 組件負責處理事務管理服務。

測試時,在 VisualAge for Java WebSphere 測試環境中運行使用 DataSource 的 EJB 組件會很方便。有關如何在產品發行版 3.0.2 中這樣做的指示,請參閱 David Zimmerman 所著的 "Creating DataSources in the VisualAge for Java WebSphere Test Environment"(在參考資料中)。

調用存儲過程

建立了連接之後,可以集中講述如何調用存儲過程了。如封裝器方法編碼示例中的代碼塊 2 所示,首先創建一個 DAB StatementMetaData 對象,該對象中有存儲過程的規范。下一步,定義要執行的 SQL 語句。在這裡將要調用 CLIENTREPORT 過程,該過程需要一個輸入參數(用於客戶標識)和兩個輸出參數(用於客戶名稱和電子郵件地址)。下一步,將參數添加到規范中。對於每個過程參數,都指定了參數名,其數據類型及其參數模式。

代碼塊 3 創建即將執行的 DAB CallableStatement 對象。CallableStatement 代表可用來執行存儲過程的 SQL。創建完對象之後,將其元數據設置成在代碼塊 2 中指定的形式。然後將 DatabaseConnection(在代碼塊 1 中創建)與該 CallableStatement 關聯。

下一個任務很簡單:需要執行 CallableStatement 對象,這將使 DBMS 運行存儲過程。但是,在這樣做之前,必須通過 EJB 客戶機應用程序,將過程的輸入參數設置成傳入方法的值。代碼塊 4 中顯示了這種邏輯。

檢索存儲過程的輸出並返回到調用程序

在封裝器方法編碼示例的代碼塊 5 中,將檢索存儲過程返回的輸出參數。想起來了嗎?這些參數代表 Web 站點客戶的名稱和電子郵件地址。但是,不需要顯式地檢索存儲過程返回的結果集。(這些結果集包含有關客戶投資總額和公告板發表信息的數據)。您可能要問:為什麼會這樣呢?

某些 DBMS 要求,在獲得任何輸出參數值之前,要從存儲過程返回的結果集檢索所有需要的值。由於這種要求,在通過 getParameter() 方法進行特別請求之前,CallableStatement bean 不從數據庫獲得任何輸出參數,因為何時從結果集檢索數據是由用戶控制的。缺省情況下,在執行存儲過程之後,將自動檢索結果集,並將其存儲在高速緩存中。但是,必須顯式檢索輸出參數,並將其存儲在高速緩存。

檢索完輸出參數之後,將 DAB CallableStatement 返回給 EJB 組件的調用程序。該對象現在包含過程返回的所有輸出(包括結果集),和幫助調用程序正確分析對象語法的適當的元數據。當我們查看調用會話 bean 封裝器方法的樣本客戶機應用程序時,將看到如何去做。

如果您熟悉 JDBC,可能會問:為什麼不在此代碼塊中顯式發出 commit 語句。確實,如果使用的是 JDBC 1.0 樣式的連接,可能需要(否則,當在 "finally" 塊中關閉數據庫連接時,將逆序恢復所做的工作)。但是,使用 DataSource 並接受 WebSphere 缺省的 EJB 組件屬性 (TX_REQUIRED),WebSphere 將自動為我們的工作提供事務管理。因此,不再需要顯式的 commit 語句。

處理異常與關閉打開的資源

當然,在執行會話 bean 時可能會出錯。因此,需要提供異常處理。代碼塊 6 包括適合於與 EJB 1.0 兼容的 bean 的簡單異常處理程序。它只是捕獲遇到的任何異常,包括一個適當的錯誤消息,並將異常作為新的 RemoteException 拋回給調用程序。

另外,該代碼塊還包含一個 "finally" 塊,以確保關閉所有打開的資源。在這裡,釋放任何與 CallableStatement 對象關聯的資源。下一步,除去在工作中所用的任何對連接的 DAB 引用。最後,確保關閉 WebSphere 連接。

構建客戶機應用程序

構建了 EJB 封裝器方法之後,該集中講述客戶機應用程序了。與 EJB 組件一樣,首先展示客戶機應用程序的完整代碼樣本。然後,將詳細講述個別代碼塊。

這裡顯示的客戶機應用程序 -- ClientAnalysis -- 使用 RMI/IIOP 與 EJB 組件通信。其工作很簡單:創建會話 bean,調用它的 lookupClient(...) 方法,處理該方法返回的 DAB CallableStatement 對象,然後除去 bean。將該應用程序編寫成處理 CallableStatement 的通用客戶機,即,假設事先不知道有關 CallableStatement 內部結構的任何信息。相反,我們嚴格依賴其中包含的元數據,來分析對象的語法,並使用其相關組件,如過程返回的輸出參數和結果集。這種方法演示了通用的編碼模式,可以在處理 CallableStatement 的任何應用程序中使用。就這樣,它補充了在無狀態會話 EJB 組件中對封裝存儲過程所用的通用編碼模式。

創建 EJB 組件並調用其封裝器方法

客戶機應用程序的代碼塊 1 以 main(...) 方法開始。它指定感興趣的客戶標識,並調用一個私有輔助方法,來獲得正在使用的會話 EJB 組件。執行完 bean 之後,調用 lookupClient(...) 方法。這是封裝 CLIENTREPORT 存儲過程並返回 DAB CallableStatement 的方法。

需要詳細講述私有輔助方法 -- createEJB()。因為 EJB 組件創建工作可能會根據所用的 Web 應用程序而略有不同,所以,選擇將這個工作隔離成單獨的方法。特別是,由於與該上下文相關的特定屬性將會改變,所以,獲得 JNDI InitialContext 的方法可能不同。

該 createEJB() 方法創建一個散列表,然後用適合於軟件環境的值填充。下一步,創建一個新的 InitialContext 對象,該對象用於通過 JNDI 服務獲得對 EJB 組件的遠程引用。因為從 JNDI 上下文返回 JNDI(這是在 IIOP 之上使用 RMI 的編碼需求),所以,限制了該遠程引用。獲得 EJB 組件宿主之後,創建一個無狀態會話 bean,然後將其返回給客戶機應用程序的 main 方法。

處理返回的對象

客戶機應用程序的代碼塊 2 處理 EJB 組件返回的 DAB CallableStatement 對象。首先定位與 CallableStatement 關聯的根元數據對象。因為 CallableStatements 可以獲得多個結果集,所以,多個 StatementMetaData 對象可以鏈接在一起,並包括在 CallableStatement 中。而鏈的根總包含描述 SQL 語句的元數據和相關參數,因此,這就是我們的開始之處。這允許我們獲得 CallableStatement 中包括的參數數目。返回的數目將包括過程的所有 IN、INOUT 和 OUT 參數。通過使用循環,可以處理所有參數並打印每個參數的相關信息,包括參數名、相應的 Java 類和模式(指明 IN、INOUT 或 OUT 模式的數字)。

下一步,查看結果集並處理它們。首先,確定 CallableStatement 對象中包括的結果集數目。通過使用循環,可以獲得每個用 DAB SelectResult 對象表示的結果集。然後,使用另一個私有輔助方法 processRS(...) 來處理結果集。processRS(...) 方法確定傳遞給 SelectResult 並包含在其中的行和列的數目。假設有一些行存在,它使用嵌套循環來打印有關所有行中的所有列的信息。該信息包括列名和它的值。

目前為止,客戶機應用程序的工作幾乎完成。代碼塊 3 除去會話 bean,打印一行表明已完成,然後終止。當然,在代碼塊 3 之後的代碼處理任何遇到的異常。在這裡,只打印一個堆棧跟蹤。

總結

希望您已理解會話 EJB 組件如何利用封裝在舊有 DBMS 存儲過程中的商業邏輯。這樣做的其它可能的好處包括:減少 EJB 服務器和 DBMS 之間的網絡通信量,提高生產力,以及降低總體軟件維護成本。如果遵循本文中列出的編碼模式,則無論與過程相關的參數或結果集如何,您都可以將任何類型的存儲過程作為方法封裝在無狀態會話 bean 中。而且,您將可以使用通用編碼模式來調用任何這樣的 EJB 組件,並處理它返回的對象,而不必事先知道該對象的內部結構。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved