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

developerWorks 中国  >  WebSphere  >

将容器管理的持久性 Bean 用于面向服务的体系结构

使用 WebSphere Process Server 控制 CMP Bean 的连接和持久性逻辑

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

讨论

样例代码


级别: 中级

John Alcorn (jalcorn@us.ibm.com), WebSphere Business Monitor 首席程序员, IBM

2005 年 11 月 02 日

本文将介绍如何使用 IBM WebSphere Process Server 对容器管理的持久性 (CMP) Bean 的连接和持久性逻辑加以控制,使其可以存储在非关系数据库中。您将了解各种受支持的数据访问模式,并将了解多个基本使用场景,其中包含相关的示例(既有非常简单的示例,也有较为复杂的示例)。本文涉及到了一些最佳实践,如从特殊的 Java 类 UserDefinedPushDownMethodsImpl 中使用服务组件体系结构(Service Component Architecture,SCA),支持重用通过面向服务的体系结构 (SOA) 公开遗留系统的服务,以及让用户不必了解与非关系数据存储进行通信所涉及到的全部细节。

引言

过去十年中,数据访问方面最重要的进步就是 CMP 的出现。此概念最初是在 Enterprise JavaBeans (EJB) 规范的第一版中引入的,随后在 EJB 规范 2.0 版中进行了大幅度改进。简言之,CMP 使软件工程师可以开发能将数据保存在任意数据存储中的实体对象,而无需在实现类中嵌入此类数据访问的逻辑。持久化 CMP 视图 Bean 所需的必要信息是在应用程序组装阶段收集的,供应商特定的代码生成器将在应用程序部署阶段使用这些信息来产生实际的数据访问逻辑。CMP 大幅度提高了部署的简易性和实体 EJB 的可移植性,而 CMP Bean 通常可以具有更高的服务质量 (QoS),如对缓存和连接池的运行时管理控制等。不过,CMP 实体 Bean 通常限于使用关系数据库。

随着面向服务的体系结构的应用越来越广泛,公开遗留非关系数据存储的访问权限的首选方法是服务。此类服务可以从本质上相当于“黑盒”的地方为调用方提供标准的数据访问方式,而不要求调用方知道任何关于服务与遗留非关系数据存储实际上如何通信的幕后细节。通过使用 SOA,IBM® WebSphere® Process Server Version 6.0 扩展了 CMP 的使用范围,可以将其用于非关系数据存储中,从而极大地增强了 EJB 和 Java2 Enterprise Edition (J2EE) 规范的价值主张。





回页首


基础知识

对于实体 EJB,有两种持久性形式:Bean 管理的持久性 (BMP) 和 CMP。开发 BMP Bean 的过程工作量更大(因为开发人员必须手动编写连接和持久性逻辑,并直接将其嵌入到 Bean 实现类的生命周期方法中),也更容易出错。通常,应用程序服务器提供的很多增值功能 BMP Bean 都无法利用。在实际操作中,通常仅在应用程序服务器不提供所需的连接和持久性支持时才使用 BMP;而通常更多使用的是 CMP。

在 WebSphere Process Server 6.0 产品中,将 CMP Bean 存储到任意(可能为非关系型的)数据存储中的支持称为支持任何系统的容器管理持久性 (CMP/A)。务必注意,对于任何 CMP Bean,开发人员都不需要知道 Bean 将最终存储到非关系数据存储中。

让我们仔细了解一下处理 EJB 时将涉及到的各个角色:

开发人员
Bean 开发人员编写并编译 EJB 的 Java™ 源代码。开发人员的主要任务是编写 Bean 实现类,包括任何业务逻辑方法的代码。其他任务包括定义 Bean 的本地和/或远程接口及其本地和/或远程 Home 接口,定义键、CMP 字段以及到其他 CMP Bean 的任何容器管理的关系 (CMR)。开发人员不关心 Bean 的最终持久化方式。
组装人员
Bean 组装人员定义 EJB 部署描述符。这包括 EJB 规范定义的泛型描述符 (ejb-jar.xml) 和任何供应商的特定部署描述符扩展和绑定。尽管可以手动完成此工作,但通常通过 IDE 中的图形用户界面向导来进行。组装人员指定如何持久化 CMP Bean 的细节。对于传统关系 CMP Bean,这通常包括将 CMP 字段映射到关系数据库表的列。组装操作将生成一个 EJB jar 文件,在 EJB jar 的 META-INF 目录中包含一组 XML/XMI 文件,其中捕获了稍后部署人员将需要的各种信息。CMP/A 组装是通过 IBM WebSphere Integration Developer Version 6.0 中的 EJB Deployment Descriptor Editor 的 PushDown 选项卡完成的。
部署人员
Bean 部署人员使用 Bean 组装人员提供的信息来为特定供应商的应用程序服务器部署 Bean。正是在此期间生成 CMP Bean 的实际持久性代码。对于 CMP/A Bean,客户可以在此时插入他们自己的自定义持久性逻辑。
管理员
管理员将已部署的 EJB jar 文件(通常包含在 J2EE Enterprise Application Archive——.ear 文件中)安装到服务器运行时中。安装了应用程序后,管理员还负责对应用程序进行管理,如指定与应用程序中的 EJB 相关的任何运行时配置属性值等。此类配置属性的示例包括缓存行为、连接池管理和用户 ID 到已定义的安全角色的映射等。管理员还将配置与 CMP/A Bean 关联的 J2C 连接工厂。通常 CMP Bean 可提供比 BMP Bean 更多的管理控制。

CMP/A 要求在组装阶段进行一些其他步骤,以创建一个特殊的特定于 IBM 的 EJB 部署描述符扩展(有关详细信息,请参阅使用场景部分)。但主要的 CMP/A 工作是由 Bean 开发人员完成的,开发人员需要在一个名为 UserDefinedPushDownMethodsImpl (UDPDMI) 的特殊 Java 类中提供所需的数据访问代码。这个特殊类是由 WebSphere Integration Developer 6.0 生成的(请参阅示例部分,以获得一些示例 UDPDMI 代码)。如果对相应的 CMP Bean 调用 Create、Retrieve、Update 或 Delete (CRUD) 方法,将对此类调用对应的方法以进行响应。在最简单的情况下,UDPDMI 将具有以下方法:ejbCreate、ejbFindByPrimaryKey、ejbStore 和 ejbRemove。更复杂的 Bean 可以添加自定义查找程序 (ejbFind*) 和选择方法 (ejbSelect*),前者包括与 CMR 关联的查找程序,而后者可以返回完整 CMP Bean 实例集合或单个 CMP 字段的集合。

请注意,到目前为止所讨论的全部内容都完全符合 CMP 2.x Bean 的 EJB 规范;即,任何符合规范的可移植 CMP Bean 都可以使用 IBM 工具和运行时保存到任意非关系数据存储中。不过,WebSphere Process Server 还支持称为“数据逻辑”的特定于 IBM 的 J2EE 编程模型扩展,此扩展实质上允许开发人员将在 CMP Bean 实现类上定义的任意方法的实现推迟到部署时进行。此类方法在 CMP Bean 实现类上声明为抽象方法,可以“向下推”到 UDPDMI,由部署人员进行实现,以与后端数据存储交互,例如调用以更成熟的方式与数据交互的某个遗留事务,而不采用简单的 CMP 生命周期方法。这样,就可以通过 J2EE 编程模型将现有遗留应用程序向用户公开,而不会将此类应用程序限制为采用基本 CRUD 方式与遗留数据进行交互。有关使用 CRUD 方法和数据逻辑方法的详细信息,请参阅使用场景部分。

