【IT168 技术文章】EJB 组件的一个比较常见的用途是在关系型数据管理领域。与 RMI 结合起来,EJB 组件让您不必钻研 JDBC 就可以从关系数据库访问数据。但这种抽象是要付出代价的:RMI 很慢,通常是极慢。那么,窍门就是找到一种方法来保持 EJB 技术的所有优点而又没有使用 RMI 的巨大开销。在这篇技巧文章中,您将看到值对象(也称为 对象映射)是如何帮助您绕开最严重的 RMI 速度障碍的。您将首先从一个工作示例开始,然后了解代码是怎样工作的。
简单实体 bean
考虑一个名为 DVDs的简单数据库表。这个表有几列: id、 title、 releaseDate、 producer(通过外键)和 director(也是通过外键)。因为我们正在使用 EJB 组件,所以表由实体 bean 表示,并且每个列都有其自己的取值(accessor)方法和赋值(mutator)方法。清单 1 显示了我们的 DVD 表的远程接口:
清单 1. DVD 远程接口
2 import java.rmi.RemoteException;
3 import java.util.Date;
4 import javax.ejb.EJBObject;
5 public interface DVD extends EJBObject {
6 public int getId() throws RemoteException;
7 public String getTitle() throws RemoteException;
8 public void setTitle(String title) throws RemoteException;
9 public Date getReleaseDate() throws RemoteException;
10 public void setReleaseDate(Date releaseDate) throws RemoteException;
11 public Person getProducer() throws RemoteException;
12 public void setProducer(Person producer) throws RemoteException;
13 public Person getDirector() throws RemoteException;
14 public void setDirector(Person director) throws RemoteException;
15 }
16
这里的问题是如何访问表数据,可能一次访问所有数据。为进行一次 DVD 销售或搜索,在线商店或清单应用程序很可能要求获取上述列中包含的大多数或所有信息。为了访问所有信息,应用程序为每列调用一个取值方法 ― 共有五次方法调用,每次都会多占用一点 RMI 通信时间。这种情况再加上其它复杂性,如可能的错误情况、网络流量和相关问题,以及数据的指数级数量(大多数此类表有 15 行或更多),我们的应用程序随时都可能崩溃。
这时就需要值对象。值对象是简单的 Java 类,可以用它来表示多种对象,包括关系数据库行中的数据。通过直接使用值对象,而不是反复使用 bean 的远程接口,我们可以将 RMI 通信减少到一次方法调用。
创建值对象
清单 2 中的值对象看起来和我们的远程接口几乎相同,但它实际上是具体类。注:通常用 bean 的名称加上 Info 来表示值对象。
清单 2. DVD 值对象
2 import java.io.Serializable;
3 import java.util.Date;
4 public class DVDInfo implements Serializable {
5 private int id;
6 private String title
7 private Date releaseDate;
8 private Producer producer;
9 private Director director;
10 public int getId() {
11 return id;
12 }
13 void setId(int id) {
14 this.id = id;
15 }
16 public String getTitle() {
17 return title;
18 }
19 public void setTitle(String title) {
20 this.title = title;
21 }
22 public Date getReleaseDate() {
23 return releaseDate;
24 }
25 public void setReleaseDate(Date releaseDate) {
26 this.releaseDate = releaseDate;
27 }
28 public Person getProducer() {
29 return producer;
30 }
31 public void setProducer(Person producer) {
32 this.producer = producer;
33 }
34 public Person getDirector() {
35 return director;
36 }
37 public void setDirector(Person director) {
38 this.director = director;
39 }
40 }
41
您应该看出这个类的两个特点。首先,它实现了 java.io.Serializable 。任何可以被实体 bean(或任何其它 EJB 组件)返回的对象都必须满足这个要求。其次,该类中没有方法能够抛出 RMI RemoteException 。这个对象不需要 RMI 通信(这是本练习的全部要点!),因此不会发生 RemoteException 。否则,值对象就成为 bean 的远程接口的翻版了。
添加两个新方法
创建值对象类是我们的 RMI 解决方案的第一个部分。第二个部分是将两个有价值的方法添加到我们的远程接口,如清单 3 所示:
清单 3. 已修改的 DVD 远程接口
2 import java.rmi.RemoteException;
3 import java.util.Date;
4 import javax.ejb.EJBObject;
5 public interface DVD extends EJBObject {
6 public DVDInfo getInfo() throws RemoteException;
7 public void setInfo(DVDInfo info) throws RemoteException;
8 public int getId() throws RemoteException;
9 public String getTitle() throws RemoteException;
10 public void setTitle(String title) throws RemoteException;
11 public Date getReleaseDate() throws RemoteException;
12 public void setReleaseDate(Date releaseDate) throws RemoteException;
13 public Person getProducer() throws RemoteException;
14 public void setProducer(Person producer) throws RemoteException;
15 public Person getDirector() throws RemoteException;
16 public void setDirector(Person director) throws RemoteException;
17 }
18
接下来,也是最后一步,在我们的 bean 的实现类中实现 getInfo() 和 setInfo() 这两个新方法,如清单 4 所示:
清单 4. 已修改的 DVD 远程接口
2 public DVDInfo getInfo() throws RemoteException {
3 DVDInfo info = new DVDInfo();
4 // Load value object with current variable values
5 info.setId(this.id);
6 info.setTitle(this.title);
7 info.setReleaseDate(this.releaseDate);
8 info.setProducer(getProducer());
9 info.setDirector(getDirector());
10 return info;
11 }
12 public void setInfo(DVDInfo info) throws RemoteException {
13 setTitle(info.getTitle());
14 setReleaseDate(info.getReleaseDate());
15 setProducer(info.getProducer());
16 setDirector(info.getDirector());
17 }
“魔术”是如何实现的
我们的应用程序需要能够访问来自 DVDs 表的 DVD bean 中的所有数据。但是,我们并不调用所有的五个取值方法,而是设置应用程序,只调用一个方法: getInfo() 。这大大减少了我们的 RMI 通信。
bean 的实现类在“幕后”调用所有相同的取值方法。但是,因为它们在 EJB 容器中发生,所以它们是本地调用。但是,所有数据仍将传到 bean 客户机,因此仍可以使用这些数据。如果需要对数据进行任何修改,我们可以仅用 setInfo() 方法将它们传回 bean,而不是使用四个或五个开销很大的 RMI 调用。
这种方法唯一的缺点是:有获得旧数据的轻微的风险。如果在内存中将值对象保持一段时间,您就会冒这种风险。虽然值对象包含数据库中数据的快照,但它不能动态地反映数据的更改。避免旧数据的非常好的方法是立即使用值对象;如果稍后您需要再次使用它,则应该花一次 RMI 开销来再次调用 getInfo() ,以确保可以使用最新数据。