摘要 Value List Handler(值列表处理器)是一种大家熟悉的处理大型数据库结果的设计模式。然而,在该模式的实现过程中也有很多要考虑的折衷。这里有一些实用技巧可使这种模式更好发挥作用,尤其是在大规模的J2EE应用程序中。
许多 J2EE应用程序都要求显示作为用户查询结果的数据库记录清单。这些查询操作通常涉及到在服务器端处理大型数据集。例如,在包含数千种产品的在线产品目录中进行的典型查找过程。
在并发客户数量增加的情况下,处理大型结果集的应用程序将面临可伸缩性和性能方面的问题。根据用户提出来的查询条件执行查询过程时,很可能没有办法预测会有多少数据返回。这个结果记录集可能包含庞大的数据量,这样您不能盲目的把查询结果返回客户,否则将导致意外的性能问题。
为了管理大型数据库返回记录集,人们经常使用Value List Handler这种设计模式。J2EE Blueprint 推荐使用这种模式来控制搜索、缓存查找结果并把查找结果提供给客户。在这一解决方案中,当客户需要搜索数据时,Value
List Handler就被调用,它截取客户搜索请求并把结果记录集以分页方式迭代返回。
ValueListHandler提供有查询执行功能和结果缓存。它实现了一个Iterator接口(见图1)。

图 1
ValueListHandler是一个集合类,它包含一组表示数据库记录的Transfer Object。在这个设计模式中,该处理程序并不返回整个结果清单,它只返回数据的一个子集。在一个生产应用程序中,根据Business
Service Fa?ade模式,ValueListHandler总是隐藏在fa?ade后面。
针对Value List Handler模式,J2EE Blueprint 描述了一些常用的实现策略。例如,它推荐在性能存在问题的时候使用Data
Access Object (DAO) 而不是实体bean。然而,当实现一个特别的应用程序时,开发人员需要考虑进一步的设计折衷,以便满足特殊的功能和性能要求。
在本文中,我们介绍一种Value List Handler模式的实现,该模式已经在一个大型的WebLogic项目中得到验证。您可以采用这种方法来开发下述情况的应用程序:
· 有数千并发用户使用。
· 必须分页显示部分用户搜索结果。
· 假定分页在各请求之间是一致的,即预期底层数据在滚动结果集时是不改变的。
案例研究
一个旅游零售商决定开发一个基于Web的系统,以便旅游代理可以直接访问众多旅游提供商信息和预定系统。该系统将被世界范围内的数千个旅游代理所使用。代理可以从超过1亿种费用全包旅游和超过5百万种最终旅游合同中进行选择,而且这些都是每日更新的。该系统允许用户搜索一个所需的全包旅游,预定房间和合适的航班,然后登记和跟踪客户预定。
该网站必须支持下面的用例:
· 旅游代理输入一个客户的请求,如旅游目的地、宾馆设施、房间家具设施和航班时间等。
· 根据代理提供的要求,网站将生成旅游选项。
· 客户选定一个旅游选项然后代理作出预定。
· 通过提供一个信用卡号,代理完成该事务。
第二个用例要求在存储全包旅游的数据库中运行复杂的查询。在这样的数据库中进行搜索可能产生长度极其不一的结果集。根据旅游目的地、宾馆种类、房间家具设施和航班时间,人们可以找到一个数目少或多的选项。然而,旅游代理并不想浏览整个结果清单。她/他宁愿在缩小了查询标准后重复进行搜索操作。
从而,就要求根据客户的偏好对结果清单进行排序,然后在给定的位置将其断开。零售商们估计只需要显示前面的数百个全包旅游服务就可以了。
另外一个要求就是在核查可用性时避免在线预定系统超载运行。因此,结果清单不能被立即核查,而是以成批的方式(如最多20个全包旅游)被核查。结果是,一个设计方案是基于Value
List Handler模式实现按页迭代器。在这种情况下,我们可以假设分页在各个请求中是一致的。换句话说,在滚动返回的结果集时,数据库中的底层数据并不发生变化。
由于该应用程序要求具有一个带有复杂工作流程的完善GUI,另外一个设计方案是使用applet技术开发一个富客户端。因此,需要使用客户端方法跨多个请求管理会话信息(会话标识符)。
服务器端的应用系统的设计遵守面向服务的架构(SOA)原则。大量的方法群集在服务层,并且客户端可以通过一个服务对象(一个EJB会话bean)来访问这些方法。例如,会话bean
HolidayPackageServices 提供搜索和预订旅游系列服务。
为了在组件之间传输数据,需要使用设计模式 Data Transfer Object (DTO)。然而,为了最大限度降低控制层频繁变化而产生的影响,我们决定使用动态数据传输对象(DDTO)。
该本系统是利用BEA WebLogic Server 8.1 SP1和一个用于存储与旅游系列服务相关的数据的Oracle 数据库来实现的。应用程序通过HTTP(S)上的SOA来访问旅游经营者的在线预定系统。客户(applet)通过WebLogic
Server提供的Web 服务基础设施来访问服务器端服务。这通过使用服务器工具是很容易实现的,这些工具只要求很少或者不要求编写代码就能生成网络服务接口。图2概要表示了应用程序架构。

