[转载]IBM WebSphere 开发者技术期刊: 通过 WebSphere Business Integration Server F

IBM WebSphere 开发者技术期刊: 通过 WebSphere Business Integration Server Foundation V5.1 在 CMP bean 中使用关系型存储过程


在 IBM® WebSphere® Business Integration Server Foundation V5.1 中,容器管理持久性 (Container Managed Persistence,CMP) 实体能利用新的或现有的过程向后端存储器中叠加任何 CRUD(创建、读取、修改、删除)方法或业务方法。过程可以是 SQL 存储过程或非关系型的遗留过程。本文描述了在关系数据库中利用存储过程开发 CMP 实体 bean 方面的考虑和部署过程。

引言

存储过程是一种很受欢迎的功能,它广泛用于提高数据访问性能。存储过程包含存储在数据库中的一系列代码,扩展了运行在数据库服务器上的客户端。客户端应用程序通过调用存储过程只需要访问数据库一次就可以执行一系列本地操作,而不用多次访问数据库服务器。


图 1. SessionBean 调用存储过程
图 1. SessionBean 调用存储过程

为 了演示调用过程,在图 1 中,AccountBean 是一个包含 updateAllBalances() 方法的会话 bean。该方法调用一个 SQL 存储过程——updateTransactions,这是一个在数据库中存储并代表 updateAllBalances 在数据库服务器上运行的存储过程。为在该实例中应用业务逻辑,updateTransactions 存储过程包含了执行数据库服务器上的下列银行操作的代码:

  1. 选择 Checking 表中 Expense 列的值,只选取从 1990 年到现在的那些费用。如果总费用超过 100000,那么从 Saving 表向 Checking 表转移 20000。
  2. 从 Loan 表中选择余额。如果余额超过 20000,那么从 Saving 表向 Loan 表中转移 20000。
  3. 返回 Checking、Saving 和 Loan 表中的所有余额。

在应用服务器上执行该代码,并分别访问数据,与用通常的 SQL 语句执行必需的操作相比,updateTransactions 存储过程至少节省了对数据库的三次访问。

存储过程可以是 Java 过程、SQL 存储过程或系统本地过程。在本文中,我们将看一下访问关系数据库的存储过程。在这里不讨论用于容器管理持久性 (Container-Managed Persistence,CMP) 的非关系型过程。

容器管理的方法

CMP Entity Enterprise Java Bean (EJB) 组件利用存储过程功能向后端存储库中叠加 CMP EJB 方法扩展了现有的 J2EE CMP EJB 编程模型。这些叠加方法,也可以看作容器管理的方法,包括 EJB CRUD(创建、读取、修改、删除)方法,例如 ejbCreate、ejbRemove、ejbLoad、ejbStore 以及执行 CMP bean 的持久存储数据相关逻辑的数据逻辑方法。

以 前,调用存储过程最好的方法是使用会话 bean,会话 bean 是一种供 Web 应用程序或客户端应用程序通过存储过程访问数据库的会话虚包(facade)。现在可以将 CMP EJB 组件作为存储过程的前端来创建和使用,这使得用户能同时使用 CMP EJB 组件和存储过程,如果用户通过会话 bean 使用存储过程,那么可以直接通过会话 bean 访问实体,但同时会失去一些 CMP 相关的好处,例如 CMP 缓存、CMP 事务控制、EJB 本地或远程访问透明性以及容器管理的方法。主要的是如果存储过程操作不涉及 CMP EJB 组件的持久字段,那么可以通过会话 bean 使用存储过程。否则,就通过 CMP bean 使用存储过程以利用 CMP 的优势。

WebSphere 支持容器管理的方法

在 WebSphere Business Integration Server Foundation V5.1 发布之前,用户可以在会话 bean 或数据逻辑方法中使用存储过程。后来,需要在 CMP 方法中实现叠加过程。在 WebSphere Business Integration Server Foundation V5.1 中,容器管理的方法通过将叠加逻辑移到生成的模板中,从而简化了 CMP bean 中的逻辑,这和今天的将逻辑移到生成的 FunctionSet 类中是类似的。但是仍需要用户的干预,将叠加逻辑插入到生成的模板中。

在 WebSphere Studio Application Developer Integration Edition 5.1 版本(以下简称为 Application Developer)中,用户可以在 CMP bean 上定义容器管理方法的信息。CMP bean 包括映射到存储过程方法的 CMP 方法(CRUD 和数据逻辑)和 preflush 指示器,该指示器告诉容器在调用存储过程前要将 CMP 数据写入数据库中。图 2 从概念上说明了利用 CMP bean 中的容器管理方法的开发和部署步骤。


图 2. 容器管理的方法部署模型
图 2. 容器管理的方法部署模型

必须将 CMP bean 中的容器管理方法创建为抽象方法。所有的 CRUD 方法都是抽象方法,这使得它们成为容器管理方法的候选方法。下面的步骤概括了如何在 CMP bean 中开发和部署容器管理方法:

  1. 从 Application Developer 中:
    1. 选择您想使用的 CMP bean,并指出哪个 CMP 方法使用存储过程。提供必需的存储过程信息,例如过程名称、preflush 指示器等等。
    2. 选择 Generate 按钮,利用 cmpDeploy 工具生成 UserDefinedPushDownMethodsImpl 模板类。
    3. 将逻辑插入到 UserDefinedPushDownMethodsImpl 模板类的方法中,从而访问存储过程并处理返回的数据。
    4. 保存 UserDefinedPushDownMethodsImpl 类。cmpDeploy 工具将自动编译这个文件并将其重新封装到 EJB JAR 文件中。
  2. 运行 ejbDeploy 命令或用 Application Developer 的部署工具(选择 Generate => Deployment and RMIC code)部署 CMP JAR 文件。
  3. ejbDeploy 工具生成 FunctionSet。
  4. FunctionSet 类中生成的容器管理方法将路由到用户修改的 UserDefinedPushDownMethodsImpl 类中的相应方法。
  5. 安装 EJB JAR 文件。