IBM 在 WebSphere Business Integration-Server Foundation Version 5.1 产品及其相关的 WebSphere Studio Application Developer Integration Edition Version 5.1 中首次引入 CMP/A。在该版本中,CMP/A 更多地是注重直接从 UDPDMI 访问后端数据存储,如通过直接使用 JDBC 或 JCA 进行访问。当然,在 WebSphere Process Server Version 6.0 中仍然支持此功能,但更注重通过中间 SOA 层间接访问此类数据存储。这样,CMP Bean 部署人员就不需要了解与后端数据存储交互的底层细节,而只需要了解如何调用服务上的可用操作即可,可以转而让服务实现人员来确定如何与后端数据存储进行最好的交互。实现 SOA 访问的首选方法是通过在该产品的 6.0 版中引入的服务组件体系结构 (SCA) 进行。还应该注意,WebSphere Business Integration Server Foundation Version 5.1 仅支持采用 EJB 2.0 Bean 的 CMP/A,而 WebSphere Process Server Version 6.0 同时支持采用 EJB 2.0 和 EJB 2.1 Bean 的 CMP/A。





回页首


数据访问模式

  1. 模板化关系数据访问:当希望将 CMP Bean 保存到关系数据库,但又有 WebSphere 的缺省关系数据访问支持没有涵盖的特殊数据访问要求时,可以使用模板化关系数据访问模式。此模式支持两种后端类型:Java DataBase Connectivity (JDBC) 和 Structured Query Language for Java (SQLj)。

    此模式的第一个例子是 Bean 的 CRUD 方法的实现,这些方法通过调用关系数据库存储过程实现(如通过 JDBC CallableStatements)。可以从 IBM WebSphere 开发者技术期刊的另一篇文章了解更多关于将 CMP/A 和存储过程一起使用的内容。

    当需要使用 CMP Bean,而其要求使用不为 CMP Bean 提供本机支持的服务器的某个特殊功能时,可以使用第二种数据访问类型。通常,这意味着您不得不编写一个 BMP Bean,但通过使用 CMP/A,您可以获得使用 CMP Bean 的好处,且同时仍然可以使用目标数据库在其他情况下不支持的功能。请注意,对于这种类型的 CMP/A Bean,并不要求您将所有 CRUD 方法向下推到 UDPDMI;可以只将需要特殊逻辑的方法向下推,而让容器按照正常方式处理其他 CRUD 方法即可。

  2. 模板化的非关系数据访问:模板化非关系数据访问模式用于将 CMP Bean 保存到非关系数据存储,而这是 CMP/A 的主要设计用途。此模式支持五种后端类型:CCI、EJB、JAX-RPC、WSIF 和 SCA,下文将对其进行讨论。模板化非关系数据访问提供了功能和易用性之间的最佳平衡,因为大部分代码都是通过使用工具生成的,只有一些地方需要进行手动编程(在代码中用注释标明)。
    • CCI:CCI 后端类型用于将 CMP Bean 保存到提供资源适配器的后端,该资源适配器基于可以用于进行持久性操作的 J2EE Connector Architecture (JCA)。对于此后端类型,UDPDMI 的每个方法均包含 JCA Common Client Interface (CCI) 代码,如用于以下用途的代码:创建输入 CCI IndexedRecord,使用其他的输入数据对其进行填充(如 CMP Bean 的键值),执行 CCI Interaction,然后从输出 IndexedRecord 检索任何返回数据。在与输入和输出 CCI Records 及任何必需的异常处理相关的位置,需要客户对生成的代码进行插入或修改。
    • EJB:EJB 后端类型用于将 CMP Bean 保存到 EJB Facade,如公开包含所需数据访问逻辑的方法的无状态会话。对于此后端类型,UDPDMI 的每个方法均包含查找目标 EJB Home、获得实例和调用指定方法的代码。在与向目标 EJB 的方法传递正确参数、处理任何返回值及任何必要的异常处理相关的位置,需要客户对生成的代码进行插入或修改。
    • JAX-RPC:JAX-RPC 后端类型用于将 CMP Bean 保存到提供可通过 Java API for XML Remote Procedure Calls (JAX-RPC) 访问的 SOA 接口的后端系统。对于此后端类型,UDPDMI 的每个方法均包含用于查找 Web 服务和对其调用操作(通过 DII)的代码。在与填充向 Web 服务操作传递的输入对象数组、处理任何返回值及任何必要的异常处理相关的位置,需要客户对生成的代码进行插入或修改。请注意,6.0 版本通过添加对复杂类型和错误的改进支持改善了 JAX-RPC 支持。
    • WSIF:WSIF 后端类型用于将 CMP Bean 保存到提供可通过 Apache 的 Web Services Invocation Framework (WSIF) 访问的 SOA 接口的后端系统。对于此后端类型,UDPDMI 的每个方法都包含用于获取 WSIF 服务、创建输入 WSIF 消息、将其传递到 WSIF 操作并处理任何返回消息的代码。请注意,WSIF 后端类型在 6.0 版中已被弃用,取而代之的是 SCA。
    • SCA:SCA 后端类型用于将 CMP Bean 保存到提供可通过 Service Component Architecture (SCA) 访问的 SOA 接口的后端系统。对于此后端类型,UDPDMI 的每个方法均包含用于定位 Web 服务和对其调用操作(通过 DII)的代码。在与填充向服务操作传递的输入服务数据对象 (SDO)、处理任何返回值及任何必要的异常处理相关的位置,需要客户对生成的代码进行插入或修改。这个 6.0 版引入的新后端类型目前是通过 SOA 保存 CMP Bean 的首选方法。
  3. 任意形式的数据访问:任意形式的数据访问模式用于将 CMP Bean 保存到不受上述任何模板化模式支持的数据存储中。此模式的后端类型称为 Custom。这是一个最灵活的选项,但要求做的工作也最多,因为使用此选项生成的代码最少:实际上,每个方法仅包含一个通用的注释“Place appropriate code here”,另外还有一些描述代码的可用输入参数和代码必须构造并返回的输出参数(如果有)。




回页首


使用场景

这一部分说明如何使用 WebSphere Integration Developer 6.0 来组装和部署 CMP/A Bean。屏幕快照均来自下面示例 #2 中基于 SCA 的Department Bean。

WebSphere Integration Developer 中的大部分工作通常都是从 Business Integration Perspective 执行的,如处理基于业务流程执行语言(Business Process Execution Language,BPEL)的应用程序时。处理 CMP/A 应用程序时,必须切换到 J2EE 透视图。从菜单栏选择 Window=>Open Perspective=>Other,再在显示的对话框上单击 Show All 复选框,从列表中选择 J2EE,然后单击 OK


图 1. 切换到 J2EE Perspective
切换到 J2EE Perspective

