一起学习
Entity beans提供了一个清楚的模型以描述应用程序中持久稳固的事务对象以及它们的设计。在面向对象的模型中,简单的Java对象通常被直接地描述,但是并不包括事务处理的功能,这通常需要使用事务对象来完成。Entity beans不仅仅考虑到模型中的简单类型,它也考虑到面向对象模型中的事务模型,它将所有的复杂的工作都交给了bean和容器服务。这使得应用程序可以象使用通常的Java对象那样来应用它。在保持存储数据的公开性和灵活性的同时,容器会对entity beans进行优化,而我们只需要考虑如何部署它。
基于Enterprise JavaBeans的开发方案,导致了面向对象的方法的广泛使用和对entity beans的大量使用。Sun的工程师对entity beans的实际使用作了大量的实践和研究。这篇文章是对开发中的一些经验的总结:
探索各种优化方案
对达到最佳的性能和适应性提供规则和建议
讨论如何避免一些已知的问题
一、尽可能地使用容器持久化管理
容器持久化管理(CMP)不仅仅可以大量减少编程的工作,而且在容器和容器生成的数据库访问代码中存在许多优化的可能。容器可以访问内存中的bean的缓冲,这使得它可以监控缓冲中的任何改变。如果缓冲没有被改变,这可以在提交事务前避免将缓冲存储到数据库中。这减少了不必要的调用数据库的开销。
另外一个优化的实例是对find方法的调用。根据对一个entity的参考搜寻一个entity,而接下去最可能的情况是使用这个entity。这通常包括两个数据库访问:
在数据库中寻找记录并得到主键
将记录的数据读入到内存中
CMP会进行这样的优化,只要它认为可以这样做,它会使用一个数据库访问来代替这两个数据库访问,它会在一个数据库访问中同时得到主键和记录的数据。
二、编写同时支持Bean持久化管理和容器持久化管理的代码
在许多情况下,EJB的作者无法控制EJB将如何被部署,也无法预知部署使用的容器是否支持CMP。同样的,部署者在目标容器可能会使用bean持久化管理(BMP)。你必须寻找一个方法以执行这个bean以允许BMP部署而不用破坏对更多的CMP机制优化的破坏。一个简单的方法是将商业逻辑和持久化机制分离开来。在你的CMP类中执行商业逻辑,如果选择了CMP,它可以被单独部署。实现持久化的代码被写入到BMP类中,它继承自CMP类。在CMP的超类中保持了所有的商业逻辑,并在BMP子类中增加了数据库访问的代码。这样的结构如图1所示。
图1: 将CMP和BMP分离
这个模型听起来很容易实现,但是事实上存在一些问题:
对执行类的继承是不可能的。
这样做意味着子类必须同时继承CMP超类和BMP超类。此外,BMP子类还必须继承它的直接的CMP执行。这导致了多重的类的继承,就象在图2中所示的那样,而这样的继承是不被Java程序所允许的。
图2:Java所不支持的多重继承
没有简单的方法支持改变的持久化执行
这可能是很有用的,例如,可能要执行不同的数据库提供商或是不同类型的数据库(例如关系型、对象型或其它类型)所特定的数据库定义代码。
要解决这些问题,你可以对目前的模型作一些改变。对所有BMP类抽象出一个辅助类以执行基本的持久化代码。这样的辅助类被称之为数据访问对象(DAO)。你可以通过对DAO接口的实现产生多个DAO子类,然后在实际应用时选择合适的DAO子类进行实例化。这就如图3所求。有许多方法可以选择合适的DAO子类进行实例化,例如,通过读取环境变量或是判断DB类型。
图3:代表并允许改变的DAO执行
使用这个模型,CMP执行和包含这个执行的DAO可以很方便地被继承;除了BMP类外,所有的东西都可以被执行。因为这些BMP类包含了一些具有代表性的代码的框架,对于第一个entity bean它看上去都是差不多的,你可以很容易地将一个bean拷贝到另一个而只需作很小的改动。而最终,你甚至可以使用工具来自动完成这项工作。
尽管你可能需要扩充一个entity bean以重新使用另一个entity bean所提供的逻辑,但是在EJB 1.1的说明书中不允许对一个继承自home接口的entity类型进行扩充。对一个调用的定位及建立在理论上总是被认为是一种远程调用。虽然在很多时候你可能需要使用这种子类型(例如,将它们作为一个factory方法),但是不幸地是,这样的子类型并没有被提供。这阻碍了对许多遵守EJB 1.1说明书的执行EJB的有用的设计平台的使用。
三、在ejbStores中尽量减少对数据库的访问
在使用CMP时,bean完全不受ejbStore的控制,所有的优化工件都是由容器来完成的。容器所提供的CMP优化成了区别容器性能的主要指标之一。
然而在使用BMP来部署bean时,为缓冲区维持一个dirty标志是一种很有效的做法。对缓冲区的所有改变都会对dirty标志进行设置,ejbStore将会对这个标志进行检查。如果dirty标志未被设置,这意味着缓冲区没有被改变,ejbStore将不执行所有的开销巨大的对数据库的访问。这个决窍对于那些经常对数据库进行查询而不是作真正的改变的bean尤为有效。而这通常组成了一个应用程序中的绝大部分(例如,查找一个表)。
对于这种技术,有一点需要注意。因为dirty是为了数据库的访问而设立的,因此它对于BMP的代码会更适合,也就是说,它并不太适合在CMP代码中使用。当使用我们前面介绍的执行平台时,它既可以在BMP代码中进行设置也可以在DAO中进行设置。 因为DAO不会调用任何的商业逻辑,因此它不是设置dirty标志的最合适的地方,最好你还是在BMP代码中对这个标志进行设置。这可能会使得BMP代码变得更加复杂,因为你不能在商业逻辑中考虑对超类的授权。
完全从一种科学的观点来看,EJB的作者并不需要处理系统级的问题,包括对缓冲区的处理。但是不幸的是,使用BMP的EJB 1.1并不提供这种系统优化的选择,因此bean的作者还是不得不手工对dirty标志进行设置。
四、对于调用的查找和定位的总是使用Reference Cache
无论是对于entity beans还是session beans,Reference Cache都是一项有用的技术。JNDI对例如数据资源、参考bean或者是环境实体这样的EJB资源的搜寻是相当耗费资源的,而要防止这种多余的搜寻,方法并不太复杂。要解决这个问题,通常的做法是:
将这些references定义为一个实例变量。
在setEntityContext中搜寻它们(对于session bean,相应地使用setSessionContext方法)。
setEntityContext方法仅仅在一个bean被实例化时被调用,因此在这时候搜寻所有需要的references并不太耗费资源。要避免在其它任何方法中搜寻references,特别是对于数据库访问方法、ejbLoad和ejbStore。这样的方法会被频繁地调用,这会导致大量的时间被无谓地耗费在搜寻的调用上。
定位另一个entity bean的调用也是相当耗费资源的。虽然这样的调用不一定适合bean初始化时的象setEntityContext这样的回叫,但是我们还是需要在合适的地方将搜寻的references结果保存在cache中。如果这个reference仅对当前的实体有效,你需要在实例被重新激活以描述另一个实体前清除references。这需要在ejbActivate方法中完成。
五、总是准备好你的SQL语句
在所有代码中涉及到对关系型数据库的访问总是使用SQL是一种有效的优化手段。因为对于绝大多数的当前EJB执行都使用关系型数据库,对于bean的作者需要编写数据库访问代码的EJB部署,这条规则是非常有效的。
因为每一个SQL语句都必须由数据库进行处理,数据库需要花费时间在执行SQL语句前对其进行编译。然而,对于一个性能良好的关系型数据库,会将语句和编译结果保存在cache中,对于新的SQL语句,它会将其与cache进行比照,以获得它的编译结果。然而,为了得用这种优化手段,新的语句必须完成与老的语句匹配。
未准备好的语句
对于未准备好的语句,数据和语句被传递到同一个字符串中,尽管语句可能与后来的调用完成一样,但是数据不匹配,这使得这种优化手段失去作用。
已准备好的语句
对于已准备好的语句,只将没有数据的语句传递到数据库,这使用从cache中获得编译结果成为可能。
在使用语句时,数据在其后被传递,而语句得以执行。通常地,这个语句必须经过编译,但是对于后来的语句,它可能与cache匹配,而不需要再被重新编译。这项技术具有较高的语句cache命中率(接近100%),可以最大限度地减少语句的编译。对于小型的数据库访问,这可以减少将近90%的执行时间。
六、完全地关闭所有的语句
当在BMP执行中处理数据库访问代码时,在完成数据库访问调用后要记得关闭语句。每一个未关闭的语句对应着数据库一个开启的光标。(尽管垃圾搜集器最后会对开启的语句进行检查,并在垃圾搜集(GC)时间将其关闭,但是你不能控制这样的一个时间。)保持语句开启会导致数据库过多的开启光标,这会浪费数据库的资源,而这些资源本来是可以用来改善数据库性能的。
同样,关闭语句时必须确保捕获所有的另外。在关闭一个语句时产生的另外必须不会导致另一条语句的被忽略或是保持开启状态。
七、避免死锁
应用程序不通过代码直接控制调用ejbStore(或其它CMP的等价物)的时间。何时执行这个调用是由容器决定的,这通常是在一个事务的最后进行。
如果在一个事务中涉及到一个复合的entity beans或者是复合的实体,调用ejbStore的次序并没有被定义。这也就意味着用户没有控制与那些实体相关的数据库记录的访问/锁定的次序。当涉及到多个数据表或数据行时,这很可能会导致死锁。
乐观地看,理想的情况是在容器会控制EJB中的访问和锁定,只使得容器会解决死锁问题,而开发者和部署者将不必担心这一类的问题。但是,不幸的是,目前可用的商业应用服务器很少有能很好地处理数据库的锁定问题的,对于复杂的entity bean部署,部署者将不得不自行解决死锁问题。
可适用的规则是(至少在我们编写这篇文档时是这样):我们可以假定容器会按照事务中最初调用事务方法的同样的次序来调用数据库访问。举例来说:如果在一个entity bean EB1中有一个名为m1的事务方法,而在另一个entity bean EB2中有一个名为m2的事务方法。如果在同一个事务中,EB1.m1在EB2.m2之前被访问,你也可以假定EB1.ejbLoad会在EB2.ejbLoad被调用,同样,EB1.ejbStore会在EB2.ejbStore之前被调用。这意味着,与EB1相应的实体或数据库记录会在EB2之前被锁定。为了避免死锁,必须确保在整个应用程序中,对于任何事务EB1总是在EB2之前被调用。
我们可以肯定,应用程序服务器将会变得越来越聪明,他将会知道如何组织对数据库的访问,而开发者和部署者将不再去小心翼翼地处理访问次序。然而,就象在下面这个例子中那样严格处理调用顺序的代码至少可以保证在目前的服务器上更好地工作。
Vehicle和Car bean的源代码可以从这里下载,它使用了这儿的绝大多数的规则,你也可以将其作为将来entity bean开发的基础和模板。(要下载jar文件,右击这个连接,并选择“目标另存为”。)
展望
当entity bean的开发者和部署者有效地利用这些规则后,可以大大地提高bean的性能和适应性,使得它们可以适应不同的持久存储类型。这给了部署者以更多的选择。也就是说 -- 一次编写,处处使用,而且是有效率地处处使用。
下载本文示例代码
优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则优化Entity Beans的七条守则