我们将在图 3 中用应用程序模型演示可叠加的 CMP bean 的开发和部署流程,其步骤将在下面的部分中描述。

应用程序实例模型


图 3. BankAccount 应用程序模型
图 3. BankAccount 应用程序模型

这里显示的企业应用程序—— BankAccountCMPSP.ear 有三个 EJB 组件:

  • AccountTransaction SessionBean
  • BankAccount CMP
  • TransactionHistory CMP。

关于这些组件:

  • AccountTransaction 充当了一个供 Web 应用程序或客户端访问实体 EJB 组件的会话虚包,因此它支持远程接口。
  • BankAccount 和 TransactionHistory CMP bean 只支持本地接口。
  • 一个 BankAccount CMP bean 有零个或多个 TransactionHistory CMP bean。
  • BankAccount 访问 DB2 数据库中的 BankAccount 表,TransactionHistory 访问 DB2 数据库中的 TransactionHistory 表。图 3 中用红色标识的方法为容器管理方法。

为了简化该实例,我们将只为 BankAccount 的几个属性和 TransactionHistory 实体建模。图 4 显示了 BankAccount 表和 TransactionHistory 表的结构。


图 4. BankAccount 表和 TransactionHistory 表的结构
图 4. BankAccount 表和 TransactionHistory 表的结构

BankAccount CMP bean
BankAccount 有下列字段:

  • accountId:帐号,关键字段。
  • balance:帐户余额。

下列 BankAccount 方法使用了存储过程:

  • ejbCreate(String id, float balance):用 accountId 创建帐户并设置帐户余额。不仅只在 BankAccount 表中创建元组(tuple),还在表示已经完成了“创建”事务的 TransactionHistory 表中创建元组。
  • ejbStore():存储帐户信息。用余额修改 BankAccount 表中的记录。
  • ejbRemove():移除帐户。它可以移除 Account 表中的元组,还可以移除 TransactionHistory 表中对应的事务历史元组。
  • ejbFindByPrimayKey(String id):根据帐户 Id 查找 BankAccount EJB 组件。
  • ejbFindByBalance(float fromAmount, float toAmount):在一定的范围内用余额查找帐户。它将返回一个 BankAccount EJB 组件集。
  • getNumberOfTransaction(String transactionType, Timestamp fromTime, Timestamp toTime):返回在一定时间段中执行的特定事务类型的事务数量。
  • getTransactionHistories():获取 TransactionHistory EJB 组件集。它是一个容器管理关系 (Container Managed Relationship,CMR) getter 方法。

除了这些方法,BankAccount CMP bean 还有其他的 CRUD 方法和下面列出的这些方法,下面这些方法不使用存储过程:

  • withdraw(float amount):从帐户中提取指定量的金额。
  • deposit (float amount):向帐户中存入指定量的金额。

TransactionHistory CMP bean
TransactionHistory 是一个存储银行事务历史的 CMP bean。BankAccount 和 TransactionHistory 之间是一对多的关系,该关系由容器管理关系处理。TransactionHistory CMP bean 有下列字段。

  • transactionId:事务的 ID,bean 的主键。
  • accountId:帐号。
  • transactionType: deposit 或 withdraw 或 create 的值。
  • transactionAmount:事务中涉及到的流通量。
  • transactionDateTime:事务的时间戳记。

主关键字段, transactionId,是插入记录时 DB2 自动生成的。

TransactionHistory 使用了三个存储过程:

  • ejbCreate(String transactionId, String accountId, String transactionType, float transactionAmount, java.sql.Timestamp time):创建 TransactionHistory EJB 实例。
  • findTransactionHistoriesByBankAccountKey_Local ():这是一个与 BankAccount CMP EJB——getTransactionHistories——的 CMR getter 相对应的方法。虽然 CMR getter 是在 BankAccount bean 中定义的,但是获取 TransactionHistory 实例的逻辑在 TransactionHistory bean 中。这个方法是在 FunctionSet 中生成的。
  • ejbFindTransactionHistoryByDateTime(String accountId, Timestamp time):这是一个定义在主接口中的多重查找程序方法,用于获取发生在特定时间戳记后的事务历史。

存储过程
在本实例中,用 DB2 V8.1 作为我们的数据库,创建 Java 存储过程,并将他们部署在 DB2 中。表 1 列出了 CMP EJB 方法、对应的存储过程以及准备 CallableStatement 时要用到的 SQL 语句。所有的存储过程都封装在 BankAccount.ear 文件中的 JavaSP.jar 中。

表 1. EJB 方法与存储过程间的映射