CMP/A 应用程序的组装是在 WebSphere Integration Developer 中的 EJB Deployment Descriptor Editor 的 PushDown 选项卡上执行的。为了使这个特殊的特定于 WebSphere Integration Developer 的选项卡可用,必须启用 J2EE 编程模型扩展(Programming Model Extensions,PME)功能。为此,从菜单栏选择 Window=>Preferences,单击左窗格中“Workbench”旁边的加号,然后单击 Capabilities。在右窗格中单击“Advanced J2EE”旁边的加号,选中“WebSphere PME Development”旁边的复选框,最后单击 Apply,然后单击 OK。如果 EJB Deployment Descriptor Editor 已打开,则将其关闭,然后重新打开,以使更改生效。


图 2. 启用 J2EE PME 功能
启用 J2EE PME 功能

要指定 CMP/A 组装设置,请打开 EJB Deployment Descriptor Editor(在 J2EE Perspective 中双击 CMP Bean),然后单击 Pushdown 选项卡。下图显示了已组装 CMP/A Bean 的一个示例(下面示例 #2 中的 Department Bean)。


图 3. 指定 CMP/A 组装设置
指定 CMP/A 组装设置

“Pushdown entities”部分中最初不会有任何条目。要添加条目,请单击 Add,然后选择要组装的 Bean 的名称和希望使用的后端类型。


图 4. 添加 Pushdown entities
添加 Pushdown entities

接下来,您可以对每个下推方法进行映射:从列表选择下推方法,单击 Edit,然后输入希望的操作名称(例如,可以指定要调用的 JDBC 存储过程、CICS 函数或 Web 服务操作的名称)。


图 5. 对每个下推方法进行映射
对每个下推方法进行映射

为 CMP/A Bean 映射了所有下推方法后,发出该 Bean 的 UDPDMI,以便部署人员可以使用恰当的逻辑对其进行填充。要发出此文件,单击 Generate,选择需要的 Bean,然后单击 Finish


图 6. 发出 Bean 的 UDPDMI
发出 Bean 的 UDPDMI

将在新选项卡打开发出的结果文件(如果关闭此选项卡,可以通过在 EJB Deployment Descriptor Editor 的 PushDown 选项卡上双击该 Bean 来返回编辑此文件)。有关编写此文件的更多信息,请参阅下面的示例部分。使用需要的持久逻辑填充了此文件后,将其保存,然后通过右键单击 Ejb 模块并选择“Deploy”来部署 EJB jar 文件,以生成调用 UDPDMI 的方法的代码。然后可以在 WebSphere Integration Developer Unit Test Environment (UTE) 中右键单击 Enterprise Application 并选择 Run on Server,从而对该 Bean 进行测试。如果需要,也可以使用调试器执行 UDPDMI 代码。





回页首


示例

将演示两个示例:一个简单示例和一个复杂示例。这两个示例均基于 SOA,使用的是模板化非关系数据访问模式。在第一个示例中,将仅演示基于 SOA 的 Bean 的四个基本 CRUD 方法;而在第二个示例中,将使用两个 CMP Bean,并在二者之间使用了一个 CMR(具有 read-ahead),还有一个 ejbSelect 方法和数据逻辑方法。将提供 J2EE ear 文件的链接,这些文件中包含每个示例的完整源代码。请注意,每个示例中的大部分代码都是由 WebSphere Integration Developer 生成的;只有粗体显示的代码是由部署人员实际输入的。

示例 #1

在本例中,我们将使用一个通过 SOA 方法保存的基本 Account Bean,其包含 ID 和帐户余额。它使用 JAX-RPC 后端类型,通过描述 Web 服务的 Web 服务定义语言(Web Services Definition Language,WSDL)保存。在本例中,有一个作为 Web 服务部署的无状态会话,名为 AccountBackend;此示例中的 AccountBackend Bean 直接在内存 HashMap 保存信息,可以将此技术应用于任意复杂的 Web 服务(从 CMP/A Bean 的角度来看,这实质上是一个“黑盒”)。这可能是最简单的非关系 Bean,演示了四个基本的 CRUD 方法。

ejbCreate

在 ejbCreate 方法中,部署人员必须添加适当的逻辑,以生成 UDPDMI 代码来将新的 Bean 数据插入后端数据存储。部署人员还必须处理 DuplicateKeyException 的情况。


清单 1. ejbCreate
				/**
    * User-defined push-down method ejbCreate.
    * If the bean already exists in the backend datastore, then you should
    * throw a javax.ejb.DuplicateKeyException.
    *
    * @param bean Reference to the bean implementation class
    * @param accessIntent Reference to the access intent settings to be used for this method
    * @param connection The CCI connection to the back-end system.  This
    * connection was previously obtained (by the EJBToRAAdapter implementation)
    * via connectionFactory.getConnection(connectionSpec).  For non-relational,
    * non-CCI based beans, this will be null and the user will have to manually
    * connect to the back-end system and close the connection before returning.
    *
    * @exception javax.ejb.CreateException Thrown if an exception occurs creating bean data
    * @exception ResourceException Any other exceptions are wrapped in a
    * ResourceException.
    **/
   public void ejbCreate(
      AccountBean bean,
      AccessIntent accessIntent,
      Object connection)
      throws javax.ejb.CreateException, ResourceException
   {
      log.entering("AccountBeanUserDefinedPushDownMethodsImpl",
         "ejbCreate(AccountBean, AccessIntent, Object)",
         new Object[] {bean, accessIntent, connection});
      //The following code uses the JAX-RPC Dynamic Invocation Interface (DII).
      //You can replace it with code that uses static (strongly-typed) JAX-RPC
      //stubs (produced by a tool such as wscompile or WSDL2Java) if desired.
      if (service == null) init();
      javax.ejb.DuplicateKeyException dupKey = null;
      try {
         javax.xml.namespace.QName operation = new javax.xml.namespace.QName("createAccount");
         // Change line below if operation returns a response (that is, if it is non-void)
         // For example, if it returns a float, then set returnType as follows:
         // returnType = new QName("http://www.w3.org/2001/XMLSchema", "float");
         javax.xml.namespace.QName returnType = new javax.xml.namespace.QName(
           "http://www.w3.org/2001/XMLSchema", "boolean");
         int paramCount = 2; //change to actual param count
         Object[] params = new Object[paramCount];
         // Begin customer-written code to populate params array
         params[0] = bean.getId();
         params[1] = new Float(bean.getBalance());
         // End customer-written code to populate params array
         Object result = helper.invokeJAXRPCCall(
            service, port, operation, returnType, endpointAddress, params);
         boolean success = ((Boolean) result).booleanValue();
         if (!success) dupKey = new javax.ejb.DuplicateKeyException(bean.getId().toString());
      } catch (ResourceException re) {
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbCreate(AccountBean, AccessIntent, Object)", re);
         throw re;
      } catch (Exception e) {
         ResourceException re = helper.createResourceException(e, this.getClass());
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbCreate(AccountBean, AccessIntent, Object)", re);
         throw re;
      }
      //Note that if the return code from the back-end datastore interaction
      //indicated that a user-defined exception (declared in the push-down
      //method's signature) should be thrown, then throw it here.
      if (dupKey != null) {
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbCreate(AccountBean, AccessIntent, Object)", dupKey);
         throw dupKey;
      }
      log.exiting("AccountBeanUserDefinedPushDownMethodsImpl",
         "ejbCreate(AccountBean, AccessIntent, Object)");
   }

ejbFindByPrimaryKey

在 ejbFindByPrimaryKey 方法中,部署人员必须添加适当的逻辑,以生成 UDPDMI 代码来从后端数据存储检索 Bean 数据。部署人员还必须处理 ObjectNotFoundException 的情况。

请注意,查找程序方法是最难实现的方法,因为您必须按照 WebSphere 运行时所期望方式返回数据;为该方法生成的 JavaDoc 注释中包含了说明 CMP 必需字段的信息。返回值是 IndexedRecords 的一个 IndexedRecord。内部 IndexedRecord 保存特定 Bean 实例的 CMP 字段,而外部 IndexedRecord 保存查找程序找到的 Bean 集合(此外部 IndexedRecord 仅包含 ejbFindByPrimaryKey 的单个条目)。

另请注意,理想情况下,ejbFindByPrimaryKey 的逻辑应基于 AccessIntent 参数,以确定是否锁定数据存储中的条目。为了使这个示例尽量简单,在此我们将不会演示这么复杂的内容。


清单 2. ejbFindByPrimaryKey
				/**
    * User-defined push-down method ejbFindByPrimaryKey.
    * For single object finders, if the bean does not exist in the backend
    * datastore, then you should throw a javax.ejb.ObjectNotFoundException.
    * For multi-object finders, if no results are found in the backend
    * datastore, then you should return an empty Record.
    *
    * @param arg0 Push-down method Parameter #0
    * @param accessIntent Reference to the access intent settings to be used for this method
    * @param connection The CCI connection to the back-end system.  This
    * connection was previously obtained (by the EJBToRAAdapter implementation)
    * via connectionFactory.getConnection(connectionSpec).  For non-relational,
    * non-CCI based beans, this will be null and the user will have to manually
    * connect to the back-end system and close the connection before returning.
    *
    * @return The return type of this finder method is a Record of Records,
    * each inner record of which contains the values of the bean's CMP fields,
    * with primitives wrapped (such as a java.lang.Integer for an int).
    * Note that with single-bean finders, like ejbFindByPrimaryKey, the outer
    * record will contain exactly one inner record, whereas with multi-bean
    * finders the outer record will contain zero or more inner records.  The
    * order of the fields of the inner record must be as follows:
    *    Position #0: id
    *    Position #1: balance
    *
    * @exception javax.ejb.FinderException Thrown if an exception occurs retrieving bean data
    * @exception ResourceException Any other exceptions are wrapped in a
    * ResourceException.
    */
   public Record ejbFindByPrimaryKey(
      java.lang.Integer arg0,
      AccessIntent accessIntent,
      Object connection)
      throws javax.ejb.FinderException, ResourceException
   {
      log.entering("AccountBeanUserDefinedPushDownMethodsImpl",
         "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)",
         new Object[] {arg0, accessIntent, connection});
      //The following code uses the JAX-RPC Dynamic Invocation Interface (DII).
      //You can replace it with code that uses static (strongly-typed) JAX-RPC
      //stubs (produced by a tool such as wscompile or WSDL2Java) if desired.
      if (service == null) init();
      javax.ejb.ObjectNotFoundException notFound = null;
      IndexedRecord returnValue = helper.createCCIIndexedRecord();
      try {
         javax.xml.namespace.QName operation = new javax.xml.namespace.QName("retrieveAccount");
         // Change line below if operation returns a response (that is, if it is non-void)
         // For example, if it returns a float, then set returnType as follows:
         // returnType = new QName("http://www.w3.org/2001/XMLSchema", "float");
         javax.xml.namespace.QName returnType = new javax.xml.namespace.QName(
            "http://www.w3.org/2001/XMLSchema", "float");
         int paramCount = 1; //change to actual param count
         Object[] params = new Object[paramCount];
         // Begin customer-written code to populate params array
         params[0] = arg0;
         // End customer-written code to populate params array
         Object result = helper.invokeJAXRPCCall(
            service, port, operation, returnType, endpointAddress, params);
         // Add records to returnValue record as described in this method's JavaDoc
         if (result == null) {
            notFound = new javax.ejb.ObjectNotFoundException(arg0.toString());
         } else {
            IndexedRecord account = helper.createCCIIndexedRecord();
            account.add(0, arg0);     //id
            account.add(1, result);   //balance
            returnValue.add(0, arg0); //single object finder
         }
      } catch (ResourceException re) {
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)", re);
         throw re;
      } catch (Exception e) {
         ResourceException re = helper.createResourceException(e, this.getClass());
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)", re);
         throw re;
      }
      //Note that if the return code from the back-end datastore interaction
      //indicated that a user-defined exception (declared in the push-down
      //method's signature) should be thrown, then throw it here.
      if (notFound != null) {
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)", notFound);
         throw notFound;
      }
      log.exiting("AccountBeanUserDefinedPushDownMethodsImpl",
         "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)", returnValue);
      return returnValue;
   }

