|
亲爱的 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 倡导者
|