CMP bean EJB 方法 存储过程 SQL 语句
BankAccount ejbCreate(String id, float balance)createAccount(String id, double balance) {call createAccount(?, ?)}
ejbStore()updateAccount(String id, double balance, java.sql.Timestamp time) {call updateAccount(?, ?, ?)}
ejbRemove()deleteAccount(String id, int[ ] numberOfDeletes). {call deleteAccount(?, ?)}
ejbFindByPrimayKey(String id)getAccountBalance(String id, double[ ] balance) {call getAccountBalance(?,?)}
ejbFindByBalance(float fromBalance, float toBalance)findByBalance(double fromBalace, double toBalance, ResultSet[ ] rs) {call findByBalance(?, ?)}
getNumberOfTransactions(String transactionType, Timestamp fromTime, Timestamp toTime)getNumberOfTransactions(String id, String transactionType, Timestamp fromTime, Timestamp toTime, int num[ ]) {call getNumberOfTransactions (?, ?, ?, ?, ?)}
getTransactionHistories (see Note 1)getHistoryByType(String id, ResultSet[ ] rs1, ResultSet[ ] rs2, ResultSet[ ] rs3) {call getHistoryByType(?)}
TransactionHistory ejbCreate(String transactionId, String accountId, float amount, String transactionType, Timestamp transactionDateTime, BankAccountLocal account)createTransactionHistory(String transactionId, String accountId, String transactionType, double transactionAmount, Timestamp time) {call createAccountHistory(?,?,?,?,?)}
ejbFindTransactionHistoryByDateTime(String accountId, Timestamp time). getAccountHistoryByDate(String id, Timestamp time, ResultSet[ ] rs) {call getAccountHistoryByDate(?,?)}

注意 1:CMR getter getTransactionHistories() 将调用 TransactionHistory CMP bean 中的 ejbFindTransactionHistoriesByBankAccountKey_Local 查找程序方法,因此实际上是 TransactionHistory CMP 使用存储过程。

本文剩下的部分用这个 BankAccount 应用程序来演示可叠加的 CMP bean 的开发和部署步骤。


blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


利用关系型存储过程开发 CMP bean

正 如前面所提到的,由于 CRUD 方法是抽象方法,因此所有的 CRUD 方法都是容器管理方法的候选方法。如果用户想利用存储过程在 CMP bean 中开发数据逻辑方法,必须将该方法定义为抽象方法。清单 1 显示了 getNumberOfTransactions() 方法,该方法是 BankAccount CMP bean 中的一个容器管理方法。

清单 1. 声明 getNumberOfTransactions 方法为抽象叠加方法

/**
*

This method gets the number of transactions of a particular
* transaction type in a certain period of time, for example,
* how many times the account has been withdrawn in the past month.


*
*

This method uses stored procedure
* getNumberOfTransactions(String id, String transactionType, Timestamp fromTime,
* Timestamp toTime).
* This stored procedure gets the number of transactions of a particular
* transaction type within a certain time period.


*
* @param transactionType The transaction type
* @param startTime The start time for the search.
* @param startTime The end time for the search.
*
* @return int The number of the transactions
*/
public abstract int getNumberOfTransactions(String transactionType,
java.sql.Timestamp startTime,
java.sql.Timestamp endTime)
throws InvalidTransactionTypeException;

下面的步骤显示了如何利用容器管理方法功能在 Application Developer 中开发应用程序。 BankAccountSample.zip 文件中包含了应用程序实例的源代码。

  1. 利用 File => Import... 导入 EAR 文件 BankAccount.ear。
  2. 指定 J2EE 项目的目标服务器。设置目标服务器为 WebSphere Business Integration server 5.1
  3. 选择 Window => Preferences,然后单击左侧面板中的 J2EE 。确保选中了 Enable server targeting support 单选按钮。选择 Apply,然后单击 OK
  4. 在 J2EE 视图中打开一个 J2EE Hierarchy 视图。在左侧面板中,展开 Enterprise applications,然后右键单击 BankAccount
  5. 选择 Target Server => Modify...,然后从列表中选择 Integration Server 5.1 作为目标服务器。选中 Update AR modules and utility projects to the selected target server,然后单击 OK。 现在已经用目标服务器 Integration Server 5.1 设置了 BankAccount 项目和它的模块,因此该项目能使用 WebSphere Business Integration Server Foundation V5.1 附带的特定功能。
  6. 展开 EJB Modules,双击 BankAccountEJB 打开 EJB Deployment Descriptor Edition。选择右侧面板上部的 Beans 选项卡。您将看见这里已经预先定义了三个 EJB: BeanAccount、 TransactionHistory 和 AccountTransaction。
现在向 BankAccount EJB 中添加数据逻辑方法 getNumberOfTransactions。
  1. 在 bean 列表中选择 BankAccount,然后向下滚动面板。从类和接口文件列表中双击 com.ibm.websphere.sample.BankAccountBean,打开 BankAccountBean.java 文件。
  2. 检查一下该类,您将发现该类中的所有方法都遵从 EJB 2.0 规范。可以复制清单 1 中的 getNumberOfTransactions 方法,然后将其粘贴到该类的底部。可以看见,这个方法被定义为抽象的。
  3. 保存该文件。
  4. 从 outline 视图中选择 getNumbeOfTransaction 方法,并右键单击该方法。选择 Enterprise Bean => Promote to local interface 将该方法添加到 BankAccountLocal 接口中。
