一、J2EE 技術簡介
J2EE是 SUN 公司提出的在分布式環境中的一種體系結構,它提供了一種基於組件的設計、開發、集成、部署企業應用系統的方法,J2EE平台提供了多層分布式的應用系統模型、重用組件的能力、統一的安全模型和靈活的事務控制。基於組件的J2EE企業應用系統具有平台獨立性,所以不受任何軟件產品和任何軟件廠家API的約束。
J2EE定義了下面的組件:
·application client 和 applets 是客戶層組件;
·Java Servlet 和 JSP 組件是WEB 層組件;
·Enterprise JavaBean(EJB) 組件是業務處理層組件。
EJB 技術是J2EE 體系一部分,EJB 組件是用 Java 語言編寫的,是可以被客戶端程序存取的可重用的服務器端組件,它運行在J2EE 服務器上,在客戶/服務器系統中,EJB 提供類似於中間件的服務。
J2EE 服務器提供應用系統系統級的服務,像事務管理、安全管理、數據庫存取等,開發人員不必自己開發系統級服務,所以可以集中精力開發應用系統中的業務邏輯處理;用EJB 組件處理業務邏輯。
二、EJB 組件簡介
EJB 組件分為兩類:Sesson bean 和 Entity bean
Sesson bean 代表 J2EE 服務器的客戶端,客戶端通過調用 Sesson bean 的功能和J2EE 服務器通信,Sesson bean 和客戶會話,可以認為是客戶端的擴展,例如:網上帳務系統的客戶可以調用 Sesson bean 的"輸入存款單"的功能來存入現金等。每一個Sesson bean 只能有一個客戶,當客戶終止時,與之相應的Sesson bean 也終止。因此Sesson bean 是暫時的,不可持久的。
Entity bean 代表業務處理對象,它存儲在持久的存貯機制如數據庫中,例如,一個Entity bean 代表一個帳戶存單,它是存儲在關系數據庫中存單表的一行。Entity bean 的信息不一定存儲在關系數據庫中,它也可以存儲在對象數據庫中、文件中或其他別的存儲機制中( 本例子中用關系數據庫)。
Entity bean 可以被多個客戶端共享。由於多個客戶端可能改變相同的數據,所以Entity bean 在事務管理下工作是很重要的。通常情況下,EJB 容器提供事務管理。開發人員可以在組件的部署描述文件中指定事務的屬性。每一個Entity bean 都有一個唯一的對象標識符,也叫主鍵,這個主鍵可以讓客戶端定位一個Entity bean。
Entity bean 的持久性可以被 Entity bean 自己管理,也可以讓 EJB 容器管理,Entity bean自己管理要求開發人員在Entity bean中提供數據存取代碼。例如客戶的Entity bean要調用 SQL 語句來通過 JDBC 存取關系數據庫。EJB 容器管理Entity bean持久性意味著 EJB容器自動處理數據存取的調用。
兩種類型的EJB 組件(Session bean 和 Entity beans)都可以存取數據庫。選擇哪一類 EJB 組件來存取數據庫依賴於具體的應用系統。
下面的情況可以在 Session bean 組件中調用 SQL 語句來存取數據庫:
· 應用系統相對簡單。
· SQL 語句返回的數據不能被多個客戶端共享使用。
· 數據不代表一個業務實體。
下面的情況要用Entity beans 組件:
· 超過一個客戶端使用數據庫調用返回的數據。
· 數據代表一個業務實體。
· 開發者想從 Sesson bean 中隱藏關系模型。
EJB 結構的這種靈活性可以讓開發人員用不同的方法來開發應用系統。
三、建立數據庫連接
EJB 容器維護數據庫的連接池,這個連接池對 EJB 組件來說是透明的。當EJB 組件申請一個連接時,EJB 容器從連接池中提取一個連接並分配給組件。由於EJB 容器只是分配一個連接給 EJB 組件,所以組件很快就獲得這個連接並連接數據庫。數據庫調用之後,組件就可以釋放連接,這樣它又可以快速申請到另一個連接。又因為一個組件只占用這個連接很短的時間,從而同一個連接可以被多個組件使用。
組件不是通過數據庫的絕對名來連接數據庫,而是用邏輯名連接到數據庫, 即用 JNDI lookup 來獲得數據庫連接,例如:在下面例子中的AccountEJB 類中,連接數據庫有以下幾個步驟:
1、指定數據庫的邏輯名:
private String dbName = "java:comp/env/jdbc/AccountDB";
2、獲得數據源:
InitialContext ic = new InitialContext();
DataSource ds = (DataSource) ic.lookup(dbName);
3、從數據源得到數據庫連接:
Connection con = ds.getConnection();
這種間接的連接數據庫有以下幾個優點:
· 可以在具有不同的數據庫名的不同環境中部署相同的 EJB 組件。
· 可以在多個應用中重用EJB 組件。
· 可以集成 EJB 組件到運行在分布環境的應用系統中。
另外,EJB 規范沒有要求 J2EE的實現支持某一個特別類型的數據庫系統,因此EJB 組件可以連接到不同的數據庫系統。
四、EJB 數據庫應用的例子
下面以一個簡單的例子 "銀行帳戶"應用系統來介紹 EJB 組件的Entity bean類型的組件存取數據庫。
Entity bean 的狀態存儲在關系數據庫的 ACCOUNT 表中,表ACCOUNT 由下面的SQL 語句創建:
CREATE TABLE ACCOUNT
(id VARCHAR(3) CONSTRAINT pk_account PRIMARY KEY,
firstname VARCHAR(24),
lastname VARCHAR(24),
balance DECIMAL(10.2));
和其他EJB 組件一樣,開發人員必須編寫 Entity bean 的 Entity Bean Class 代碼(AccountEJB.java)、Home Interface 代碼(AccountHome.java)及Remote Interface 代碼(Account.java)。
Entity Bean Class 代碼( AccountEJB.java)
EntityBean 接口方法
EjbCreate 方法:當客戶端調用 create 方法時,EJB 容器調用相應的ejbCreate 方法。一個Entity組件的ejbCreate 方法要實現下列工作:
· 插入Entity Bean 的狀態到數據庫中
· 初始化實例變量
· 返回主鍵。
AccountEJB的ejbCreate 方法調用insertRow 方法,而insertRow 方法發出一個 insert SQL 語句插入Entity Bean 的狀態到數據庫中,下面是 Account 類中ejbCreate 方法的源代碼:
sublic String ejbCreate (String id,String firstName,String lastName,double balance)throw CreateException{
if (balance < 0.00)
{
throw new CreateException("A negative initial balance is not allowed."); }
try {
insertRow(id,firstName,lastName,balance);
} catch (Exception ex) {
throw new EJBException("ejbCreate:"+ex.getMessage()); } this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.balance = balance;
return id; }
throws 子句可以包含 javax.ejb.CreateException 和別的應用系統中指定的例外處理例程(exceptions)。Entity Bean的狀態也可以通過非 J2EE 應用系統直接插入到數據庫中,如SQL 語言腳本插入一行數據到 ACCOUNT表中,盡管這條數據不是通過 ejbCreate 方法插入到數據庫中的,但Entity Bean 同樣能通過客戶端程序定位這條數據。
ejbPostCreate 方法
對於每一個ejbCreate 方法,開發人員必須在Entity Bean中寫一個ejbPostCreate 方法,EJB 容器在調用完ejbCreate 以後,就立即調用ejbPostCreate,和 ejbCreate 方法不一樣,ejbPostCreate 方法可以調用 getPrimaryKey 等方法,通常ejbPostCreate 方法是空的。
ejbRemove 方法
當客戶端通過調用 remove 方法來刪除一個 Entity Bean 的狀態數據時,EJB 容器調用 ejbRemove 方法,ejbRemove 方法從數據庫中刪除一個Entity Bean 狀態數據。代碼如下:
public void ejbRemove() {
try {
deleteRow(id);
} catch (Exception ex){
throw new EJBException ex("ejbRemove:"+exgetmessage());
}
}
如果ejbRemove 方法遇到系統級錯誤,將執行javax.ejb.EJBException. 如果遇到應用級錯誤,將執行javax.ejb.RemoveException。
entity bean 的狀態數據也可以通過數據庫的 delete 語句直接刪除數據。
EjbLoad 方法和 ejbStore 方法
EJB 容器需要維持 Entity Bean 的實例變量與數據庫中相應值的同步,這需要調用 ejbLaod 方法和ejbStore 方法。ejbLoad 方法用數據庫中的數據刷新變量的值,ejbStore 方法把變量的值寫入到數據庫中。客戶端不能調用 ejbLoad 方法和ejbStore 方法。
如果業務處理的方法關系到事物處理,EJB 容器要在業務處理方法調用之前調用 ejbLoad 方法刷新數據,業務處理方法執行之後,EJB 容器又立即調用 ejbStore 方法把數據存儲到數據庫中。因為 EJB 容器調用ejbLoad方法和ejbStore 方法,開發人員在業務處理方法中不必刷新和存儲實例變量的值。
如果ejbLoad 方法和ejbStore 方法不能在低層數據庫中定位 Entity Bean,將執行 javax.ejb.NoSuchEntityException。
在 AccountEJB 類中,ejbLoad 方法調用 loadRow 方法,loadRow 則發出一個 select 語句從數據庫提取數據分配給實例變量;ejbStore 方法調用 storeRow 方法,storeRow 方法則用 update 語句把實例變量的值存儲到數據庫。代碼如下:
public void ejbLoad(){
try{
loadRow();
}catch (Exception ex){
throw new EJBException ("ejbLoad:"+ex.getMessage());
}
}
public void ejbStore(){
try{
storeRow();
}catch (Exception ex){
throw new EJBException ("ejbStore:"+ex.getMessage());
} }
Finder 方法 :
Finder 方法允許客戶端查找 entity bean ,AccountClient 中有三種方法查找entity bean:
Account jones = home.findByPrimaryKey("836");
Collection c home.findByLastName("Smith");
Collection c home.findInRange(20.00,99.00);
Entity bean 類必須實現相應的方法,並且文件名以ejbFind 前綴開始,如:AccountEJB 類實現 ejbFindByLastName 的方法如下:
public Collection ejbFinfBylastName(String lastName)
throw FinderException {
Collection result;
Try {
Result = selectByLastName(lastName);
} catch (Exception ex) {
throw new EJBException("ejbFindByLastName" + ex.getMessage()); }
if (result.isEmpty()){
throw new ObjectNotFoundException("No row found.");
} else {
return result} }
應用系統中特定的 finder,如 ejbFindByLastName 和ejbFindInRange,是可選的,但是必須含有 ejbFindByPrimaryKey 方法,ejbFindByPrimaryKey 方法用主鍵作參數,用來定位一個 entity bean 的狀態數據,下面是 ejbFindByPrimaryKey 方法的代碼:
public String ejbFindByPrimaryKey(String primaryKey)
throws FinderException {
boolean result;
try {
result = selectByPrimaryKey(primaryKey);
} catch (Exception ex) {
throw new EJBException("ejbFindByPrimaryKey: " + ex.getMessage()); }
if (result) {
return primaryKey;
}
else {
throw new ObjectNotFoundException ("Row for id " + primaryKey + " not found.");
}
}
ejbFindByPrimaryKey 方法以 primaryKey 作為參數並返回它的值。
注意:
1.只有 EJB 容器可以調用ejbFindByPrimaryKey,而客戶端不能直接調用 ejbFindByPrimaryKey 方法,客戶端只能調用在 home 接口中定義的 findByPrimaryKey。
2.在 entity bean 類中開發人員必須實現ejbFindByPrimaryKey 方法。
3.一個 finder 方法的名字必須以 ejbFind 作為前綴。
4.返回值必須是主鍵或者是一個主鍵的集合。
throw 子句可以包含 javax.ejb.FinderException,和其他別的例外處理例程。如果一個finder 方法只要求返回唯一一個主鍵,如果主鍵不存在,則應該執行 javax.ejb.ObjectNotFoundException,ObjectNotFoundException 是FoundException的一個子類;如果 finder 方法要求返回的是一個主鍵的集合,則應該執行 FinderException 來處理。
業務處理方法
業務處理方法包含想在 Entity Bean 中實現的業務處理邏輯。通常業務處理方法不存取數據庫,這允許開發人員可以把業務處理邏輯從數據庫存取中獨立出來。
在 AccountEJB entity bean 中包含下面的業務處理方法:
public void debit(double amount)
throw InsufficientBalanceException {
if (balance - amount <0){
throw new InsufficientBalanceException();
}
balance -= amount;
}
public void credit (double amount) {
balance += amount;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public double getBalance() {
return balance;
}
AccountClient 程序中調用業務處理方法:
Account duke = home.create("123","Duke","Earl",0.00);
Duke.credit(88.50);
Duke.debit(20.25);
Double balance = duke.getBalance();
注意:
1、業務處理方法的名稱不能和 EJB 體系中定義的方法的名稱沖突,其他的要求和 entity bean 和 sesson bean 中其它方法的要求相同。
2、可以在throw 子句中包含應用程序定義的例外處理例程,如 debit 方法執行InsufficientBalanceException。為了識別系統級錯誤,業務處理邏輯應該調用 javax.ejb.EJBException。
下面是對 AccountEJB 類中存取數據庫的總結:
因為業務處理方法中不需要存取數據庫,所以在 AccountEJB 類中的業務處理方法沒有存取數據庫。但業務處理方法可以通過 EJB 容器調用 ejbStore 來修改實例變量。當然開發人員也可以在 AccountEJB 類的業務處理方法中存取數據庫,這依賴於應用程序的具體要求,在存取數據庫之前必須連接數據庫。
Home 接口(Interface)
在home 接口中定義讓客戶端創建和查找 entity bean 的方法。Account Home 接口如下:
import java.util.Collection;
import java.rmi.RemoteException;
import javax.ejb.*;
public interface AccountHome extends EJBHome {
public Account create(String id, String firstName, String lastName, double balance)
throws RemoteException, CreateException;
public Account findByPrimaryKey(String id)
throws FinderException, RemoteException;
public Collection findByLastName(String lastName)
throws FinderException, RemoteException;
public Collection findInRange(double low, double high)
throws FinderException, RemoteException;
}
在Home 接口中,每一種finder 方法必須和 entity bean 類中的 finder 方法對應;finder 方法的名稱必須以 find 開始,就象 entity bean 類中的 finder 方法必須以 ejbFind 一樣,例如:AccountHome 類中定義的findByLastName 方法和AccountEJB 類中實現的 ejbFindByLastName 方法。
Remote 接口
Remote 接口定義客戶端可以調用的業務處理方法,Account remote 接口的代碼如下:
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface Account extends EJBObject {
public void debit(double amount)
throws InsufficientBalanceException, RemoteException;
public void credit(double amount)
throws RemoteException;
public String getFirstName()
throws RemoteException;
public String getLastName()
throws RemoteException;
public double getBalance()
throws RemoteException; }