ejbStore

在 ejbStore 方法中,部署人员必须添加适当的逻辑,以生成 UDPDMI 代码来将已更改的数据更新到后端数据存储中。部署人员还必须处理 NoSuchEntityException 的情况。

应了解,在理想情况下,ejbStore 的逻辑应该基于 AccessIntent 参数,以确定是否检查数据存储来确定自从第一次加载 Bean 数据以来,是否有别人对其进行了修改(在优化并发控制场景中,不会将其锁定)。为了使这个示例尽量简单,在此我们将不会演示这么复杂的内容。


清单 3. ejbStore
				/**
    * User-defined push-down method ejbStore.
    * If the bean no longer exists in the backend datastore, then you should
    * throw a javax.ejb.NoSuchEntityException.
    *
    * @param bean Reference to the bean implementation class, containing the "new" field values
    * @param accessIntent Reference to the access intent settings to be used for this method
    * @param oldValues IndexedRecord holding original values of CMP fields.  If
    * the accessIntent indicates that optimistic concurrency control is being
    * used, then this holds the values of the CMP fields as they were when the
    * bean was loaded.  If the values have changed since then in the backend
    * datastore, then you should throw a javax.ejb.NoSuchEntityException.
    * The order of the CMP fields in the IndexedRecord is as follows:
    *    Position #0: id
    *    Position #1: balance
    * @param connection The CCI connection to the back-end system.  This
    * connection was previously obtained (by the EJBToRAAdapter implementation)
    * via connectionFactory.getConnection(connectionSpec).  For non-relational,
    * non-CCI based beans, this will be null and the user will have to manually
    * connect to the back-end system and close the connection before returning.
    *
    * @exception javax.ejb.NoSuchEntityException Thrown if bean no longer exists
    * @exception ResourceException Any other exceptions are wrapped in a
    * ResourceException.
    */
   public void ejbStore(
      AccountBean bean,
      AccessIntent accessIntent,
      IndexedRecord oldValues,
      Object connection)
      throws javax.ejb.NoSuchEntityException, ResourceException
   {
      log.entering("AccountBeanUserDefinedPushDownMethodsImpl",
         "ejbStore(AccountBean, AccessIntent, IndexedRecord, Object)",
         new Object[] {bean, accessIntent, oldValues, connection});
      //The following code uses the JAX-RPC Dynamic Invocation Interface (DII).
      //You can replace it with code that uses static (strongly-typed) JAX-RPC
      //stubs (produced by a tool such as wscompile or WSDL2Java) if desired.
      if (service == null) init();
      javax.ejb.NoSuchEntityException noSuchEntity = null;
      try {
         javax.xml.namespace.QName operation = new javax.xml.namespace.QName("updateAccount");
         // Change line below if operation returns a response (that is, if it is non-void)
         // For example, if it returns a float, then set returnType as follows:
         // returnType = new QName("http://www.w3.org/2001/XMLSchema", "float");
         javax.xml.namespace.QName returnType = new javax.xml.namespace.QName(
            "http://www.w3.org/2001/XMLSchema", "boolean");
         int paramCount = 2; //change to actual param count
         Object[] params = new Object[paramCount];
         // Begin customer-written code to populate params array
         params[0] = bean.getId();
         params[1] = new Float(bean.getBalance());
         // End customer-written code to populate params array
         Object result = helper.invokeJAXRPCCall(
            service, port, operation, returnType, endpointAddress, params);
         boolean success = ((Boolean) result).booleanValue();
         if (!success) {
            noSuchEntity = new javax.ejb.NoSuchEntityException(bean.getId().toString());
         }
      } catch (ResourceException re) {
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbStore(AccountBean, AccessIntent, IndexedRecord, Object)", re);
         throw re;
      } catch (Exception e) {
         ResourceException re = helper.createResourceException(e, this.getClass());
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbStore(AccountBean, AccessIntent, IndexedRecord, Object)", re);
         throw re;
      }
      //Note that if the return code from the back-end datastore interaction
      //indicated that a user-defined exception (declared in the push-down
      //method's signature) should be thrown, then throw it here.
      if (noSuchEntity != null) {
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbStore(AccountBean, AccessIntent, IndexedRecord, Object)", noSuchEntity);
         throw noSuchEntity;
      }
      log.exiting("AccountBeanUserDefinedPushDownMethodsImpl",
         "ejbStore(AccountBean, AccessIntent, IndexedRecord, Object)");
   }