现在已经向 BankAccount EJB 中添加了一个数据逻辑方法,下一步是给那些将用存储过程作为叠加方法的方法做上标记,这样部署工具将在 BankAccountUserDefinedPushDownMethodImpl 类中生成方法模板:
  1. 从 EJB Deployment Descriptor 面板中,选择 EJB 模块下的实体 bean,然后选择右侧面板底部的 Pushdown。图 5 显示了叠加实体面板。
    图 5. 叠加实体面板
    图 5. 叠加实体面板
  2. 选择 Add 添加实体。图 6 中的对话框显示了如何添加实体。
    图 6. 添加新叠加实体
    图 6. 添加新叠加实体
  3. 选择后端类型。对于关系数据库来说可能的值为 JDBCSQLJ
  4. 不要选择 Requires custom adapter 类名称,除非您想插入您自己的符合 J2EE 连接器体系结构 (J2EE Connector Architecture,JCA) 的资源适配器。默认的 com.ibm.ws.rsadapter.cci.WSRelationalRAAdapter 类是 WebSphere 提供的关系 JCA 资源适配器实现。
  5. 选择 Next 继续。
  6. 图 7 显示了可能的 CMP 方法。选择所有的容器管理方法: ejbCreateejbFindByBalanceejbFindByPrimaryKeyejbRemoveejbStoregetNumberOfTransactions,然后选择 Finish
    图 7. 叠加方法面板
    图 7. 叠加方法面板
  7. 保存该 CMP bean 后,叠加实体面板将类似于图 8。
    图 8. 定义了叠加方法的叠加面板
    图 8. 定义了叠加方法的叠加面板
    重 复步骤 2 到步骤 5,给 TransactionHistory CMP bean 中的那些将存储过程作为叠加方法的方法做上标记。例如,ejbCreate、 ejbFindTransactionHistoriesByBankAccountKey 和 ejbFindTransactionHishtoryByDateTime。现在,已经成功开发了一个使用了容器管理方法功能的 EJB 应用程序。

应用程序考虑

如果使用从上到下的方法开发 CMP 应用程序,那么接下来您需要依靠部署者来创建新的存储过程。如果在开发 CMP 应用程序之前已经创建了存储过程,那么理解存储过程和这个 CMP bean 之间的关系非常重要。

开发 CMP bean 来使用存储过程时,必须要考虑几个关键地方:

下面是一些帮助开发这样的应用程序的指导方针。本文作者在 WebSphere Application Server 信息中心简要描述了这些指导方针,然而这里提供了更详细的信息和说明,以补充那个材料。

CMP 事务和存储过程之间是什么关系?
存储过程可以运行在它自己的事务中,也可以作为 CMP bean 事务的一部分来运行。如果存储过程是 CMP bean 事务的一部分,那么它不应该中止事务。否则,容器提交 CMP bean 事务时将抛出异常,或发生数据完整性问题。

例如,BankAccount CMP bean 中的 ejbStore 方法使用存储过程 updateAccount。当使用非 XA(或单阶段)数据源时,如果 updateAccount 过程提交事务,但另一方面 ejbStore 方法又想回滚事务,这样就会因为已经在存储过程中提交事务而导致数据完整性问题。如果 updateAccount 过程试图在 XA 事务中提交连接,将会抛出异常,这是因为在 XA 事务中不允许连接提交/回滚。但是应用服务器不支持在 XA 事务中使用非 XA 连接,因此如果存储过程用这种配置提交/回滚连接,将不会发生整个事务异常。用户需要知道这种行为。

存储过程中的事务永远不会中止。相反,可以让 CMP bean 方法通过容器控制事务。

CMP 字段和存储过程之间是什么关系?
在 CMP EJB 开发中不利用存储过程而用它的数据逻辑方法更新数据库表中的持久存储数据,也不结束数据逻辑方法中 bean 的事务,这通常是一个最佳方法。用户使用存储过程更新后端数据时,应用程序应该在接触任何 CMP 持久字段前结束事务。在 lifetimeInCacheUsage bean 实例中,使用 PMCacheInvalidationRequest API 再次同步发送您的应用程序中 bean 的持久数据。

清单 2. 使用 PMCacheInvalidationRequest API 再次同步发送数据

 PMCacheInvalidationRequest req = new PMCacheInvalidationRequest("DepartmentHome", accoutID);   
try {
sendMessage(req);
} catch (Exception t) {
// handle exception here
t.printStackTrace();
throw t;
}

如果存储过程需要涉及最新的 CMP 持久存储数据,包括任何尚未压入到后端数据存储器中的更新,当确定用叠加方法从 Application Developer 访问存储过程,可以将 preflush 字段设置为 TRUE。(有关如何设置 preflush 字段的详细信息,请参阅 使用关系型存储过程部署 CMP bean。)

不要关闭 UserDefinedPushDownMethodsImpl 类中的 WebSphere Business Integration Server Foundation 给定的连接。如果您的UserDefinedPushDownMethodsImpl 方法返回一个 ResultSet 记录,而该类中的连接是关闭着的,处理 ResultSet 时,WebSphere Business Integration Server Foundation 将抛出一个 “ResultSet has already closed” 异常。

在 UserDefinedPushDownMethodsImpl 类中每个方法的输入/输出参数都是以部署描述中定义的 CMP 持久存储字段为基础的。如果实现 UserDefinedPushDownMethodsImpl 类后,bean 中字段的顺序或类型发生了改变,用户必须更新类来反射更改并与更改同步。否则,将不能正确访问您的 CMP EJB。如果没有修改已经叠加的方法签名,那么接下来可以编译 UserDefinedPushDownMethodsImpl 的最新版本并将其放回部署的 EAR 文件中。如果签名发生了变化,用户必须重新运行 cmpDeploy 和 ejbDeploy 工具,重写 UserDefinedPushDownMethodsImpl 类(预先复制一下该类再重写)。