图 2
问题
因为应用程序需要管理数千个并发客户,我们决定使用无状态方法。然而,如果要对每一个分页请求都进行重复,数据库中的搜索操作就会占用太多的资源。无论何时当查询结果集太大而没有办法在一个批中返回给客户时,就用一种设计决策来将搜索结果存储在数据库中。
旅游系列服务由一个对象(这些对象称为package项)图来表示。在最初的实现中,package作为使用一个对象到关系映射(O/R映射)的完整对象图存储在数据库中。遗憾的是,负载和性能测试很快表明响应时间太差,这主要是应用服务器和数据库服务器之间的太多的交互而引起的。每一“存储查询结果”的请求都会导致数百次的package
项记录的“写”操作。
改进后的解决方案
由于以上描述的性能问题,最初的Value List Handler模式不得不进行改变。主要的思想是最大限度减少“写”操作的次数。我们先把所有的holiday
package项存储在一个Oracle BLOB字段,接下来的改进就是以成批页面形式写入结果集,每一批作为一个BLOB。最后我们决定把整个结果集(也就是实际取出来的部分)只用一次“写”操作就存储在一个BLOB字段中。
根据用户提供的搜索标准执行完查询操作后,最初的N个记录就被取出来(如果存在),并被存储在一个动态数据传输对象清单(DDTOListt)中。数值N可以配置而且在运行时也可以进行更改。当所返回记录的实际数目大于页面大小时,就将DDTOList序列化并作为单一记录存储在HOLI
-DAY-_PACKAGE_BAG 表中(请查阅表1)。当然,还应实现一种清理机制。
| 列名称 |
数据类型 |
备注 |
| ID |
NUMBER(16) |
主键 |
| RESULT_SET |
BLOB |
序列化含有查询结果的DDTOList对象 |
| CREATION |
TIMESTAMP DATE |
记录创建的时间戳 |
|
表 1:用于存储结果集的数据库表
在这个解决方案中,含有结果集的记录每次在客户请求下一页时就被读一次,这就意味着实际所读取的数据量比需要的要多。但是,我们发现“读”操作比“写”操作的开销要少。
最终,进一步的负载和性能测试证实“一次写入(one-write)”解决方案的性能比最初的实现提高了一个数量级。
内部详情
让我们更仔细地看一下该实现方案,然后讨论一个典型用例。
在销售旅游服务时,旅游代理会询问消费者所期望的休假目的地、宾馆设施和房间家具设施等。在提供了所要求的全部信息后,旅游代理向系统发出请求,然后等待第一页的holiday
package。随后他把结果清单给消费者看,请他作出选择,但是可能没有一个选项能够打动该消费者。这种情况下,旅游代理或者请求系统给出下一页或者更改搜索标准并重复搜索请求。
从技术的观点看,该用例可分解为两个序列图,我们将在下一节讨论它们。
场景 1:获得第一页的holiday package
该场景(见图 3)显示了当定义客户的请求并启动搜索操作时的控制流。

图 3
消息 (1):首先,由applet收集getHolidayPackages请求所需的全部信息。客户端的工作流仅在输入一组有效的搜索标准时支持用户。该数据存储在一个动态数据传输对象(DDTO)中。
消息 (2):在填充DDTO后,applet使用一个局部存根对象调用Web服务方法getHoliday-Packages。该存根将DDTO序列化到一个SOAP消息并通过HTTPS将其发送到服务器。
消息 (3):当服务器收到SOAP消息时,它将其有效负载反序列化并将数据存入一个DDTO。然后它调用无状态会话bean的HolidayPackageServicesBean的getHolidayPackages方法(为了方便叙述,我们跳过了会话跟踪和安全及授权问题)。
消息 (4):现在一个DAO对象使用该DDTO中所含的搜索标准构建了一个select语句。在这一流程中,按照用户偏好和业务规则对结果集进行排序。
消息 (5):DAO对象从数据库获得头N个记录(数值 N 是可配置的),如果存在,则将它们存储一个DDTOList对象中,当DAO读完记录后,就关闭数据库连接。
消息 (6):创建一个惟一的holidayPackageBagId并将其存储在DDTOList对象中。在随后的请求中,这个id必须和其他项一起由客户端提供,以标识结果集。
消息 (7) - (9):仅当所得出的记录的实际数目超出页大小(可配置)时,才发送这些消息。使用标准Java机制将DDTOList标准化并作为一个记录(如BLOB)存储在HOLIDAY_PACKAGE_BAG表中。holidayPackageBagId在此处用作键。此后,将DDTOList
截断,使之仅包含第一批的结果列表,长度为原始清单的长度。
消息 (10):将下一页nextOffset的位置添加到DDTOList,然后利用WLS WebServices基础设施将该清单发送回客户端。
消息 (11):客户端接收到响应并显示结果清单的第一页。同时显示出服务器上可用记录的总数。
场景 2:获得下一页的Holiday Package
场景2(见图4)显示的是当客户端请求下一页的结果清单时的控制流程。这意味着初始的搜索操作将返回一个所含记录多于一页所能包含记录数的记录集。