ejbRemove

在 ejbRemove 方法中,部署人员必须添加适当的逻辑,以生成 UDPDMI 代码来从后端数据存储删除 Bean 数据。部署人员还必须处理 RemoveEntityException 的情况。


清单 4. ejbRemove
				 /**
    * User-defined push-down method ejbRemove.
    * If the bean does not exist in the backend datastore, then you should
    * throw a javax.ejb.RemoveException.
    *
    * @param bean Reference to the bean implementation class
    * @param accessIntent Reference to the access intent settings to be used for this method
    * @param connection The CCI connection to the back-end system.  This
    * connection was previously obtained (by the EJBToRAAdapter implementation)
    * via connectionFactory.getConnection(connectionSpec).  For non-relational,
    * non-CCI based beans, this will be null and the user will have to manually
    * connect to the back-end system and close the connection before returning.
    *
    * @exception javax.ejb.RemoveException Thrown if an exception occurs deleting bean data
    * @exception ResourceException Any other exceptions are wrapped in a
    * ResourceException.
    */
   public void ejbRemove(
      AccountBean bean,
      AccessIntent accessIntent,
      Object connection)
      throws javax.ejb.RemoveException, ResourceException
   {
      log.entering("AccountBeanUserDefinedPushDownMethodsImpl",
         "ejbRemove(AccountBean, AccessIntent, Object)",
         new Object[] {bean, accessIntent, connection});
      //The following code uses the JAX-RPC Dynamic Invocation Interface (DII).
      //You can replace it with code that uses static (strongly-typed) JAX-RPC
      //stubs (produced by a tool such as wscompile or WSDL2Java) if desired.
      if (service == null) init();
      javax.ejb.RemoveException removeEx = null;
      try {
         javax.xml.namespace.QName operation = new javax.xml.namespace.QName("deleteAccount");
         // Change line below if operation returns a response (that is, if it is non-void)
         // For example, if it returns a float, then set returnType as follows:
         // returnType = new QName("http://www.w3.org/2001/XMLSchema", "float");
         javax.xml.namespace.QName returnType = new javax.xml.namespace.QName(
            "http://www.w3.org/2001/XMLSchema", "boolean");
         int paramCount = 1; //change to actual param count
         Object[] params = new Object[paramCount];
         // Begin customer-written code to populate params array
         params[0] = bean.getId();
         // End customer-written code to populate params array
         Object result = helper.invokeJAXRPCCall(
            service, port, operation, returnType, endpointAddress, params);
         boolean success = ((Boolean) result).booleanValue();
         if (!success) removeEx = new javax.ejb.RemoveException(bean.getId().toString());
      } catch (ResourceException re) {
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbRemove(AccountBean, AccessIntent, Object)", re);
         throw re;
      } catch (Exception e) {
         ResourceException re = helper.createResourceException(e, this.getClass());
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbRemove(AccountBean, AccessIntent, Object)", re);
         throw re;
      }
      //Note that if the return code from the back-end datastore interaction
      //indicated that a user-defined exception (declared in the push-down
      //method's signature) should be thrown, then throw it here.
      if (removeEx != null) {
         log.throwing("AccountBeanUserDefinedPushDownMethodsImpl",
            "ejbRemove(AccountBean, AccessIntent, Object)", removeEx);
         throw removeEx;
      }
      log.exiting("AccountBeanUserDefinedPushDownMethodsImpl",
         "ejbRemove(AccountBean, AccessIntent, Object)");
   }

cmpaExample1.ear

示例 #2