如何处理存储过程返回的数据?
各个存储过程的返回值互不相同,因此在 UserDefinedPushDownMethodsImpl 类中正确处理返回值以确保 WebSphere Business Integration Server Foundation 能识别 UserDefinedPushDownMethodsImpl 返回的记录,这是非常重要的。

两个 helper 类, WSPushDownHelperFactory 和 WSRelationalPushDownHelper 是由 WebSphere 提供的,用于帮助返回运行时所识别的记录。清单 3 显示了创建 WSRelationalPushDownHelper 的代码实例。用户需要将 CMP bean 名称传递给 WSPushDownHelperFactory 来创建 helper。

清单 3. 用 WSPushDownHelperFactory API 创建叠加 helper

WSRelationalPushDownHelper helper = 
WSPushDownHelperFactory.createRelationalPushDownHelper
("com.ibm.myapp.BankAccountBean");

通常,WebSphere Business Integration Server Foundation 在叠加方法的开始部分为用户的引用生成输入/输出参数信息,如清单 4 所示。

清单 4. 叠加方法的开始部分中生成的输入/输出参数信息

/**
* User-defined push-down method getNumberOfTransactions.
* 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 arg1 Push-down method Parameter #1
* @param arg2 Push-down method Parameter #2
* @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.
*
* @return The return type of this push-down method is int
* @exception com.ibm.websphere.sample.exception.InvalidTransactionTypeException
* Thrown if an exception occurs in the data logic method
* @exception ResourceException Any other exceptions are wrapped in a
* ResourceException.
*/

以开始部分的信息为基础,用户可以相应地管理输入/输出参数。下面是一组代码实例,显示了如何处理 UserDefinedPushDownMethodsImpl 类中的各种形式的返回值。

在 EJB 查找程序中,返回值是一个 ResultSet,它的顺序以及格式和UserDefinedPushDownMethodsImpl 方法中所描述的是相同的。在这种情况下,让 WebSphere 为您处理返回的 ResultSet。这里有一个实例:

清单 5. 返回值是生成相同描述的 ResultSet

callableStatement.execute();
ResultSet rs = callableStatement.getResultSet();
returnValue = helper.createCCIResultSet(rs, connection);

createCCIResultSet() 方法是一个用来封装存储过程所返回的 ResultSet 的封装器。WebSphere Business Integration Server Foundation 运行时总是从本地 ResultSet 中检索值,但是它需要一个处理 ResultSet 流程封装器。因此,需要使用 createCCIResultSet() 方法从存储过程中返回 ResultSet。

在 EJB 查找程序中,返回值不是 ResultSet,但是通过 CallableStatement 输出参数,它具有所有的 CMP EJB 持久存储字段。用户需要处理返回值并将其转化为一个 IndexedRecord。无论是单查找程序还是多重查找程序,返回的 IndexedRecord 必须是 记录的记录。创建一个 IndexedRecord 将您的返回值放在这个基于文档格式的记录中。例如:

清单 6. 返回值不是 ResultSet

callableStatement.execute();
// create a record to store all the return values
IndexedRecord rec = helper.createIndexedRecord();
// assume that there are three output parameters: one integer and two
// String fields.
rec.add(new Integer(callableStatement.getInt(1)));
rec.add(callableStatement.getString(2));
rec.add(callableStatement.getString(3));
// return a wrapper of this record.
return helper.createCCIRecord(rec);

在 EJB 查找程序中,返回值是 ResultSet,但是与当前的 CMP EJB 组件相比,字段的数量不同。用户需要处理 上面所描述的返回值。创建一个 IndexedRecord 将返回值放在这个记录中。例如,ResultSet 返回 6 个字段,只有 1、4、6 供这个 CMP EJB bean 使用。

清单 7. 返回值是 ResultSet,但是比生成的描述具有更多的字段

callableStatement.execute();
// create a record to store all the return values
IndexedRecord rec = helper.createIndexedRecord();
// assume that there are three output parameters: one integer and two
// String fields.
rec.add(new Integer(callableStatement.getInt(1)));
rec.add(callableStatement.getString(4));
rec.add(callableStatement.getString(6));
// return a wrapper of this record.
return helper.createCCIRecord(rec);

在 EJB 查找程序中,返回的 ResultSet 有多行,并且格式与 UserDefinedPushDownMethodsImpl 类中所描述的相同。 参阅 上面的实例。 在 EJB 查找程序中,返回的 ResultSet 有多行,但是格式与 UserDefinedPushDownMethodsImpl 类中所描述的不同。在 这种情况下,构建一个复合 IndexedRecord。这种处理和前面所描述的类似,但是 returnValue 充当了顶层 IndexedRecord。对于 ResultSet 中的每一行,用户都必须创建一个 IndexedRecord 来存储数据,然后将这个记录添加到 returnValue IndexedRecord 中。例如:

清单 8. 返回值是一个多行的 ResultSet

callableStatement.execute();
ResultSet rs = callableStatement.getResultSet();
// create a top level record to save each inner record
IndexedRecord returnValue = helper.createIndexedRecord();
IndexedRecord item = null; // this is the inner record
while (rs.next()) {
// create an IndexedRecord to store the values
item = helper.createCCIIndexedRecord();
item.add(new Integer(rs.getInt(1)));
item.add(rs.getString(4));
item.add(rs.getString(6));
// add this record to the top level record
returnValue.add(item);
}
// return a wrapper of this composite record
return helper.createCCIRecord(returnValue);

