EJB 2.0 发布的时候我就想:“哦,EJB 终于发布了!”通过 容器管理关系(CMR)和一种标准查询范例,我认为袖珍的、独立于数据层的应用程序的涅磐已经近在眼前了。
时间一天天流逝,当CMR把外键关系的定位看作是一种”脱离”时,我发现自己仍然在Collections bean中辛劳的原地运动。在每个bean上调用findByPK;去掉应用程序不需要的部分;并为大量根本不需要的耦合 bean 运行缓存内计数。这样比SQL更好吗?我想要JDBC!
某天夜里,比我喜欢的时间要晚,当我努力从TimedOutException中恢复而不再使用陈旧的JDBC框架时,一些偶然发现照亮了我的世界,恢复了我的信心:EJBSelect。
目的:维护CMP 事务支持的同时提升性能
EJB 容器管理持久性(CMP)可以处理所有令人难于应付的事务stuff使我对它情有独钟。不再有“哦,我忘记回滚那部分了”。当然,令人痛苦的部分是有时候必须处理一些不实用的代码和大量嵌套的循环,才能获得所需数据。这样就会产生大量的代码,进而不可避免地导致性能下降。
性能下降是因为在所有的循环中,为了定位其他的stuff而加载了大量 stuff。您不需要它或者不关心它,您只需要定位可以使您获得真正所需结果的模式即可。我对您不了解,但是Tom的第一条性能调优规则是“不要让计算机去做它不必做的事情。”应用程序代码的合并暗示了这样一个宗旨,油炸圈饼不能变味,咖啡不能凉。
适用性:
用于JDBC再分类
“只使用 JDBC ”这件事情使我感到困惑。我不是在抨击JDBC。我喜爱JDBC。在某种情况下它是最合适的工具,而EJB就像是用一个大铁锤去敲一个图钉。问题在于,一旦从BMP开始,就只有两种选择。
选择一,构建属于自己的持久性框架。既来之则安之。以前的客户拥有权力,而现在的客户则不希望听到关于在计划表中增加工作时间的报告(读入成本)。
选择二,加快速度,希望团队的每一个人了解他们正在做的事情。我讨厌承认这一点,但是我已经处在这个位置,而且也已经开始工作了,所以我不准备回头了。
结构
在图1中,为了简洁而把每一个实体EJB的多种EJB接口归结为一类。图中去掉了两个重点。第一,注意CMR关系。事情以一种适当的标准化方式被很好地联系在一起。外键使定位成为可能。这里没有什么令人可怕的。