图 4
动态数据传输对象(DDTO)
动态数据传输(dynamic data transfer object,DDTO)的概念基于通用属性访问(Generic Attribute Access)模式。这种方式用于最大程度地减少域层中的时常变化对系统其他部分的影响。DDTO
是一个通用数据容器,其中含有一个任意数据集。它使用HashMaps和键值注释提供了一个通用属性访问接口。
本案例研究中实现的DDTO既可以包含简单数据类型(String和Integer等),也可以包含复杂的数据类型(各种结构)。此外还支持嵌套和DDTO清单(DDTOList)。
实现框架提供了对DDTO内容的类型安全访问。它还含有用于DAO的超类和完整实现generic接口方法的实体bean。因此,DAO和实体类可以对它们的属性提供一个统一的接口,同时几乎不需要任何额外的编码。
消息 (1):这个applet创建了一个动态数据传输对象(DDTO),同时将holidayPackageBagId 和nextOffset放入其中。注意,这里应由客户端负责管理会话信息。
消息 (2):这个applet 使用一个局部存根对象来调用Web服务方法。这个存根将DDTO序列化到一个SOAP消息,并通过HTTPS将其发送到服务器。
消息 (3):服务器将消息有效负载反序列化,并将数据放入一个DDTO。然后它调用无状态会话bean HolidayPackageServicesBean的getNextHolidayPackages方法。
消息 (4):使用一个DAO对象来从表HOLIDAY_PACKAGE_BAG中获取对应于holidayPackageBagId的记录。
消息 (5)-(7):使用标准Java机制,将BLOB字段RESULT_SET 反序列化到一个DDTOList 对象中。前面讲过(见场景1),DDTOList
中含有初始搜索操作中获得的所有holiday package。现在使用参数nextOffset,可创建下一页的结果清单,并将其作为DDTOList发送回客户端。在此之前,应将nextOffset的值更新以使它指向后续页。
消息 (8):客户端收到响应并显示结果清单的下一页。
返回到模式
在上述解决方案中,HolidayPackageServices Bean仅仅实现了接口ValueListIterator的一部分。例如,由于客户端(applet)缓存了所读取纪录,所以不需要getPreviousElement方法。从客户端的观点看,仅提供ValueListIterator的两种方法getHolidayPackages和getNextHolidayPackages就足够了。图5显示了与Sun的archetype相关的本案例研究中实现的Value
List Handler模式。

图 5
结束语
在本文的案例研究中,我们使用了Value List Handler模式来提供一种用于搜索holiday package的Web服务。在该模式下,长的结果集以分页方式迭代地发送到客户端。
虽然这种方式减少了网络开销,但结果集的缓存仍可能是一个问题。当您有数千个并发用户且ValueList 对象必须包含长的结果集,有状态会话bean并不总是理想的选择。
本案例研究中的关键设计决策是使用无状态会话bean,并将长于1页的结果集存储到数据库。所选择的实现通过减少写操作的数目而提高了数据库操作的性能。这是通过以一个如Oracle
BLOB的紧凑形式来存储结果集而实现的。
这种方式的另一个优势是可以轻松部署负载平衡机制。
参考文献
· Sun Microsystems: java.sun.com/blueprints...
· SOA: webservices.xml.com/pub/a/ws/2003/09/30/soa.html
· Marinescu, Floyd. (2002) EJB Design Patterns: Advanced Patterns, Processes,
and Idioms. Wiley Computer Publishing.
关于作者
Thomas Kruse 已经在IT业工作了13年。他在J2EE技术方面有着丰富的经验,并在此期间完成了2个大型J2EE应用程序。他的从业经历也颇为丰富,曾先后作过开发人员、系统设计师和系统架构师。(更多信息)
原文出处:http://www.sys-con.com/story/?storyid=45563&DE=1
|