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