第二,注意“ejbSelectCorporationsOpenInvoices ”。它存在于CorporationBean类中。它可能不符合EJB 2.0规范,从bean的本地或者远程接口就可以看出这一点。访问它需要在bean类中实现一种业务方法,并将其暴露给公共接口。这就是“getCorporationsOpenInvoices”。它是一种称为“ejbSelectCorporationsOpenInvoices”的简单传递方法,将实例“getCorporation”方法的结果当作一种参数传递。
结论:谁知道结果?它足够快吗?
在下面的清单中(本文源代码位于www.sys-con.com/weblogic/sourcec.cfm),最终解决方案包含了可以请求Corporation实体提供“开放””发票的应用程序。我们可以边喝啤酒边讨论Corporation对象是否是存放“发票公开?”问题的正确位置,关键是需要它来执行。发票是一种在哲学上无懈可击的位置以便封装该问题。这就意味着,此路不通。
清单1
Corporation corp = getCorporation(corpNo);
Collection custs = corp.getCustomers();
for(Iterator cIt = custs.iterator();cIt.hasNext();) {
Customer cust = (Customer) cIt.next();
Collection cInvs = cust.getInvoices();
for(Iterator iIt = cInvs.iterator();iIt.hasNext();) {
Invoice inv = (Invoice) iIt.next();
if (inv.getInvoiceState().equals("O")){
invoices.add(inv.getAll());
}
}
}
清单2
<query>
<query-method>
<method-name>
ejbSelectCorporationsOpenInvoices
</method-name>
<method-params>
<method-param>
java.lang.Integer
</method-param>
</method-params>
</query-method>
<result-type-mapping>
Local
</result-type-mapping>
<ejb-ql>
select object(inv)
from Invoice as inv,
Corporation as corp,
IN (corp.customers.invoices) as corpsInvs
where corp.corporationNo = ?1 and
inv MEMBER OF corpsInvs and
inv.invoiceState = 'O'
</ejb-ql>
</query>
清单3
Corporation corp = getCorporation(corporationNo);
Collection openInvoices = corp.getCorporationsOpenInvoices();
for(Iterator custOpenInvIt = openInvoices.iterator();
custOpenInvIt.hasNext();) {
Invoice inv = (Invoice) custOpenInvIt.next();
invoices.add(inv.getAll());
}
但是它足够快吗?Okay,这就是离开标准金锤反应(Golden Hammer Response)的地方。这不是一颗银弹。在某些情况中,为JDBC再分类也许是最佳选择。但是,在这种情况下是行不通的。
实现
Okay,说得够多了,是时候挽起袖子研究代码了。首先需要了解一些背景。我日以继夜为之工作的客户经营的是食品配送业务。他们为国内最大的几家连锁餐馆提供各种夹饼、薯条和其他食品。食品配送商必须把食物交付给每一家餐馆,然后从连锁餐馆的公司实体领取付款。因此,发票在客户级上发布,而付款在公司级上接收。(注意,在实际情况中有多级的公司,如Franchise Groups、Divisions和Customers。付款和发票级会因连锁的不同而不同,因此我们不能只在发票表格中输入公司),当收到付款时,我们需要这家公司的全部公开发票。
由于讨论的是性能问题,所以另一件事情是测试时使用什么配置的机器。这是一台Dell Inspiron 8200计算机,配置了1.7GHz Pentium 4处理器和512MB内存。运行Windows XP Pro、WebLogic 7.0 SP2和本地SQLServer 7.0数据库。测试时,假设公司拥有500位客户,每一位客户有5份发票(4份非公开的,一份公开的),总数为2500份发票。
第一种方法是一些EJB 1.1-ish(参见清单1)。在会话bean代码中,从获得对公司的引用开始,然后使用公司与客户的CMR关系来获得与公司链接的客户集。这就是EJB 2.0,然后在客户中循环。从而得到每位客户的全部发票。检查每一份发票是否公开,保留公开发票,去掉非公开发票。
EJB1.1 Joins在应用程序代码中。为什么我们不能只使用JDBC?其性能非常差。在测试用计算机上,平均使用4126毫秒才可以完成操作,这一点不奇怪。参看图2,加载500位客户以获得他们的发票。然后通过整理2500份发票来获得所需的500份发票。
现在了解一种不同的方法。还记得只能编写SQL来做这些事情的那些时候吗?那么,旧貌换新颜了。使用EJBSelect可以在部署描述符中执行join。看看更多的代码。
清单2中,引擎位于EJB Select后面。EJBQL可以通过定位数据库模式的方法来定位Abstract Schema,继而执行join 。它所需要做的就是在外键中输入公司、客户和发票后返回的结果集中选择状态为"O"PEN的发票。有点像从代码中进行JDBC调用。
现在请看清单3,了解它对会话bean代码的作用。现在只有一个循环,没有if语句。把9行代码减少到5行。从双字符日期流行的时候我就在编写应用程序,并且一直正确的是:删除的代码行数越多,代码就越好。
“代码整洁干净了,但是性能如何呢?”很高兴您问到这个问题。我们得到了需要的结果。参看图3,查询程序加载了1个公司bean和500个发票bean。操作平均用时337毫秒。比第一种方法快1225%!我说过那种方法行不通的。
结论:三思而后行
任何时候任何人都会提出性能要求,即使是和作者一样诚实和勤劳的人,每个人都会说:“哦,您是如何提升这些数字的?”但是我没有,它正在运行中,并且抛弃了难以应付的TimedOutException,我可以在适当的时候休息了... 对不起,但是销售人员也不能把这个数字提升1225%。Okay,也许,它原本是非常难以克服的。
再说一遍,它不是银弹或者金锤,但它的确是一把非常漂亮的钳子。下次做JDBC的时候考虑一下它。它是提高性能而不必丧失CMP事务处理能力的一种方法。
|