在一个后台使用关系数据库的ejb系统中,如果客户端只需要使用表格形式的用户界面来显示数据,那么怎样才能高效的存取,表格化服务器端的数据呢?
在分布式计算中,一个最常用的用例就是为客户端显示服务器端的静态数据,这些数据通常是只读的,在客户端很少要更新。举个例子:一个应用程序要显示大批量的数据到客户端,这些数据是只读的,在html表格中显示可能如下:
---------------
Employee
|
Department
------------------------------
Adam Berman |
Development
Ed Roman
|
Management
Clay Roach
|
Architecture
------------------------------
在服务器端,我们会将数据模型化为一个Employee实体Bean和一个Department实体Bean.具体过程如下:在session Facade模式下调用getEmployees()方法。这需要先在home接口上调用finder方法,返回所有的employee,对每个employee查找其对应的Department Entity Bean.然后利用这两个实体Bean获取的数据,创建一个数值对象视图,session bean将此数值对象的雇员/部门集合返回到客户端:
public class EmployeeProjectViewObject {
public String employeeName;
public String employeeTitle;
...
public String departmentName;
public String departmentLocation;
...
}
根据不同的ejb server和应用程序,这个过程会存在很多问题:
1)n+1次 Entity Bean的数据库调用问题
意思是:对BMP和某些CMP,从N个entity Beans中获取数据会需要n+1的数据库调用,对一个好的CMP实现来说,应该允许批量加载,而一个开发者应知道这样会存在可怕的问题。
n+1问题的产生过程:为了从N个entity bean中获取数据,首先必须调用finder方法(作为一次数据库调用),在一个finder方法执行之后或者在一个商业方法调用之前,容器为每个entity bean执行ejbLoad(),这就是说,每个entity bean都要调用ejbLoad()方法(每个entity bean执行一次数据库调用),这样一个简单的数据库查询操作就需要n+1次数据库调用。每一次的数据库调用都会在连接池中临时锁定数据库连接。通常分布式系统的数据库一般都单独放在一台机器上,这样每次的数据库往返操作都会增加一次网络调用。整个系统的速度就受到了影响。对我们这个例子来说,运行这个例子实际将需要2n+1次数据库调用(1个查找finder,n个employee ejbLoad(),n个department ejbLoad()).
2)远程调用过多
如果要经过entity bean的远程接口,对n个employee & department数据来说就需要3n次远程调用
◆为每个employee的getvalueObject有n次调用
◆................getDepartment
........
◆......department的getvalueObject有n次调用
在获取每个数值对象集后,session bean将把这些数值对象绑定到EmployeeProjectViewObject上。
3)简单的联结操作(join operation)过于繁重。对这个例子来说,不管是BMP还是CMP,都要实例化许多的entity bean,和处理entity bean之间的交互。设想一个稍微复杂一点的情况,就是需要显示employee及相关的department,project & company,这样就不是仅仅增加几十行代码的问题,由于要增加数据库调用和远程调用,整个系统的速度将大大的降低。
4)需要创建一个数值对象层,使用entity bean层进行数据库操作,需要实现value Objects,Subset value Objects & View value Objects.这样增加了代码的复杂性降低了可维护性。在我们的这个例子中,我们创建了数值对象视图,来封装从deployee bean & department bean中获得的数据,并且把他们匹配发送到客户端。请阅读"Generic Attribute Access"来进一步了解数值对象(value Object)的使用。
当客户端只是浏览数据时,使用entity bean层的优点就不是很明显。使用本地接口和一个好的CMP实现将明显得减少以上这些问题。但是BMP开发者就不会如此幸运。在BMP中,只能通过使用entity bean缓存来减少这样的问题,而这只能用在单个EJB server部署上,并且要求数据库的结构在ejb之外不能改变。否则的话,你只好忍受这些问题了。
还有既然客户端需要列表显示数据,这样将数据库中的数据转换为对象的优点也就体现不出来了,因为还要将对象的数据在客户端列表化。
鉴于以上问题:
在BMP中,使用JDBC从关系数据库取出数据,使用RowSets将数据列表到客户端,在使用Entity Bean来进行更新操作。
作为一个通常的笨拙的处理,你需要为客户端列出数据,然后你在session facade模式下使用jdbc将比使用Entity Bean好的多。更重要的是,RowSets提供了一个清晰的和实际的方法从JDBC记录集取出数据。然后直接列出到客户端,而不需要先将数据转化为数值对象再将数据列出到客户端。
直接使用jdbc而不使用Entity Bean对许多开发者来说是不可思议的,自从Entity Bean出现以来,人们就一直在为此而争论。毕竟,Entity Bean 很好的封装了数据和数据逻辑,隐藏了持续管理的细节(比如:你不需要知道你所使用的数据库是什么),使整个系统的商业概念模型化,使用了容器的许多特性,比如:连接池,并发性,事务等。如果使用一个非面向对象的方法,似乎是一种退步。但是同任何其他的设计模式一样,有好处也有坏处,这些将在后面讨论,不过我们先来看一下RowSets.
在ejb2.0中,RowSet 是一个接口,是javax.sql.ResultSet的子类。RowSet的特殊实现允许你将ResultSet数据包装并排列显示到客户端,客户端可以直接操作RowSet记录集合字段。jdbc2.0把这种实现称作CachedRowSet.CachedRowSet并不连接数据库,一旦它从ResultSet中把数据复制过来,就可以断开与数据库的连接,此时,CachedRowSet就存放了sql查询的结果集。
对我们这个例子,使用RowSet,我们可以将一张表里的所有数据一次性的存到一个对象中,并且发送到客户端。下面的图形显示了RowSet方法与数值对象方法的区别
doc1.html
在客户端,从RowSet里取出的数据可以直接对应到表格的行和列。
使用JDBC和RowSet的优点:
1)RowSet为所有的查询操作提供通用的接口
2)简单的查询操作没有事务过载
3)利用缓存中的数据库
4)只返回你想要的数据
5)用户所要的数据只通过一次大的调用
缺点:
1)用户需要知道数据库表的字段名
2)商业逻辑与持续逻辑之间的紧密耦合
3)不是面向对象的
4)在编译期间不检查查询结果。
在数值对象中调用getXXX()而用RowSet在客户端需要调用getString("XXX")
5)有可能产生bug,可维护性差
最后,这个模式并不是说不要用Entity Bean,只说说当用户只需要临时列表显示数据时,还有更高效的方法。在这个模式中,jdbc 和 RowSet用来进行列出数据操作而entity bean责负责更新操作。特别注意:尽管RowSets也可以进行更新操作并且可以保证与数据库的同步,但是在应用程序中永远都不要这么用!
尽管在客户端列表显示数据时,商业/数据概念的完整性以及和其他商业概念的联系显得不是很重要,但是当进行更新操作时,这些概念就非常重要。Entity Bean封装了数据以及对数据的"操作规则"。当更新Entity Bean的属性时,Entity Bean有可能需要进行有效性验证并更新同一个应用程序中其他的Entity Bean。
举个例子:对一个应用程序中的Book和Chapter Entity Bean,当你修改Chapter Entity Bean章节标题时,Chapter将检验新的标题并且调用Book Bean让它进行修改,然后Book Bean有可能在调用其他的Entity Bean进行相应的修改操作。
从Session Facade通过jdbc/RowSets进行更新操作迫使开发者书写将商业逻辑和复杂的数据逻辑混在一起的代码。所有的某个特殊的商业概念(business concepts)要求的规则(rules),关联和验证都必须以更新记录和表的形式出现。
JDBC and RowSets for reading采用的是Session Facade模式,与value Object 和 Generic Attribute Access模式是不一致和不兼容的。value Object 和 Generic Attribute Access还可以进行更新操作