在此更为复杂的示例中,我们采用了两个 Bean,即 Department 和 Employee,其中 Department 具有到 Employee 的一对多 CMR。除了每个 Bean 的四个基本 CRUD 方法之外(为了简洁起见,我们在此将不会演示这些方法,因为其与示例 #1 中所示的方法十分相似;有关详细信息,请参阅 ear 文件中的源代码),我们将演示 Employee Bean 上的一个自定义查找程序(当对 Department Bean 调用 getEmployees() CMR getter 方法)和一个演示 read-ahead 的查找程序。我们还将演示 Employee Bean 上的 Select 方法(用于只获取指定部门的 Employee 的 salary 值)和 Employee Bean 上的数据逻辑方法(用于更改指定部门的所有 Employee 的 salary 值)。前一个示例可以在 WebSphere Business Integration Server Foundation 5.1 或 WebSphere Process Server 6.0 上运行,与此不同,本示例要求使用 WebSphere Process Server 6.0。

CMR

在 ejbFindEmployeesByDepartmentKey 方法中,部署人员必须添加适当的逻辑,以生成 UDPDMI 代码来根据所属的部门从后端数据存储检索 employee Bean 数据。由于此查找程序是多对象查找程序,因此如果没有找到任何条目则不会引发异常。

请注意,对于部署人员而言,CMR 的处理方式与普通查找程序方法很相似。在本例中(与 ejbFindByPrimaryKey 不同),将会返回多个 Bean,即输出 IndexedRecord 中将包含多个记录。每个输入记录除了包含 Bean 的 CMP 字段之外,还必须包含 CMR 源端的 Bean 的外键字段,例如 Employee 上的查找程序将返回 EmployeeCMP 字段值,后跟 Employee 所属的 Department 的 ID(类似地,此外键需要在 Employee 的 ejbStore 方法中进行保存)。请注意,另一方面,CMR(Employee 上的 getDepartment 方法)是在幕后自动处理的,由容器直接调用 Department Bean 上 ejbFindByPrimaryKey 方法来完成。

另请注意,如果启用了级联删除,并且删除了所属的 Department,则将自动删除属于该 Department 的 Employee(将对每个 Employee 的 UDPDMI 调用 ejbRemove)。最后请注意,其他自定义查找程序方法(与 CMR 不相关)将按照处理以下方法的方式进行类似的处理。

清单 5. ejbFindEmployeesByDepartmentKey

				/**
    * User-defined push-down method ejbFindEmployees&ByDepartmentKey.
    * For single object finders, if the bean does not exist in the backend
    * datastore, then you should throw a javax.ejb.ObjectNotFoundException.
    * For multi-object finders, if no results are found in the backend
    * datastore, then you should return an empty Record.
    *
    * @param arg0 Push-down method Parameter #0
    * @param accessIntent Reference to the access intent settings to be used for this method
    * @param connection The CCI connection to the back-end system.  This
    * connection was previously obtained (by the EJBToRAAdapter implementation)
    * via connectionFactory.getConnection(connectionSpec).  For non-relational,
    * non-CCI based beans, this will be null and the user will have to manually
    * connect to the back-end system and close the connection before returning.
    *
    * @return The return type of this finder method is a Record of Records,
    * each inner record of which contains the values of the bean's CMP fields,
    * with primitives wrapped (such as a java.lang.Integer for an int).
    * Note that with single-bean finders, like ejbFindByPrimaryKey, the outer
    * record will contain exactly one inner record, whereas with multi-bean
    * finders the outer record will contain zero or more inner records.  The
    * order of the fields of the inner record must be as follows:
    *    Position #0: id
    *    Position #1: name
    *    Position #2: salary
    *    Position #3: department_id
    *
    * @exception javax.ejb.FinderException Thrown if an exception occurs retrieving bean data
    * @exception ResourceException Any other exceptions are wrapped in a
    * ResourceException.
    */
   public Record ejbFindEmployeesByDepartmentKey(
      java.lang.Integer arg0,
      AccessIntent accessIntent,
      Object connection)
      throws javax.ejb.FinderException, ResourceException
   {
      log.entering("EmployeeBeanUserDefinedPushDownMethodsImpl",
         "ejbFindEmployeesByDepartmentKey(java.lang.Integer, AccessIntent, Object)",
         new Object[] {arg0, accessIntent, connection});
      //The following code uses the SCA Dynamic Invocation Interface (DII).
      //You can replace it with code that uses static (strongly-typed) SCA
      //stubs (similar to an RMI client) if desired.
      IndexedRecord returnValue = helper.createCCIIndexedRecord();
      try {
         commonj.sdo.DataObject sdo = helper.createSDO(
            "Example2Backend", "cmrFinder");
         // Begin customer-written code to populate SDO
         sdo.set(0, arg0);
         // End customer-written code to populate SDO
         Object result = helper.invokeSCAService(
            "Example2Backend", "cmrFinder", sdo);
         // Add records to returnValue record as described in this method's JavaDoc
         java.util.Vector employees = (java.util.Vector) result;
         for (int index = 0; index < employees.size(); index++) {
            com.ibm.cmpa.example2.EmployeeData empData =
               (com.ibm.cmpa.example2.EmployeeData) employees.get(index);
            IndexedRecord employee = helper.createCCIIndexedRecord();
            employee.set(0, empData.getId());
            employee.set(1, empData.getName());
            employee.set(2, empData.getSalary());
            employee.set(3, empData.getDeptId());
            returnValue.add(employee);
         }
      } catch (ResourceException re) {
         log.throwing("EmployeeBeanUserDefinedPushDownMethodsImpl",
            "ejbFindEmployeesByDepartmentKey(java.lang.Integer, AccessIntent, Object)", re);
         throw re;
      } catch (Exception e) {
         ResourceException re = helper.createResourceException(e, this.getClass());
         log.throwing("EmployeeBeanUserDefinedPushDownMethodsImpl",
            "ejbFindEmployeesByDepartmentKey(java.lang.Integer, AccessIntent, Object)", re);
         throw re;
      }
      //Note that if the return code from the back-end datastore interaction
      //indicated that a user-defined exception (declared in the push-down
      //method's signature) should be thrown, then throw it here.
      log.exiting("EmployeeBeanUserDefinedPushDownMethodsImpl",
         "ejbFindEmployeesByDepartmentKey(java.lang.Integer, AccessIntent, Object)", returnValue);
      return returnValue;
   }

Read-Ahead

正如前面所提到的,对于 ejbFindByPrimaryKey,输出 Indexed Record 通常将仅包含单个条目。当此 Bean 是一对多 CMR 的源,且在部署描述符中为此 Bean 指定了 read-ahead 时则例外。在此情况下,其中包含的条目数目与关系的目标端的实例的数目一样多。例如,如果给定的 Department 有三个 Employee,则外部 IndexedRecord 将包含三个 IndexedRecords;每个输入 IndexedRecords 首先包含的是该 Department 的 CMP 字段值,随后跟着对应的 Employee 的 CMP 字段值(顺序与 Employee ejbFindByPrimaryKey 所期望的顺序一样)。当然,如果 CMR 用于一对一关系,则将仅有一个输入 IndexedRecord。CMP/A 目前尚不支持多对多 CMR(为了模拟这种功能,将需要定义一个中间 Bean,用于存储 CMR 两端的键;对于多对多关系中的每个端,该 Bean 具有一个一对多 CMR)。

请注意,read-ahead 可以跨多个 CMR。例如,Corporation 可能具有多个 Division,而每个 Division 有多个 Department,每个 Department 有多名 Employee,以 Division 上的查找程序为例,该查找程序对后端数据存储的一次访问可以返回该 Division 上的数据,也可以返回其所有的 Department 上的数据及这些 Department 中的每个 Employee 上的数据。为查找程序生成的 JavaDoc 注释将说明,根据部署描述符中指定的 read-ahead 设置,哪个 Bean 字段映射到 IndexedRecord 中的哪个位置,如下面的示例所示。


清单 6. Read-Ahead
				/**
    * User-defined push-down method ejbFindByPrimaryKey.
    * For single object finders, if the bean does not exist in the backend
    * datastore, then you should throw a javax.ejb.ObjectNotFoundException.
    * For multi-object finders, if no results are found in the backend
    * datastore, then you should return an empty Record.
    *
    * @param arg0 Push-down method Parameter #0
    * @param accessIntent Reference to the access intent settings to be used for this method
    * @param connection The CCI connection to the back-end system.  This
    * connection was previously obtained (by the EJBToRAAdapter implementation)
    * via connectionFactory.getConnection(connectionSpec).  For non-relational,
    * non-CCI based beans, this will be null and the user will have to manually
    * connect to the back-end system and close the connection before returning.
    *
    * @return The return type of this finder method is a Record of Records,
    * each inner record of which contains the values of the bean's CMP fields,
    * with primitives wrapped (such as a java.lang.Integer for an int).
    * Note that with single-bean finders, like ejbFindByPrimaryKey, the outer
    * record will contain exactly one inner record (unless this bean has a
    * 1-to-many CMR and read-ahead has been enabled), whereas with multi-bean
    * finders the outer record will contain zero or more inner records.  The
    * order of the fields of the inner record must be as follows:
    *    Position #0: id
    *    Position #1: description
    *    Position #2: EmployeeBean.id
    *    Position #3: EmployeeBean.name
    *    Position #4: EmployeeBean.salary
    *    Position #5: EmployeeBean.department_id
    *
    * @exception javax.ejb.FinderException Thrown if an exception occurs retrieving bean data
    * @exception ResourceException Any other exceptions are wrapped in a
    * ResourceException.
    */
   public Record ejbFindByPrimaryKey(
      java.lang.Integer arg0,
      AccessIntent accessIntent,
      Object connection)
      throws javax.ejb.FinderException, ResourceException
   {
      log.entering("DepartmentBeanUserDefinedPushDownMethodsImpl",
         "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)",
         new Object[] {arg0, accessIntent, connection});
      //The following code uses the SCA Dynamic Invocation Interface (DII).
      //You can replace it with code that uses static (strongly-typed) SCA
      //stubs (similar to an RMI client) if desired.
      javax.ejb.ObjectNotFoundException notFound = null;
      IndexedRecord returnValue = helper.createCCIIndexedRecord();
      try {
         commonj.sdo.DataObject sdo = helper.createSDO(
            "Example2Backend", "retrieveDepartment");
         // Begin customer-written code to populate SDO
         sdo.set(0, arg0);
         // End customer-written code to populate SDO
         Object result = helper.invokeSCAService(
            "Example2Backend", "retrieveDepartment", sdo);
         // Add records to returnValue record as described in this method's JavaDoc
         java.util.Vector employees = (java.util.Vector) result;
         String description = (String) employees.get(0); //first slot is for Department
         for (int index = 1; index < employees.size(); index++) {
            com.ibm.cmpa.example2.EmployeeData empData =
               (com.ibm.cmpa.example2.EmployeeData) employees.get(index);
            IndexedRecord employee = helper.createCCIIndexedRecord();
            employee.set(0, arg0);
            employee.set(1, description);
            employee.set(2, empData.getId());
            employee.set(3, empData.getName());
            employee.set(4, empData.getSalary());
            employee.set(5, empData.getDeptId());
            returnValue.add(employee);
         }
      } catch (ResourceException re) {
         Throwable cause = re.getCause();
         if (cause instanceof javax.ejb.ObjectNotFoundException) {
            notFound = (javax.ejb.ObjectNotFoundException) cause;
         } else {
            log.throwing("DepartmentBeanUserDefinedPushDownMethodsImpl",
               "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)", re);
            throw re;
         }
      } catch (Exception e) {
         ResourceException re = helper.createResourceException(e, this.getClass());
         log.throwing("DepartmentBeanUserDefinedPushDownMethodsImpl",
            "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)", re);
         throw re;
      }
      //Note that if the return code from the back-end datastore interaction
      //indicated that a user-defined exception (declared in the push-down
      //method's signature) should be thrown, then throw it here.
      if (notFound != null) {
         log.throwing("DepartmentBeanUserDefinedPushDownMethodsImpl",
            "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)", notFound);
         throw notFound;
      }
      log.exiting("DepartmentBeanUserDefinedPushDownMethodsImpl",
         "ejbFindByPrimaryKey(java.lang.Integer, AccessIntent, Object)", returnValue);
      return returnValue;
   }

ejbSelect

在 ejbSelectDepartmentSalaries 方法中,部署人员必须添加一定的逻辑,以生成 UDPDMI 代码来检索给定部门的所有员工的薪资。

请注意,对于部署人员而言,处理 ejbSelect 的方式与处理普通查找程序方法相似,不同的是其输入 IndexedRecord 仅包含单个条目。


清单 7. ejbSelect
				/**
    * User-defined push-down method ejbSelectSalaries.
    * For single object finders, if the bean does not exist in the backend
    * datastore, then you should throw a javax.ejb.ObjectNotFoundException.
    * For multi-object finders, if no results are found in the backend
    * datastore, then you should return an empty Record.
    *
    * @param arg0 Push-down method Parameter #0
    * @param accessIntent Reference to the access intent settings to be used for this method
    * @param connection The CCI connection to the back-end system.  This
    * connection was previously obtained (by the EJBToRAAdapter implementation)
    * via connectionFactory.getConnection(connectionSpec).  For non-relational,
    * non-CCI based beans, this will be null and the user will have to manually
    * connect to the back-end system and close the connection before returning.
    *
    * @return The return type of this finder method is a Record of Records,
    * each inner record of which contains the values of the bean's CMP fields,
    * with primitives wrapped (such as a java.lang.Integer for an int).
    * Note that ejbSelect methods can be defined to return either individual
    * CMP fields or entire beans.  For the former type, the inner record
    * should only contain the single CMP field.  For the latter type, the
    * order of the fields of the inner record must be as follows:
    *    Position #0: id
    *    Position #1: name
    *    Position #2: salary
    *    Position #3: department_id
    *
    * @exception javax.ejb.FinderException Thrown if an exception occurs retrieving bean data
    * @exception ResourceException Any other exceptions are wrapped in a
    * ResourceException.
    */
   public Record ejbSelectSalaries(
      java.lang.Integer arg0,
      AccessIntent accessIntent,
      Object connection)
      throws javax.ejb.FinderException, ResourceException
   {
      log.entering("EmployeeBeanUserDefinedPushDownMethodsImpl",
         "ejbSelectSalaries(java.lang.Integer, AccessIntent, Object)",
         new Object[] {arg0, accessIntent, connection});
      //The following code uses the SCA Dynamic Invocation Interface (DII).
      //You can replace it with code that uses static (strongly-typed) SCA
      //stubs (similar to an RMI client) if desired.
      //EJB-QL: select e.salary from Employee e where e.department.id = ?1
      IndexedRecord returnValue = helper.createCCIIndexedRecord();
      try {
         commonj.sdo.DataObject sdo = helper.createSDO(
            "Example2Backend", "selectEmployeeSalaries");
         // Begin customer-written code to populate SDO
         sdo.set(0, arg0);
         // End customer-written code to populate SDO
         Object result = helper.invokeSCAService(
            "Example2Backend", "selectEmployeeSalaries", sdo);
         // Add records to returnValue record as described in this method's JavaDoc
         java.util.Vector salaries = (java.util.Vector) result;
         for (int index = 0; index < salaries.size(); index++) {
            Float salary = (Float) salaries.get(index);
            Record record = helper.createCCIRecord(salary);
            returnValue.add(record);
         }
      } catch (ResourceException re) {
         log.throwing("EmployeeBeanUserDefinedPushDownMethodsImpl",
            "ejbSelectSalaries(java.lang.Integer, AccessIntent, Object)", re);
         throw re;
      } catch (Exception e) {
         ResourceException re = helper.createResourceException(e, this.getClass());
         log.throwing("EmployeeBeanUserDefinedPushDownMethodsImpl",
            "ejbSelectSalaries(java.lang.Integer, AccessIntent, Object)", re);
         throw re;
      }
      //Note that if the return code from the back-end datastore interaction
      //indicated that a user-defined exception (declared in the push-down
      //method's signature) should be thrown, then throw it here.
      log.exiting("EmployeeBeanUserDefinedPushDownMethodsImpl",
         "ejbSelectSalaries(java.lang.Integer, AccessIntent, Object)", returnValue);
      return returnValue;
   }

Data-logic

在 awardRaises 方法中,部署人员需要添加一定的逻辑,以生成 UDPDMI 代码来使用指定的数量(正或负)修改某个部门所有员工的薪资。在这个简单的例子中,该部门的所有员工的工资都上涨(或在效益不好时下调)相同的数额。例如,如果某个部门有 20 名员工,拨款 10,000 美元给员工涨工资,每个员工的工资将增加 500 美元。


清单 8. Data-logic
				/**
    * User-defined push-down method awardRaises.
    * If the bean no longer exists in the backend datastore, then you should
    * throw a javax.ejb.NoSuchEntityException.
    *
    * @param arg0 Push-down method Parameter #0
    * @param bean Reference to the bean implementation class
    * @param accessIntent Reference to the access intent settings to be used for this method
    * @param connection The CCI connection to the back-end system.  This
    * connection was previously obtained (by the EJBToRAAdapter implementation)
    * via connectionFactory.getConnection(connectionSpec).  For non-relational,
    * non-CCI based beans, this will be null and the user will have to manually
    * connect to the back-end system and close the connection before returning.
    *
    * @exception ResourceException Any exceptions are wrapped in a
    * ResourceException.
    */
   public void awardRaises(
      float arg0,
      DepartmentBean bean,
      AccessIntent accessIntent,
      Object connection)
      throws ResourceException
   {
      log.entering("DepartmentBeanUserDefinedPushDownMethodsImpl",
         "awardRaises(float, DepartmentBean, AccessIntent, Object)",
         new Object[] {new Float(arg0), bean, accessIntent, connection});
      //The following code uses the SCA Dynamic Invocation Interface (DII).
      //You can replace it with code that uses static (strongly-typed) SCA
      //stubs (similar to an RMI client) if desired.
      javax.ejb.NoSuchEntityException noSuchEntity = null;
      try {
         commonj.sdo.DataObject sdo = helper.createSDO(
            "Example2Backend", "awardRaises");
         // Begin customer-written code to populate SDO
         sdo.set(0, bean.getId());
         sdo.set(1, new Float(arg0));
         // End customer-written code to populate SDO
         Object result = helper.invokeSCAService(
            "Example2Backend", "awardRaises", sdo);
      } catch (ResourceException re) {
         Throwable cause = re.getCause();
         if (cause instanceof javax.ejb.NoSuchEntityException) {
            noSuchEntity = (javax.ejb.NoSuchEntityException) cause;
         } else {
            log.throwing("DepartmentBeanUserDefinedPushDownMethodsImpl",
               "awardRaises(float, DepartmentBean, AccessIntent, Object)", re);
            throw re;
         }
      } catch (Exception e) {
         ResourceException re = helper.createResourceException(e, this.getClass());
         log.throwing("DepartmentBeanUserDefinedPushDownMethodsImpl",
            "awardRaises(float, DepartmentBean, AccessIntent, Object)", re);
         throw re;
      }
      //Note that if the return code from the back-end datastore interaction
      //indicated that a user-defined exception (declared in the push-down
      //method's signature) should be thrown, then throw it here.
      if (noSuchEntity != null) {
         log.throwing("DepartmentBeanUserDefinedPushDownMethodsImpl",
            "awardRaises(float, DepartmentBean, AccessIntent, Object)", noSuchEntity);
         throw noSuchEntity;
      }
      log.exiting("DepartmentBeanUserDefinedPushDownMethodsImpl",
         "awardRaises(float, DepartmentBean, AccessIntent, Object)");
   }

cmpaExample2.ear

其他示例

有关更多示例,请参阅 WebSphere Process Server 6.0 Samples Gallery 中的“CMP over Anything”示例。该示例包含五个 CMP/A Bean,演示了 CCI、EJB、JAX-RPC、SCA 和自定义后端类型。每个 Bean 都是同一个基本 Account Bean 的子类,包含四个基本 CRUD 方法,另有一个自定义查找程序、一个选择方法和多个数据逻辑方法。提供这些 Bean 及其 UDPDMI 类的全部源代码,可以将其作为基本 Web 客户机,可以通过此客户机来选择后端类型和要调用的操作,如下所示。


图 7. CMP over Anything
CMP over Anything




回页首


CMP/A 的其他注意事项

将 CMP/A Bean 安装到 WebSphere Process Server 运行时中时,必须将每个 Bean 都绑定到存在于 Java Naming and Directory Interface (JNDI) 名称空间的托管资源。需要考虑三种场景:1) Bean 可以为关系型的(JDBC 或 SQLj 后端类型),此时将绑定到 JDBC 数据源。2) Bean 可以使用 JCA 资源适配器(CCI 后端类型),此时将绑定到为该资源适配器定义的 Java2 Connector (J2C) 连接工厂。3) 对于所有其他类型的 CMP/A Bean(如 SOA 所支持的类型),必须安装一个特殊的资源适配器 cmpaAdapter.rar,该适配器位于 WebSphere Process Server 服务器安装目录的 installableApps 子目录中,必须在该适配器下创建一个 J2C 连接工厂,并为其分配一个 JNDI 名称。安装包含此类 CMP/A Bean 的 J2EE 应用程序时,必须指定通过此 JNDI 名称到 J2C 连接工厂的绑定。

