IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  WebSphere  >

EJB 倡导者: 使实体 EJB 组件满足需求,第 2 部分

来自 IBM WebSphere 开发者技术期刊

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Geoff Hambrick (ghambric@us.ibm.com), 杰出工程师, IBM

2005 年 6 月 01 日

正如在上月的专栏中所讨论的,使用设计欠妥的 EJB 组件可能导致在系统测试或者(更糟的情况)产品实际使用中产生严重的性能问题。本月,EJB 倡导者将向您展示如何使用 CMR 来获取在单个工作单元中使用多个相关 CMP 所带来的好处。

在每个专栏中,EJB 倡导者通过前后衔接的对话,针对某一大家关注的设计问题,向客户以及开发者推荐解决方案并与其进行交流。然而其中并未介绍一些特定的细节,并且也没有提出“新颖的”或专有的体系架构。要了解更多信息,请参见 EJB 倡导者简介

问题

上月我曾提及,这次我们将对服务数据对象(Service Data Object)进行讨论,但是上月在讨论有关性能的话题后,出现了一个后续问题需要在此重点地叙述一下。

亲爱的 EJB 倡导者,

上次我阅读了你们的文章,并且我和 No Longer a Fan 一样,对于 CMP 实体 EJB 组件有了更深的了解。因此我从我们的应用程序中开发了一个典型的应用实例,其中的数据模型如下所示:

图 1. 数据模型
数据模型

正如您所建议的那样,为了最小化各层之间的分歧,我通过使用定制方法返回的关键字(Key)和数据传输对象(Data Transfer Object,DTO),在上面的类图表中为每一个对象创建了实体 CMP。表示相互关系的 DTO 有一个特性,引用了目标 DTO(如果基数不大于一)或者它们的阵列(如果基数大于一)。所有的这些 DTO 都声明为可外部化,以此来加快编组速度,我甚至创建了一些定制的定位器,这样就可以在不必创建主关键字对象的情况下,更方便地访问 CMP。然后,在会话虚包中我对它们使用了本地接口来确保只有一个事务能被发布。该会话 bean 有一个方法,该方法假定可以采用 ejbCreate() 方法检索相关 CMP 的本地位置,并且将其设置为实例变量。而该方法可能有些问题,如下:

public CustomerData  getOpenOrderForCustomer(int cId) {
	// Get the Customer DTO	
	Customer cRef = cHome.findByCustomerId(cId);
	CustomerData cData = cRef.getData();
	// Check to see if there is an open order
	int oId = cData.getOpenOrderId();
	if (oId == 0) {
		throw new OrderNotOpenException(cId);
	}
	// Get the Order entity and the DTO object	
	Order oRef = oHome.findByOrderId(oId);
	OrderData oData = oRef.getData();
	cData.setOrder(oData);
	
	// Get the array of Line Items DTOs set up
	Collection liList = liHome.findAllItemsForOrderId(oId);
	int liSize = liList.size();
	LineItemData[] liArray = new LineItemData[liSize];
	oData.setLineItems(liArray);
	Iterator liIterator = liList.iterator();
	LineItem liRef = null;
	LineItemData liData = null;
	Product pRef = null;
	for (int i = 0; i < liSize; i++) {
		// Get the Line Item DTO
		liRef = (LineItem) liIterator.next();
		liData = liRef.getData();
		// Get the Product DTO
		pRef = pHome.findByProductId(pId);
		liData.setProduct(pRef.getData());
		liArray[i] = liData;
	}
	
	return cData;
}

当我借助手写的 JDBC 程序来运行该会话和 CMP 集时,它的吞吐量明显变少(将近 2 倍)。不用说,完成这些工作后我觉得不满意。

签名:
Won't Get Fooled Again

自上月以来,我一直有这样的想法,就是似乎和 No Longer 情况很相似,这并不是简单的进行比较。但是和本实例确实有许多相似之处。

亲爱的 Won't Get Fooled,

我必须说,我的印象还是很深刻的 -- 不仅仅是您确实读了我的专栏同时也听从了我的建议!