在数据逻辑方法中,返回值是一个对象或与方法签名相匹配的值。将该值返回给调用方。

AccessIntent 和存储过程之间是什么关系?
与 CMP bean 相关联的 AccessIntent 将被传送给 UserDefinedPushDownMethodsImpl 类。以 AccessIntent 参数为基础,存储过程能决定如何处理 SELECT 语句,从而避免死锁或提高性能。例如,有些数据库在 SELECT 语句上提供了特殊的语法,声明用来更新 SELECT 语句所选中的行。这使得数据库系统管理员可以优化没有使用更新语句处的性能,或使用更新语句时也可以防止死锁。用户可以使用 AccessIntent 来解决锁定类型并将该死锁类型传送给存储过程,然后以锁定类型为基础,确定适当的 SELECT 语句语法。WSRelationalPushDownHelper.getLockType() 方法能返回 5 种可能的锁定类型。常量定义在 com.ibm.websphere.ejb.persistence.EJBToRAAdapter 接口中:

LOCKTYPE_NOTAPPLICABLE:值为 0,这是一种未知的锁定类型。 LOCKTYPE_SELECT:值为 1,使用通常的 SELECT 语句执行查询。 LOCKTYPE_SELECT_FOR_UPDATE:值为 2,使用 select..forUpdate 语句执行查询。 LOCKTYPE_SELECT_FOR_UPDATE_WITH_RS:值为 3,使用 select..forUpdate with RS 语句执行查询。 LOCKTYPE_SELECT_FOR_UPDATE_WITH_RR:值为 4,使用 select..forUpdate with RR 语句执行查询。 清单 9 显示了 AccessIntent 的一种用法。

清单 9. 将 AccessIntent 信息传送给存储过程

public int getNumberOfTransactions(
String arg0,
java.sql.Timestamp arg1,
java.sql.Timestamp arg2,
BankAccountBean bean,
AccessIntent accessIntent,
Object connection) throw
com.ibm.websphere.sample.exceptin.InvalidTansactinTypeException,
ResourceException {
.........................
// Assume that the 4th parameter is used to pass in the
// lockType to the stored procedure
callableStatement = helper.prepareCall(connection,
"{call getAccountByBalance(?, ?,?,?)}");
// use the accessIntent parameter that is passed into this method
// to get the lock type.
int lockType = helper.getLockType(accessIntent);
callableStatement.setInt(4, lockType);
// In the stored procedure getAccountBalance, it uses the
// lockType to determine which SELECT statement to use.

如何处理存储过程返回的异常?
WebSphere 要求 UserDefinedPushDownMethodsImpl 类中的每个 CRUD 叠加方法都抛出 javax.resource.ResourceException。用户必须使用 WSRelationalPushDownHelper 中的 createResourceException 方法来套住存储存储过程异常或返回 UserDefinedPushDownMethodsImpl 类中的代码。下面是代码实例:

清单 10. 使用 ResourceException 返回异常

try {
// invoke stord proc code here...
} catch (Exception e) {
ResourceException re = helper.createResourceException(e,
this.getClass());
throw re;
}

如果 UserDefinedPushDownMethodsImpl 类中的数据逻辑方法抛出异常,用户可以在方法内部定义并创建一个与异常具有相同类型的实例。客户端代码调用数据逻辑方法可以捕获用户定义的异常。

清单 11. 返回数据逻辑定义的异常

// assume the error code is from the CallableStatement. If error code 
// is not 0, then return a user defined InvalidTypeException.
ResultSet rs = CallableStatement.getResultSet();
if (rs.next()) {
returnValue = rs.getInt(1);
if (returnValue != 0)
throw new mycomp.account.InvalidTypeException(returnValue);
else
{ // process the return value here... }
}

存储过程逻辑与部署者间的通信

创建过使用存储过程的 CMP bean 的开发人员知道如何调用这个存储过程,以及如何处理输入和输出参数。为简化 CMP bean 的开发,WebSphere 把这一逻辑从 CMP bean 自身转移到了 UserDefinedPushDownMethodsImpl 类,该类是在 CMP 的部署过程中生成的。因此,在部署阶段由开发人员负责存储过程调用流程与部署者之间的通信。在这里,最好的方法是使用一个单独的文件提供此类信息,并将文件名放到 EJB 部署描述符中。

清单 12. 在部署描述符中输入存储过程调用信息

<?xml version="1.0" encoding="UTF-8"?>
br />"http://java.sun.com/dtd/ejb-jar_2_0.dtd">


This BankAccountEJB EJB file is used for handling BankAccount
transactions using stored procedure support. The stored procedure logic is described
in the com.ibm.websphere.sample.BankAccountSPFile which stores in this JAR file.

BankAccountEJB


BankAccount

com.ibm.websphere.sample.BankAccountSPFile 文件包含处理输入/输出参数和调用存储过程的逻辑。当将 bean 部署到 WebSphere Business Integration Server Foundation 中时,部署者将逻辑注入到 UserDefinedPushDownMethodsImpl 类中。实例如下,清单 13 显示了这个 BankAccountSPFile。

清单 13. mycomp.BankAccountSPFile 实例

This file documents the stored procedure invocation in the BankAccountUserDefinedPushDownMethodsImpl 

1) getNumberOfTranasction
CallableStatement: call getNumberOfTransactions(?, ?, ?,?)
Input parameters and position into CallableStatement:

Parameter position type name
1 String accountId
2 String transactionType
3 Timestamp fromTime
4 Timestamp toTime
5 int (out) numOfTransactions
Output parameters handling:
int returnValue = callableStatement.getInt(5);
Exception:
InvalidTransacationTypeException -- this exception is thrown when the transaction type is invalid
Exception -- no resultSet returned from getNumberOfTransactions

2) ejbRemove
CallableStatement: call deleteAccount(?)
Input parameter and position into CallableStatement:

