技术开发 频道

利用实体EJB来避免性能的缺陷

【IT168 技术文档】

  WebLogic Server的EJB容器提供两种不同的实体bean类型:Read-Only 实体beans 和Read-Write 实体beans。Read-Only 实体beans 支持高级群集缓存。
 
  实体beans是具有事务功能的对象。理解事务和持久性之间的关系是很重要的。当实体bean的一个实例首次在事务中使用时,将会从数据库来刷新它的状态。对实体bean状态的任何修改会立刻刷新到数据库中,而且会是在事务提交之前完成。
 
  让我们来看一个Employee 实体bean的例子,它具有所需的事务属性。可以假设我们在一个单独的事务中创建了这个bean,而且当前它有一个指向Employee实例的引用。
 
Employee e = ...
 
String name = e.getName();
 
e.setSalary(e.getSalary() * 2);
 
e.setLevel(e.getLevel() + 1);
 
  从上面的代码片断中得出的重要发现是调用者没有启动事务。因为bean是使用必需的事务属性来部署的,每个方法都作为一个单独的事务运行,并加载、存储到数据库中。在这个例子中,每一个getXXX方法导致了一次数据库读操作,而每个setXXX方法导致了一次数据库读操作和一次数据库更新操作。这个简单的例子里有五个单独的事务,并有五次数据库读操作(SELECTs)和两次更新操作。
 
Employee e = ...
 
tx.begin();
 
String name = e.getName();
 
e.setSalary(e.getSalary() * 2);
 
e.setLevel(e.getLevel() + 1);
 
tx.commit();
 
  上面这个代码样例用单个事务包装了对Employee 实体bean的调用。在这个示例代码中使用了一个UserTransaction的引用,用手工方式开始并提交事务。这些功能也可以用一个session bean来实现,它需要使用一个可用容器管理的事务,然后在容器事务中调用实体beans。
 
  当所有的业务方法都在一个单独的事务中执行时,只存在两种数据库访问。第一个getName调用产生了一个从数据库加载的SELECT语句。随后的getXXX和setXXX 方法没有导致任何数据库访问,因为它们在同一个事务中,而且数据已经被加载了。在事务提交时,将执行一个UPDATE语句向数据库写入新的"salary"和新的"level"。
 
使用强制性事务属性
 
  许多EJB的程序员用"Required"级别的事务属性来部署他们的beans。"Required"级别的事务属性在多数情况下都能起作用,因为如果存在一个调用者的事务,它将继承这个事务;否则它将启动它自己的事务。正如我们前面看见的,开发人员必须理解:事务属性是如何强烈影响性能的。小组中的新程序员可能会在一个单独的事务中重用Employee 实体bean,并错误地调用每个 getXXX方法。一种避免这种情况的方法是使用强制性(Mandatory)事务属性来部署你的实体bean。和"Required"级别的事务属性不同,强制性(Mandatory)bean不会启动它自己的事务。如果它和一个事务被同时调用,强制性bean将会参与到这个事务中。如果强制性(Mandatory)bean没有和某个事务一起调用, EJB容器会立即向调用者抛出一个异常。
 
用强制性(Mandatory)事务属性的来部署实体beans是一种容易的方法,它指出应该将这些操作组织到一个调用事务中。
 
CMP的优势 - 加载Beans 的Finder
 
与业务方法一样,实体bean的finder方法的性能依赖于对事务的正确设置和使用。让我们来考虑使用Employee Bean的另一个例子。
 
Collection employees =
 
home.findEmployeesWithSalariesGreaterThan(30000);
 
Iterator it = employees.iterator();
 
while (it.hasNext()) {
 
Employee e = (Employee) it.next();
 
double salary = e.getSalary();
 
}
 
  这个简单的例子执行了一个finder(数据查询),用于返回其薪金大于传入参数(在本例中,转入的参数为30,000)的雇员。然后从头到尾重复这个搜索过程,并从每个雇员中检索其薪金。在finder返回了N个雇员的情况下,该例子将访问N+1次数据库。finder访问了数据库,随后的每一个getSalary回调都访问了数据库。
 
  到现在,你可能已经猜测出:我们试图在一个单独的事务中执行finder和getSalary方法。现在,有多少次对数据库的访问呢?答案可能会令你惊奇。
 
  如果Employee是一个CMP 实体bean,而且finder和随后的getSalary方法发生在同一个事务中,那么只存在一次数据库访问。finder执行了一次选择查询,这次查询加载了对应的主键和其它的Employee字段。这些预取出的beans被输入到EJB缓存中,而且getSalary方法直接从内存缓存中调用读操作。这就是weblogic-ejb-jar.xml中的finders-load-bean选项。默认状态下它是启用的。而且它允许finder从EJB缓存中预取出附加的数据。在这个常见的例子中,对数据库访问从N+1次减少到了1次。
 
  如果Employee类是BMP 实体bean将会怎样呢?令你吃惊的是,如果这样就还会有N+1次数据库采样。如果Employee是一个BMP实体bean,该如何呢?这也许会令你惊奇,但它对数据库的访问仍然是N+1次。BMP 实体bean会在bean类中执行一个ejbFindEmployeesWithSalariesGreaterThan方法,以返回容器的一个主键集合,但它不会预取出到缓存中。然后每个getSalary调用都会产生一个ejbLoad调用和另一个数据库访问。
 
  Finder预取出beans是CMP在性能上的一大优势,一般来说,CMP(特别是EJB 2.0的CMP)实体beans的性能要优于BMP 实体beans。

  
注:作者 Rob Woollen是BEA Systems公司WebLogic Server开发小组中的一位资深软件工程师。他拥有普林斯顿大学的计算机科学学士学位。
0
相关文章