应注意,当使用 CMP/A Bean 时,只有从 UDPDMI 调用的代码是事务性的,该 Bean 才是事务性的。只有在事务提交后,才能对 UDPDMI 调用 ejbStore。不过,其他更改(如创建和删除 Bean 或从数据逻辑方法内进行更新)将立即执行,如果事务回滚,这些更改并不会撤消。不过,如果此类更改是在事务资源管理器控制下完成的,则例外。





回页首


结束语

在本文中,我们了解了 IBM WebSphere Process Server 如何允许部署人员对 CMP Bean 的连接和持久逻辑进行控制,以便将其保存到非关系数据存储中。我们讨论了所支持的各种数据访问模式和基本使用场景,并给出了非常简单的示例和较为复杂的示例。我们讨论了一些最佳实践,如从 UDPDMI 内使用 SCA,支持重用通过面向服务的体系结构公开遗留系统的服务,以及让部署人员不必了解与非关系数据存储进行通信所涉及到的全部细节等等。

基本 WebSphere Application Server 提供了符合规范的 J2EE 实现,包括对 CMP 实体 EJB 的支持(适合用于处理标准关系数据库)。但是,对于那些具有更复杂的数据访问要求(如与遗留非关系数据访问系统集成)的客户可能会发现,通过使用 WebSphere Process Server 6.0 及更高版本中包含的 CMP/A 功能,可以转到 J2EE 和 SOA 编程模型,且仍然可以对现有的遗留投资加以利用。有关详细信息,可以与本文作者或 IBM 代表联系。






回页首


下载

描述名字大小下载方法
Example 1 and 2 .ear files0511_alcorn-cmpa-ex1and2.zip60 KB  FTP|HTTP
关于下载方法的信息


参考资料



关于作者

John Alcorn 获得了肯塔基大学工程学院的计算机科学学士学位。Jonh 自 1993 年加入 IBM,1996 年开始从事 Java 开发,拥有多项 Java 相关专利,现在是 IBM Software Group 的 Application Integration and Middleware (AIM) 部门的高级软件工程师。John 曾担任过多个项目的软件开发人员,包括 OS/2、OpenDoc、VisualAge Bean Extender、Component Broker、WebSphere Application Server Enterprise Edition、WebSphere Business Integration Server Foundation、WebSphere Process Server 和 WebSphere Business Monitor。您可以通过 jalcorn@us.ibm.com 与 Jonh 联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

将您的建议发给我们或者通过参加讨论与其他人分享您的想法.




回页首


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