Parameter position type name
1 String accountId
Output parameters handling: none
Exception:
ResourceException -- this exception is thrown if stored procedure can't delete the row.
You must wrap the SQLException in this ResourceException.
3)... more methods...


blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


使用关系型存储过程部署 CMP bean

一旦将 CMP bean 和应用程序开发到了 .ear 文件中,就可以开始准备在 WebSphere 中部署这个应用程序了。部署这种类型的 CMP bean 不同于部署典型的 CMP bean,部署它用户需要以从 bean 开发人员那里传递过来的消息为基础,将逻辑注入到 UserDefinedPushDownMethodsImpl 类中。 com.ibm.websphere.sample.BankAccountSPFile 就是这样的一个实例,如清单 13 所描述的:

部署可叠加的 CMP bean 有两个选项:使用 Application Developer 或使用 WebSphere cmpDeploy 命令工具。Application Developer 提供更好的服务,尽管 cmpDeploy 命令工具和 ejbDeploy 命令工具的模式类似,但是它需要用户的干预,所以不能完全地用于批处理模式中。本文只描述了在 Application Developer 环境中的部署步骤。有关 cmpDeploy 命令指令的详细信息,请参阅 WebSphere Application Server 信息中心。

在 Application Developer 中部署可叠加的 CMP bean

  1. 在 Application Developer 中,打开 EJB Deployment Descriptor 并选择 Pushdown 面板。面板看上去类似于图 8 。
  2. 选择 BankAccount 作为叠加实体,并从叠加方法列表中选择 ejbCreate
  3. 选择 Edit... 可以看见如图 9 所示的对话框。
    图 9. 编辑叠加方法
    图 9. 编辑叠加方法
  4. 对于 JDBC SQL 语句字段,输入 {call createAccount(?,?)},如 mycomp.BankAccountSPFile 中所列出的。为便于说明,请参阅表 1 第三列中的存储过程语句。Preflush 字段只用于数据逻辑方法。编辑 getNumberOfTransactions 数据逻辑方法时可以选择它。
  5. 重复步骤 2 到步骤 4 以修改 BankAccount 和 TransactionHistory 中所有其他的叠加方法。表 1 的第三列中列出了SQL 语句。
  6. 编辑完所有的叠加方法后, Save EJB Deployment Descriptor Editor。
  7. 选择 Application Developer 左侧面板中的 Project Navigator 选项卡切换到 Project Navigator 视图。选择 BankAccountEJB => ejbModule => META-INF,然后双击 ibm-ejb-jar-ext-pme51.xml
  8. 选择右侧面板中的 Source 选项卡。您可以看到叠加方法部署信息被保存在这个文件中。清单 14 显示了这个文件中 getNumberOfTransactions 方法的部署信息。

    清单 14. ibm-ejb-jar-ext-pme51.xml 的部分内容

    	xmi:id="PushDownMethodElement_1087326655887" 
    userDefined="true"
    BackEndMethodName="{call getNumberOfTransactions(?, ?, ?, ?)}">
    xmi:id="MethodElement_1087326655887"
    name="getNumberOfTransactions"
    parms="java.lang.String java.sql.Timestamp java.sql.Timestamp"
    type="Local">
    xmi:type="ejb:ContainerManagedEntity"
    href="META-

  9. 在叠加实体列表中选择 BankAccount,然后选择 Generate...。将显示如图 10 所示的对话框。
    图 10. 叠加代码生成面板
    图 10. 叠加代码生成面板
  10. 选中 TransactionHistory 和 BankAccount 这两个叠加实体,然后选择 Finish
  11. 仍然在 Project Navigator 视图中,展开 BankAccountEJB => ejbModule,如图 11 所示:
    • 将显示 com.ibm.websphere.sample.websphere_deploy 和 com.ibm.websphere.sample.websphere_deploy.jdbc 这两个包。
    • 在第一个包中,创建了两个接口:BankAccountBeanUserDefinedPushDownMethods 和 TransactionHistoryBeanUserDefinedPushDownMethods。
    • 在第二个包中创建了两个类文件: BankAccountBeanUserDefinedPushDownMethodsImpl 和 TransactionHistoryBeanUserDefinedPushDownMethodsImpl。
    • 第二个包中的类文件实现了第一个包中的接口。

    图 11. 生成的叠加方法的结构
    图 11. 生成的叠加方法的结构
  12. 现在用户需要定制 BankAccountBeanUserDefinedPushDownMethodsImpl 和 TransactionHistoryBeanUserDefinedPushDownMethodsImpl 类来访问存储过程。我们将使用 getNumberOfTransactions 作为实例。
  13. 首 先,添加代码为 CallableStatement 设置 IN 和 OUT 参数,如清单 15 所示,其中包含了 getNumberOfTransactions 方法中所使用的代码。在这些代码中,我们从实体 bean BankAccount 中获取帐户 ID 然后为 CallableStatement 设置适当的参数。

    清单 15. 设置 CallableStatement 参数

    // Place code here like: callableStatement.setXXX(X, argX);
    String accountId = bean.getAccountId();
    callableStatement.setString(1, accountId);
    callableStatement.setString(2, arg0);
    callableStatement.setTimestamp(3, arg1);
    callableStatement.setTimestamp(4, arg2);
    callableStatement.registerOutParameter(5, java.sql.Types.INTEGER);

  14. 我们还需要添加代码来处理输出。清单 16 显示了处理结果部分的代码,在这里我们只需要从 CallableStatement 中获得输出参数。这个输出值是在指定的时间段内处理事务的总数。然后将 returnValue 变量放到这个输出值中。

    清单 16. 处理 CallableStatement 输出

    // The returnValue variable should be set to the appropriate value
    returnValue = callableStatement.getInt(5);

    本文附带的 BankAccountBeanUserDefinedPushDownMethodsImpl.java 和 TransactionHistoryBeanUserDefinedPushDownMethodsImpl.java 文件中包含这两个类的全部源代码。用户可以复制和粘贴剩下的部分。

  15. 编辑完这两个类后,保存并重新编译项目。清单 17 显示了完整的 getNumberOfTransactions 方法。

    清单 17. getNumberOfTransactions 方法

    /**
    * User-defined push-down method getNumberOfTransactions.
    * 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 arg1 Push-down method Parameter #1
    * @param arg2 Push-down method Parameter #2
    * @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.
    *
    * @return The return type of this push-down method is int
    *
    * @exception com.ibm.websphere.sample.exception.InvalidTransactionTypeException Thrown
    * if an exception occurs in the data logic method
    * @exception ResourceException Any other exceptions are wrapped in a
    * ResourceException.
    */
    public int getNumberOfTransactions(
    String arg0,
    java.sql.Timestamp arg1,
    java.sql.Timestamp arg2,
    BankAccountBean bean,
    AccessIntent accessIntent,
    Object connection)
    throws
    com.ibm.websphere.sample.exception.InvalidTransactionTypeException,
    ResourceException {
    Tr.entry(
    WsJrasTraceLogger.TYPE_ENTRY_EXIT,
    this,
    "getNumberOfTransactions(String, java.sql.Timestamp, java.sql.Timestamp,
    BankAccountBean, AccessIntent, Object)",
    new Object[] { arg0, arg1, arg2, bean, accessIntent, connection });
    int returnValue = 0;
    java.sql.CallableStatement callableStatement = null;
    try {
    callableStatement =
    helper.prepareCall(
    connection,
    "{call getNumberOfTransactions(?, ?, ?, ?, ?)}");
    // Begin customer-written code to populate callableStatement
    // Place code here like: callableStatement.setXXX(X, argX);
    String accountId = bean.getAccountId();
    callableStatement.setString(1, accountId);
    callableStatement.setString(2, arg0);
    callableStatement.setTimestamp(3, arg1);
    callableStatement.setTimestamp(4, arg2);
    callableStatement.registerOutParameter(5, java.sql.Types.INTEGER);
    // End customer-written code to populate callableStatement
    callableStatement.execute();
    // The returnValue variable should be set to the appropriate value
    returnValue = callableStatement.getInt(5);
    }
    catch (ResourceException re) {
    Tr.exception(
    WsJrasTraceLogger.TYPE_ERROR_EXC,
    this,
    "getNumberOfTransactions(String, java.sql.Timestamp,
    java.sql.Timestamp, BankAccountBean, AccessIntent, Object)",
    re);
    throw re;
    }
    catch (Exception e) {
    ResourceException re =
    helper.createResourceException(e, this.getClass());
    Tr.exception(
    WsJrasTraceLogger.TYPE_ERROR_EXC,
    this,
    "getNumberOfTransactions(String, java.sql.Timestamp,
    java.sql.Timestamp, BankAccountBean, 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.
    Tr.exit(
    WsJrasTraceLogger.TYPE_ENTRY_EXIT,
    this,
    "getNumberOfTransactions(String, java.sql.Timestamp, java.sql.Timestamp,
    BankAccountBean, AccessIntent, Object)",
    new Integer(returnValue));
    return returnValue;
    }

  16. 要导出 EAR 文件,请选择 File => Export...,然后将应用程序安装到 WebSphere Business Integration Server Foundation 中。

在 WebSphere Business Integration Server Foundation 中安装这种 CMP EJB 组件(利用存储过程功能安装 CMP)和安装任何典型的 CMP EJB 组件是类似的,因此本文没有涉及到这方面的内容。用户可以从 下载文件中安装 BankAccountCMPSP 应用程序,运行应用程序客户端来查看如何使用存储过程。有关详细信息请参阅 BankAccountSample.zip 文件中的 readme.txt。这个 ZIP 文件包含了 BankAccountBeanUserDefinedPushDownMethodsImpl 和 TransactionHistoryBeanUserDefinedPushDownMethodsImpl 的源代码。


blue_rule.gif
c.gif
c.gif
u_bold.gif回页首


结束语

我 们通过回顾存储过程模型和它所带来的好处开始本文,然后介绍了容器管理的方法,该方法使我们能够使用存储过程将 CMP 方法叠加到数据库中。然后描述了如何在 WebSphere Business Integration Server Foundation 环境中开发和部署可叠加的 CMP bean。这里提供了一个 BankAccount 应用程序实例,期待着它能够帮助您理解这个功能的更多细节问题,并帮助您开发能提供容器管理方法的 CMP bean。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130627/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/374079/viewspace-130627/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值