EJB 組件的一個比較常見的用途是在關系型數據管理領域。與 RMI 結合起來,EJB 組件讓您不必鑽研 JDBC 就可以從關系數據庫訪問數據。但這種抽象是要付出代價的:RMI 很慢,通常是極慢。那麼,竅門就是找到一種方法來保持 EJB 技術的所有優點而又沒有使用 RMI 的巨大開銷。在這篇技巧文章中,您將看到值對象(也稱為對象映射)是如何幫助您繞開最嚴重的 RMI 速度障礙的。您將首先從一個工作示例開始,然後了解代碼是怎樣工作的。
簡單實體 bean
考慮一個名為 DVDs 的簡單數據庫表。這個表有幾列:id、title、releaseDate、producer(通過外鍵)和 director(也是通過外鍵)。因為我們正在使用 EJB 組件,所以表由實體 bean 表示,並且每個列都有其自己的取值(Accessor)方法和賦值(mutator)方法。清單 1 顯示了我們的 DVD 表的遠程接口:
清單 1. DVD 遠程接口
import com.ibm.ejb;
import Java.rmi.RemoteException;
import Java.util.Date;
import Javax.ejb.EJBObject;
public interface DVD extends EJBObject {
public int getId() throws RemoteException;
public String getTitle() throws RemoteException;
public void setTitle(String title) throws RemoteException;
public Date getReleaseDate() throws RemoteException;
public void setReleaseDate(Date releaseDate) throws RemoteException;
public Person getProducer() throws RemoteException;
public void setProducer(Person producer) throws RemoteException;
public Person getDirector() throws RemoteException;
public void setDirector(Person director) throws RemoteException;
}
這裡的問題是如何訪問表數據,可能一次訪問所有數據。為進行一次 DVD 銷售或搜索,在線商店或清單應用程序很可能要求獲取上述列中包含的大多數或所有信息。為了訪問所有信息,應用程序為每列調用一個取值方法 ? 共有五次方法調用,每次都會多占用一點 RMI 通信時間。這種情況再加上其它復雜性,如可能的錯誤情況、網絡流量和相關問題,以及數據的指數級數量(大多數此類表有 15 行或更多),我們的應用程序隨時都可能崩潰。
這時就需要值對象。值對象是簡單的 Java 類,可以用它來表示多種對象,包括關系數據庫行中的數據。通過直接使用值對象,而不是反復使用 bean 的遠程接口,我們可以將 RMI 通信減少到一次方法調用。
創建值對象
清單 2 中的值對象看起來和我們的遠程接口幾乎相同,但它實際上是具體類。注:通常用 bean 的名稱加上 Info 來表示值對象。
清單 2. DVD 值對象
package com.ibm.ejb;
import Java.io.Serializable;
import Java.util.Date;
public class DVDInfo implements Serializable {
private int id;
private String title
private Date releaseDate;
private Producer producer;
private Director director;
public int getId() {return id;
}
void setId(int id) {this.id = id;
}public String getTitle() {return title;
}
public void setTitle(String title) {this.title = title;
}
public Date getReleaseDate() {return releaseDate;
}
public void setReleaseDate(Date releaseDate) {
this.releaseDate = releaseDate;
}
public Person getProducer() {
return producer;
}
public void setProducer(Person producer) {this.producer = producer;
}
public Person getDirector() {return director;
}
public void setDirector(Person director) {this.director = director;
}}
您應該看出這個類的兩個特點。首先,它實現了 Java.io.Serializable。任何可以被實體 bean(或任何其它 EJB 組件)返回的對象都必須滿足這個要求。其次,該類中沒有方法能夠拋出 RMI RemoteException。這個對象不需要 RMI 通信(這是本練習的全部要點!),因此不會發生 RemoteException。否則,值對象就成為 bean 的遠程接口的翻版了。
添加兩個新方法
創建值對象類是我們的 RMI 解決方案的第一個部分。第二個部分是將兩個有價值的方法添加到我們的遠程接口,如清單 3 所示:
清單 3. 已修改的 DVD 遠程接口
import com.ibm.ejb;
import Java.rmi.RemoteException;
import Java.util.Date;
import Javax.ejb.EJBObject;
public interface DVD extends EJBObject {
public DVDInfo getInfo() throws RemoteException;
public void setInfo(DVDInfo info) throws RemoteException;
public int getId() throws RemoteException;
public String getTitle() throws RemoteException;
public void setTitle(String title) throws RemoteException;
public Date getReleaseDate() throws RemoteException;
public void setReleaseDate(Date releaseDate) throws RemoteException;
public Person getProducer() throws RemoteException;
public void setProducer(Person producer) throws RemoteException;
public Person getDirector() throws RemoteException;
public void setDirector(Person director) throws RemoteException;
}
接下來,也是最後一步,在我們的 bean 的實現類中實現 getInfo() 和 setInfo() 這兩個新方法,如清單 4 所示:
清單 4. 已修改的 DVD 遠程接口
// Rest of class excluded for brevity
public DVDInfo getInfo() throws RemoteException {
DVDInfo info = new DVDInfo();
// Load value object with current variable values
info.setId(this.id);
info.setTitle(this.title);
info.setReleaseDate(this.releaseDate);
info.setProducer(getProducer());
info.setDirector(getDirector());
return info;
}
public void setInfo(DVDInfo info) throws RemoteException {
setTitle(info.getTitle());
setReleaseDate(info.getReleaseDate());
setProducer(info.getProducer());
setDirector(info.getDirector());
}
“魔術”是如何實現的
我們的應用程序需要能夠訪問來自 DVDs 表的 DVD bean 中的所有數據。但是,我們並不調用所有的五個取值方法,而是設置應用程序,只調用一個方法:getInfo()。這大大減少了我們的 RMI 通信。
bean 的實現類在“幕後”調用所有相同的取值方法。但是,因為它們在 EJB 容器中發生,所以它們是本地調用。但是,所有數據仍將傳到 bean 客戶機,因此仍可以使用這些數據。如果需要對數據進行任何修改,我們可以僅用 setInfo() 方法將它們傳回 bean,而不是使用四個或五個開銷很大的 RMI 調用。
這種方法唯一的缺點是:有獲得舊數據的輕微的風險。如果在內存中將值對象保持一段時間,您就會冒這種風險。雖然值對象包含數據庫中數據的快照,但它不能動態地反映數據的更改。避免舊數據的最佳方法是立即使用值對象;如果稍後您需要再次使用它,則應該花一次 RMI 開銷來再次調用 getInfo(),以確保可以使用最新數據。
在您的 EJB 應用程序中使用值對象可以產生極大的性能優勢,在具有多個取值方法的 bean 中尤其是如此。