说实话,感谢您开发了一个对象模型向我展示应用的基本设计。开发小组经常希望得到关于什么是"最好的"的问题的答案,这时候最好的答案要分情形来看。我很高兴看到您提供了您的 EJB 代码,因此我没有必要再去猜测您是否真正地按照您所描述的实践进行了操作。您对 EJB 的代码的使用已经超出了开发人员的一般做法!

也就是说,在我给出任何建议之前,最好能看到在您对比中使用的手写 SQL。这是因为"最好的"选择总是需要与一个参照对象比较后才能体现出最优。

同时,希望您能够为我回答一个问题:我想知道您是否对实体 bean 使用了一个称为"容器管理的相互关系"(或者是 CMR)的概念?

期待着您的回答。

好了,
您的 EJB 倡导者





回页首


两者之间的比较

亲爱的 EJB 倡导者,

让我首先回答您有关 CMR 问题,因为它最容易回答:不,我没有使用它们,是因为我对它们还不太了解。

以下是等价的使用 JDBC 的会话 bean 方法:假设驱动程序装载在 ejbCreate() 方法中,且运行时异常情况不必在该方法签名中声明,可以通过我们的代理框架得到处理。首先向大家抱歉,因为这些代码太长,所以我没有在最初就将其发布出来:

public CustomerData  getOpenOrderForCustomer(int cId) 
throws OrderNotOpenException
{
	Connection con = null;
	try { 
		con = DriverManager.getConnection(
			"jdbc:db2:orderentry"
      		);
	}
	catch (SQLException e) {
		throw new RuntimeException,  
 			"Connection cannot be made.", e)
 		);
 	} 	
 	try {
 		con.setAutoCommit(false);
		con.setReadOnly(true);
		con.setTransactionIsolation(
			Connection.TRANSACTION_READ_COMMITTED
		);
	 	PreparedStatement stmt1 = con.prepareStatement("
 			SELECT NAME, OPEN_ORDER_ID
   			FROM CUSTOMER_USER.CUSTOMER 
 			WHERE ID = ?
	 	");
		stmt1.setInt(1, customerId);
	 	stmt1.setMaxRows(1);
	 	stmt1.setMaxFieldSize(30);
 		ResultSet result = stmt1.executeQuery();
	 	result.next();
 		String name = result.getString(1);
	 	int openOrderId = result.getInt(2);
	 	result.close();
	 	stmt1.close();
 		// Validate that the order is open
 		if (openOrderId == 0) {
 			throw new OrderNotOpenException(cId);
 		}
 		// Now go get the Line Items and Products together
 		PreparedStatement stmt2 = con.prepareStatement("
 			SELECT 
 				PRODUCT.SKU,
 				PRODUCT.DESC,
 				PRODUCT.PRICE,
 				LINEITEM.QUANTITY
 	   		FROM 
 				CUSTOMER_USER.LINEITEM,
 				CUSTOMER_USER.PRODUCT 
 			WHERE 
 				LINEITEM.ORDER_ID = ? 
 	   		
 			ORDER BY 
 				PRODUCT.SKU ASC
	 	");
 		stmt2.setInt(1, openOrderId);
 		stmt2.setFetchSize(10);
 		ResultSet result2 = stmt2.executeQuery();
		LineItemData liData = null;
 		ProductData pData = null;
		int productId = 0;
 		String description = null;
 		Int price = 0;
 		int quantity = 0;
 		Vector v = new Vector(10, 10);
 		while (result2.next()) {
 			// Get the fields
 			productId = result2.getInt(1);
 			description = result2.getString(2);
 			price = result2.getInt(3);
 			quantity = result2.getInt(4);
 			// Set the fields related to Order
 			liData = new LineItemData();
			pData = new ProductData();
 			liData.setKey(
 				new LineItemKey(openOrderId, productId)
 			);
			liData.setQuantity(quantity);
 			liData.setAmount(price * quantity);
 			liData.setProduct(pData);
 			// Set the fields related to Product
 			pData.setKey(new ProductKey(productId));
 			pData.setDescription(description);
 			pData.setPrice(price);
 			// Add the item to the Vector 
 			v.addElement(liData);
 		}
	
 		// Close the result set and statement
 		result2.close();
 		stmt2.close();
 		
 		// Now we can set the Customer and Order
 		CustomerData cData = new CustomerData();
 		OrderData oData = new CustomerData();
 		cData.setKey(new CustomerKey(cid));
 		cData.setName(name);
 		cData.setOpenOrderId(openOrderId);
 		cData.setOrder(oData);
 		// The order data can be defaulted for the most part
 		oData.setKey(new OrderKey(openOrderId));
 		oData.setStatus("Open");
 		oData.setLineItems((LineItem[])v.toArray());
		// Return the top of the graph
 		return cData;
 	}	
 	catch (SQLException e) {
 		throw new RuntimeException(
 			"Error during processing.", e
 		);
	}
 	finally {
		try {
			con.close();
		}
		catch (SQLException e) { 
			throw new RuntimeException(
 				"Error closing connection.", e
 	 		);
		}
	}
}

我知道您在查看完这段代码后,会笑我为实体 EJB "所做的工作"。但是我需要提醒您的是,您看到的是完成工作所必须的全部代码。我没有向您展示任何的实体 EJB 以及部署描述符,而我必须编写它们来完成所有关于 CMP 的工作。

所以尽管如此,但是仍然:
Won't Get Fooled Again

Wow!这人不错。他知道如何在关口处将您卡住。

亲爱的 Won't Get Fooled,

再次感谢。确实,当我看到大段的代码时笑了出来。但是另一方面,代码的质量确实很高啊!您按照我所知道的所有 JDBC 最好实践编写了代码,因此也做到了那种水平。首先让我对您所做的工作进行大致的总结:

  • 尽可能晚的创建资源,并且尽可能早的释放它们。
  • 对错误进行适当处理,并且不论是否存在异常,都要确保使用 finally 子句来完成应该进行的操作。
  • 您在连接上设置了访问意图,并且使用特别处理过的的语句来优化这些通信功能。(我尤其欣赏您在与 Vector 初始程序和增量相连接的语句中使用 setFetchSize() )
  • 当然,您在编写 SQL 时,添加了特定的列选定和联结功能,这样可以使发送到数据库层的语句的数量和长度最小化,并且也使返回的数据量最小。

该段代码将 bar 设置的相当高,我们可以利用它将当前的代码与提出的改进措施进行比较。

信不信由你,尽管我认为您使用带有 CMR 的 CMP 可以使您更好的完成有关 JDBC 的工作。其实,不用查看您的实体 EJB 代码或是部署描述符,只需查看会话 bean 代码,我就能猜到您并没有在实体 EJB 组件中使用 CMR。这是为什么呢?因为您的会话 bean 是流程化的,而不是面向对象的。也就是说,它流程化的处理所有的逻辑,而不是委托给实体来处理,这些逻辑可以对涉及的四个实体之间关系进行"定位"。如果它是面向对象的,那么您的会话中将会出现如下代码:

public CustomerData  getOpenOrderForCustomer(int id) 
throws OrderNotOpenException
{
	// Get the Customer DTO	
	CustomerKey key = CustomerKey new(id)
	Customer ref = cHome.findByPrimaryKey(key);
	return ref.getDataWithOpenOrder();
}

简言之,这是一个真正的会话虚包模式:此会话 bean 代表了到另一个类的业务逻辑 - 在这种情况下就是 Customer 实体,它代表了对象图的顶端。此会话 bean 仅仅将事务、安全性和分配行为作为服务质量来添加。

也就是说,我很欣赏您直接在该会话 bean 中对您的 CMP 和 JDBC 访问逻辑进行编码,而不是委托给助手 POJO。您一定也在之前阅读过我的第一篇文章。但是,不管调用 CMP 的逻辑在该会话 bean 中还是在 POJO 中,它本质上仍然是流程化的而不是面向对象的。

重新回到主题,即使我看到了以上贯穿实体的真实的会话虚包模式,那么除非我查看 Customer,否则我将仍然不会知道您在使用 CMR。Customer 的"核心部分"如下所示:

// CMP fields
public abstract String getName();
public abstract void setName(String value);
public abstract int getOpenOrderId();
public abstract void setOpenOrderId(int value);
// Custom getters
public CustomerData  getData() {
	CustomerData data = new CustomerData();
	data.setKey(getPrimaryKey());
	data.setOpenOrderId(getOpenOrderId());
	return data;
}
public CustomerData  getDataWithOpenOrder(int cId) {
	// Check to see if there is an open order
	int oId = getOpenOrderId();
	if (oId == 0) {
		throw new OrderNotOpenException(cId);
	}
	// Get the Order entity and the DTO object	
	Order oRef = oHome.findByOrderId(oId);
	OrderData oData = oRef.getDataWithLineItems();
	CustomerData data = getData();
	data.setOrder(oData);
	return data;
}

此代码表示您正使用面向对象的代理,而不是使用 CMR。如果您正使用 CMR,那么代码就会如下所示:

// CMP fields
public abstract String getName();
public abstract void setName(String value);
public abstract int getOpenOrderId();
public abstract void setOpenOrderId(int value);
// CMR fields
public abstract Order getOpenOrder();
public abstract void setOpenOrder(Order value);
public abstract Collection getOrders();
public abstract void setOrders(Collection value);
// Custom getters
public CustomerData  getData() {
	CustomerData data = new CustomerData();
	data.setKey(getPrimaryKey());
	data.setOpenOrderId(getOpenOrderId());
	return data;
}
public CustomerData  getDataWithOpenOrder() {
	// Check to see if there is an open order REFERENCE
	Order oRef = getOpenOrder ();
	if (oRef == null) {
		throw new OrderNotOpenException(cId);
	}
	// Get the Open Order data	
	OrderData oData = oRef.getDataWithLineItems();
	CustomerData data = getData();
	data.setOrder(oData);
	return data;
}

我确信您可以生成流程化的或者面向对象的模式,它们通过图形工作,并且使用独立于该代理的 CMR 。从 Order 开始,我将提供用于面向对象的 CMR 实例的 Order,Line Item 和 Product 实体的核心部分,这是因为我们最终想与以上的流程化 JDBC 代码进行比较:

// CMP fields
public abstract String getStatus();
public abstract void setStatus(String value);
// CMR fields
public abstract Customer getCustomer();
public abstract void setCustomer(Customer value);
public abstract Collection getLineItems();
public abstract void setLineItems(Collection value);
// Custom getters
public OrderData  getData() {
	OrderData data = new OrderData();
	cData.setKey(getPrimaryKey());
	data.setStatus(getStatus());
	return data;
}
public OrderData  getDataWithLineItems() {
	// Use CMR to get the line items into an array
	Collection liList = getLineItems();
	int liSize = liList.size();
	LineItemData[] liArray = new LineItemData[liSize];
	Iterator liIterator = liList.iterator();
	LineItem liRef = null;
	for (int i = 0; i < liSize; i++) {
		// Get the Line Item DTO
		liRef = (LineItem) liIterator.next();
		liArray[i] = liRef.getDataWithProduct();
	}
	// Create the object and return
	OrderData data = getData();
	data.setLineItems(liArray);
	return data;
}

以下是 Line Item:

// CMP fields
public abstract int getQuantity();
public abstract void setQuantity(int value);
public abstract int getAmount();
public abstract void setAmount(int value);
// CMR fields 
public abstract Order getOrder();
public abstract void setOrder(Order value);
public abstract Product getProduct();
public abstract void setProduct(Product value);
// Custom getters
public LineItemData  getData() {
	LineItemData data = new LineItemData ();
	data.setKey(getPrimaryKey());
	data.setQuantity(getQuantity());
	data.setAmount(setAmount());
	return data;
}
public CustomerData  getDataWithProduct() {
	// Get the Product from the CMR	
	Product pRef = getProduct();
	ProductData pData = pRef.getData ();
	LineItemData data = getData();
	data.setProduct(pData);
	return data;
}

最后,以下是 Product:

// CMP fields
public abstract String getDescription();
public abstract void setDescription(String value);
public abstract int getPrice();
public abstract void setPrice(int value);
// CMR fields
public abstract Collection getLineItems();
public abstract void setLineItems(Collection value);
// Custom getters
public ProductData  getData() {
	ProductData data = new ProductData();
	data.setKey(getPrimaryKey());
	data.setDescription(getDescription());
	data.setPrice(getPrice());
	return data;
}

于是现在我们可以真正地在这些方法之间做比较了,"这些方法"的种类有:

  • Procedural/JDBC
  • Procedural/CMP
  • Procedural/CMR
  • OO/JDBC
  • OO/CMP
  • OO/CMR.

我包含了即将完成的 OO/JDBC 实例——它本质上是使用业务对象(在您的实例中有四个)一对一的开发"定制"对象的任意方法。使用 bean 管理持久性(BMP)的实体 EJB 也将会包含在此清单中,如同没有工具或者 JVM 支持的"类似 JDO" 的 对象一样。换句话说,如果您定制代码的 JDBC 与单个业务对象相关联,而且期望它能够在自己的方法范围内调用其它的对象,同时能够在其它的上下文中被调用,那么就可以将其当作 OO/JDBC。

现在,开始评估。

正如你所认识到的那样,我将要指出,由于 SQL 和体系框架复杂性的原因,任何 CMP/CMR 实例中的任何单个的方法都远比使用 JDBC 直接编码的方法容易。我同时也提到了一个需要指出的"显而易见"的问题:如果您突然切换了数据库,那么 JDBC 实现将不能继续工作(除非您修改了代码,或者使用数据源、属性文件、或环境变量作为获取连接 URL 的方法)。甚至于,如果您不使用关系数据库作为底层数据存储库,那么 JDBC 实现也将完全不能工作。

接下来,正如您所指出的那样,使用 CMP/CMR 方式(或者甚至是 OO/JDBC 方式)来开发给定的工作单元时,要比使用 procedural/JDBC 方式拥有更多的方法——部分原因是它包含了所有的自定义 get 和 set 方法。这些自定义的面向对象的方法都是可重用的,而流程化的代码,特别是使用 JDBC,将不得不针对场景来手工编写。

举一个可重用的例子,使用传入的适当关键字段,您可以很容易的将任何实体 bean(CMP 或 CMR)的各种 getData 方法在会话中公开。如前面所述,这个会话方法将是一个真正会话虚包。例如,我们希望获取与给定实体类型相关的 "独立的" DTO。可以编写四个非常简单的会话虚包方法:

public CustomerData  getCustomerData(int id) 
throws FinderException
{
	// Get the Customer DTO	
	CustomerKey key = CustomerKey new(id)
	Customer ref = cHome.findByPrimaryKey(key);
	return ref.getData ();
}
public OrderData  getOrderData(int id) 
throws FinderException
{
	// Get the Order DTO	
	OrderKey key = OrderKey new(id)
	Order ref = oHome.findByPrimaryKey(key);
	return ref.getData ();
}
public LineItemData  getLineItemData(int orderId, int productId) 
throws FinderException
{
	// Get the LineItem DTO	
	LineItemKey key = LineItemKey new(orderId, productId)
	LineItem ref = liHome.findByPrimaryKey(key);
	return ref.getData ();
}
public ProductData  getProductData(int id) 
throws FinderException
{
	// Get the Product DTO	
	ProductKey key = ProductKey new(id)
	Product ref = pHome.findByPrimaryKey(key);
	return ref.getData ();
}

可重用性真正的好处是便于维护;通过使用与 DTO(来源于您的数据模型)相关的定制方法,您在添加或者删除属性(不管它是与 CMP 字段相关,还是与 CMR 相关)时,只需修改不同实体的 bean getData() 和 setData() 方法。如果您修改了该模式,那么与这些字段相关的每一个流程化或 OO/JDBC 方法将必须进行更改。

使用 CMR 时,必须获取本地实体,然后使用定制的查找程序来检索一个或者更多对实体的引用;在声明 CMR 时,CMR 在后台自动完成这些工作。这种简化导致了与流程化或只有 CMP 的方法相比,传递的参数要少得多。实际上,在使用 CMR 时,即使是流程化的类型,工作单元中也只需要显式的调用一个查找程序。并且这包括了用于无状态会话 bean 引用的部分,因为它可以被缓存在客户端中!同样,在使用 OO/CMR 的很多情况下,使用 findByPrimaryKey() 方法来对实体进行查找。这就意味着您必须在部署描述符中指定较少的定制查找程序。数量越少就越有利于可维护性和可重用性。

与关键字相关的是,当使用 CMR 时,通常只有会话 bean(或"最终客户端",例如 servlet 或 JSP)必须了解实体关键字段的内容,甚至要知道实体本身的定制方法。这使得实体的维护更简单方便,如果始终使用CMR 就更是如此。您可以修改这些关键字段,在重新部署之外不需要做其他工作。当不使用 CMR 时,您的代码必须要知道这些关键字段,从而找到相关的对象,并且不管是流程化还是 OO 样式,您在代码中很可能要使用自定义查找程序。

现在我们将讨论您的最后一个并且也可能是最重要的问题。从您的加载测试可以很明显的看出,procedural/JDBC 能够得到比 procedural/CMP 情况下更好的吞吐量。而且这里也没有理由认为 OO/CMP 实例要比流程化 CMP (意味着在您的实例中 2X 要差于 procedural JDBC)运行得更好或者更差,因为唯一需要显著变更的就是代理。

目前还不明确的是,在 CMR 情况下是否能和手工编写的 JDBC 代码运行的一样好,不管使用的技术是流程化方法还是 OO 方法。当使用 CMR 时,可以在许多的应用程序服务器(例如 IBM WebSphere Application Server)中调整容器,使用与手工编码类似的方式来优化 SQL。举例来说,可以指定"access intents"以使您能够设置前向读取的长度(类似于 setFetchSize),读取限制(类似于 setMaxRows)和预装载缓存(类似于 joins)。您同时也可以将加载到所选集合的列"集结"在一起。(请查看 IBM WebSphere Application 信息中心的应用程序概要文档。)

如果您认为使用 CMR 和概要文件以及访问意图来进行处理,会使实体和直接编码进行优化的 JDBC 一样复杂,那么我将提出以下几点问题:

  1. CMR 是模型信息,它类似于数据库中的外键。但是和外键不同的是,它们也使得应用程序代码变得简单,不管它们本质上是 OO 还是流程化的。
  2. 明确指定的访问意图应该只是在即装即用(out-of-the-box)性能不能满足指定目标时才使用;举例来说,上月我们讨论的案例中,当工作单元的目标是一个或者多个不相关的实体时,您发现性能只能达到几个百分点,相当于一个较简易的编程模型。
  3. EJB 容器在用CMR 将 CMP 映射到底层数据存储库方面做得越来越好,这使得以后可能需要重新部署,并可获得手工编写 JDBC 不可能得到的性能优势。
  4. 在因为性能原因而必须指定访问意图的地方,不必更改 EJB 的代码,只需调整参数。因此,即使这些代码没有起到它应有的作用,它也将始终运行。 这些担保将对部署和测试周期产生巨大的影响。
  5. 在许多工作单元共享相同的访问意图基本设置时,比如所有那些需要订单头(如 submit、cancel 和 show orders 方法)的工作单元,您可以创建一个公共的应用程序概要文件,并将这些功能进行协调到一起。

如果不使用 CMR,您的流程化或者 OO/CMP 代码将隐含的发布许多单独的 SQL 语句:一个用于客户,一个用于订单,一个用于行条目清单,一个用于每个产品。毫无疑问,CMP 不如手工编写的 JDBC 运行效率高。

但通过充分利用 CMR,应用程序有时可以用简单的对象定位路径表达式来协调,以发布仅仅一个 SQL 语句(使用零个或者更多的内部连接来处理多基数定位;我的朋友 Stacy 将这些连接称为 "honking big" 连接)。换句话说,就是充分利用了 CMR 的应用程序可能要比普通 JDBC 程序员愿意编写的代码执行的更好 (我甚至讨厌看到使用内部连接的 SQL — 尤其是看到别人编写它的时候!)

因此,希望这项评估会使您深信不疑的使用 CMR。同时,有意思的是,即使当您的代码没有显式的使用 CMR 时,您仍然可以将 CMR 添加到实体并重新部署,而不需要更改任何方法。因此,只要您的代码始终使用 findByPrimaryKey() 方法,容器就可以试图利用所生成的 SQL 中的 CMR。这一点很关键。EJB 2.0 规范明确指出必须调用自定义查找程序,因为可能有关键逻辑隐藏其中。请查看上面的第三点,可以了解到这个选项是没有风险,即使您没有更改任何一行代码。

但是,返回并更改您的 procedural/CMP 方法也并不困难,它与 CMR 是一致的,比如:

public CustomerData  getOpenOrderForCustomer(int cId) {
	// Get the Customer DTO
	// Using PK here to discourage use of custom finders
	// But since this is the "root" of the call, it is optional
	Customer cRef = cHome.findByPrimaryKey(new CustomerKey(cId));
	CustomerData cData = cRef.getData();
	// Check to see if there is an open order
	int oId = cData.getOpenOrderId();
	if (oId == 0) {
		throw new OrderNotOpenException(cId);
	}
	// Get the Order entity and the DTO object !!fBPK is mandatory
	Order oRef = oHome.findByPrimaryKey(new OrderKey(oId));
	OrderData oData = oRef.getData();
	cData.setOrder(oData);
	
	// Get the array of Line Items DTOs set up
	// When not using CMRs, this custom finder is mandatory!
	Collection liList = liHome.findAllItemsForOrderId(oId);
	int liSize = liList.size();
	LineItemData[] liArray = new LineItemData[liSize];
	oData.setLineItems(liArray);
	Iterator liIterator = liList.iterator();
	LineItem liRef = null;
	LineItemData liData = null;
	Product pRef = null;
	for (int i = 0; i < liSize; i++) {
		// Get the Line Item DTO
		liRef = (LineItem) liIterator.next();
		liData = liRef.getData();
		// Get the Product DTO !!fBPK is mandatory to support join
		pRef = pHome.findByPrimaryKey(new ProductKey(pId));
		liData.setProduct(pRef.getData());
		liArray[i] = liData;
	}
	
	return cData;
}

这段代码可以被调整为只发布两个 SQL 语句,这是因为自定义的 findAllItemsForOrderId() 方法通过自定义查找器来调用,并强制"reset"。通过这种级别的调整,流程化 CMP-CMR(定义了 CMR,但没有明确的在代码中使用)应该在您手工编写的 JDBC 的一小部分中执行,同样也发布两个 SQL 语句。

但是,一旦你费尽周折的指定了 CMR 之后(基本上上述相同的代码行必须进行更改),即使代码是流程化的,您也将会真正发现使用 CMR 变得更加简单:

public CustomerData  getOpenOrderForCustomer(int cId) {
	// Get the Customer DTO
	// Using PK here to discourage use of custom finders
	// But since this is the "root" of the call, it is optional
	Customer cRef = cHome.findByPrimaryKey(new CustomerKey(cId));
	CustomerData cData = cRef.getData();
	// Check to see if there is an open order
	// Note that this check is now much more "meaningful" since
	//	the code does not have to interpret '0' as no open order
	Order oRef = cData.getOpenOrder();
	if (oRef == null) {
		throw new OrderNotOpenException(cId);
	}
	// Get the Order DTO object, since we already have the ref
	OrderData oData = oRef.getData();
	cData.setOrder(oData);
	
	// Get the array of Line Items DTOs using the CMR
	Collection liList = oRef.getLineItems();
	int liSize = liList.size();
	LineItemData[] liArray = new LineItemData[liSize];
	oData.setLineItems(liArray);
	Iterator liIterator = liList.iterator();
	LineItem liRef = null;
	LineItemData liData = null;
	Product pRef = null;
	for (int i = 0; i < liSize; i++) {
		// Get the Line Item DTO
		liRef = (LineItem) liIterator.next();
		liData = liRef.getData();
		// Get the Product DTO using the CMR
		liData.setProduct(liRef.getProduct().getData());
		liArray[i] = liData;
	}
	
	return cData;
}

不要忘记,您可以通过对每个实体构建(或者更好是,生成)会话虚包,从而在您的会话 ejbCreate() 方法中丢弃大部分繁琐的本机和 IC 查找。多么了不起的胜利!从今以后,我希望你把签名改为:

Led to Water

我同时也希望您能领略这些内容,不再犹豫不决。

那么 OK,
您的 EJB 倡导者





回页首


结束语

上月所讲述的最佳实践基本上是关于在单独事务中使用非相关的 CMP 实体 EJB。它们构成了正确使用 CMP 的基础。这些最佳实践包括:

  1. 始终使用会话虚包来启动有关所使用实体的全局事务。
  2. 对 CMP 实体总是使用本地接口以避免可能的双跳转(double hop)。
  3. 即使使用本地接口,也应创建自定义的 create, find, get 和 set 方法,从而最小化各层之间的障碍,并提高重用和可维护性。
  4. 在本机上找到一个基数,或者通过迭代器从集合中检索到下一个基数后,尽量使用不超过一个调用。

通过本月的交流,我们详细了解了在单个事务中使用多个相关实体 EJB 的场景。事实就是不使用 CMR 的 CMP 比起手工编写的 JDBC,执行效率明显要差。那就是为什么 2.0 版本之前的实体 EJB 没有得到广泛使用的根本原因。但是,EJB 2.0 已经解决了大部分场景存在的问题。

在这次的交流中,我们研究了以下与实体相关的最佳实践:

  1. 您已经妥善设置了与您的数据模型相匹配的 CMR,即使应用程序代码并没有显式的使用它们。
  2. 衡量系统的性能,以及测试您的实体不满足预期结果的地方,尝试调整容器以优化查询。可以让性能接近甚至优于手工编写的 JDBC 程序 - 且工作量更少,更利于重用,并与存储库无关。
  3. 同样,要考虑重构您的会话虚包,使它们与单个"网关"实体相关联,该实体是代码的起始点。根据这些网关实体来构建应用程序概要文件和自定义查找器,以重用协调参数。
  4. 如果性能仍然没有满足预期目标,那么请检查实体 EJB 的使用,查看是否可以使用 findByPrimaryKey() 或者 CMR 调用来代替自定义查找器。换句话说,除了工作单元中对实体 EJB(该实体被看作是到其他相关实体的"网关")的第一个调用之外,使用自定义查找器应该被认为是一种反模式。如果建立了这种反模式,则可以按照上面第二点所述的那样,尝试重新协调此应用程序。
  5. 如果从零开始创建此应用程序,或者因为某些原因重构代码时(例如,最佳实践 #3 或者 #4),就要考虑对您的会话 bean 使用 OO 方式以使它们成为真正的虚包。这种重构可以渐进式(method-by-method)的完成。
  6. 如果给定工作单元的性能仍然不能满足您的预期目标,那么就要对该事务中使用的网关对象上所调用的方法使用 OO 方式,并编写直接使用 JDBC 的 BMP 方法(请参阅参考资料中 Kyle Brown 的书籍)。并请将范例发给我!
  7. 经常检查产品更新,查看是否有可以尝试使用的新的协调选项,以使您可以不借助于 BMP 方法,或者只是可以让您提高系统性能。最终的目标是 100% CMP/CMR 和 0% BMP。但这可能还需要一段时间。

本文到此为止。这个月,我也认识到了不要对下个月的文章中将要讨论的内容做出承诺。



参考资料



关于作者

作者照片

Geoff Hambrick是 IBM Software Services for WebSphere Enablement Team 的一名高级顾问。他住在 Round Rock,Texas(Austin 附近)。Enablement Team 一般通过深入的技术简报和短期概念验证支持,来支持售前流程。Geoff 创建和宣传了一些最佳实践,用于开发托管在 IBM WebSphere Application Server 上的 J2EE 应用程序,并在 2004 年 3 月被评为 IBM 杰出工程师




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款