在 WebSphere Studio 中配置并使用 XA 分布式事务

来源:互联网 发布:分配算法 编辑:程序博客网 时间:2024/05/18 12:38

2004 年 9 月 01 日

本文描述了分布式事务,并向您演示了如何使用用于 DB2、Oracle 和 JMS 等可以在分布式事务中同时使用的 XA 资源来配置 WebSphere Studio Application Developer 测试服务器。本文描述了一个在同一事务中更新数据库并发送 JMS 消息的会话 bean。由于必须配置 Oracle 数据库以支持 XA 事务,因此本文展示了当 Oracle 数据库没有正确配置时会发生的 Java 异常,以及如何解决这些问题。

什么是分布式事务?

在开始研究如何使用分布式事务开发应用程序之前,让我们回顾一下什么是分布式事务以及为什么说它们很有用。有几个术语经常被混淆:事务、分布式事务、两阶段提交(two-phase commit)、XA 事务、事务传播和 CORBA OTS 事务。但实际上这些术语的含义各不相同:

事务-- 作为工作的单一逻辑单元而执行的一系列操作,在执行过程中要么所有的操作都被执行,要么一个都不执行(也被称为 本地或 简单事务)。事务经常被描述为 ACID-- 原子性、一致性、隔离性和持久性。为使事务持久,在事务的试验期所做的变更,最终必须用 提交操作作为事务的结尾。如果有任何变更不能被提交,则事务将 回滚(roll back),并取消所有的变更,恢复到事务从没有发生之前的状态。如果代码执行的操作决定不进行提交或者不能成功提交,那么它必须回滚该事务以取消这些操作。如果在事务进行期间应用程序崩溃,则在重启时,事务恢复将回滚该事务。在 写事务中,所有的变更必须全部提交或者全部回滚。在 读事务中,没有变更需要提交,但是事务会防止对正在被读取的数据的修改,直到所有的读操作全部结束。

分布式事务-- 两个或更多事务资源(比如,两个单独的数据库)之间的 ACID 事务。为使事务成功提交,所有的独立资源必须全部成功提交。如果其中的任何一个没有成功提交,则事务必须在所有的资源中回滚。

两阶段提交-- 一种分两步提交分布式事务的方法: 第 1 阶段,准备:每个资源报告是否其已经准备完毕,可以提交 -- 通常通过继续并保持新数据,但仍然不删除旧数据。 第 2 阶段,提交:如果所有的资源都准备就绪,则全部进行提交 -- 在旧数据删除且事务不再回滚之后进行。两阶段提交确保了分布式事务也可以始终提交或者始终回滚,即使在事务提交过程中系统的一部分崩溃,也不会受到影响。有许多(但不是全部)分布式事务的实现使用两阶段提交。

XA 规范-- 开放组织(Open Group)的 X/Open 分布式事务流程(DTP)模型,它定义了 应用程序如何使用 事务管理程序跨多个 资源管理程序来协调分布式事务。如果事务是通过遵循 XA 的事务管理程序来进行协调的,则任何遵循 XA 规范的资源管理程序都可以参与该事务,因此就可以让不同厂商的事务产品可以共同工作。所有遵循 XA 的事务都是分布式事务。XA 既支持一步提交,也支持两阶段提交。

下图是 XA 规范,它展示了分布式事务的各部分:


X/Open 分布式事务(DTP)模型
DTP 模型 

事务传播-- 通过将事务上下文作为线程的一部分进行传递,以允许多个合作对象参与单个事务。当线程经过合作对象时,事务管理程序使用线程的事务上下文来执行所有的工作。

CORBA OTS 规范-- 对象管理组(Object Management Group)的通用对象请求代理体系结构对象事务服务(Common Object Request Broker Architecture Object Transaction Service) -- 定义遵循规范的流程如何跨多个流程线程将事务上下文从一个流程传播到另一个流程。这种传播使得即使分布式对象运行于来自不同厂商的容器中,也可以在单个事务中合作。CORBA OTS 规范建立在 XA 规范的基础之上。

下图是 OTS 规范,它展示了包含分布式对象的事务的各部分:


对象事务服务(OTS)模型
OTS 模型 

IBM® WebSphere® Application Server V5 为其应用程序提供既符合 XA 规范又符合 CORBA OTS 规范的事务管理程序。该事务管理程序实现了两个 API,这两个 API 是 Java™ 2 Enterprise Edition (J2EE) 的一部分 -- Java 事务 API(JTA),它提供对 Java 事务服务(Java Transaction Service,JTS)的简单访问。对于任何遵循 XA 规范的资源,JTA 的 XA 部分都有能力协调用这些资源的事务(通过接口 javax.transaction.xa.XAResource )。J2EE 中支持 XA 的两个资源类型是 Java 数据库连接(Java Database Connectivity,JDBC)API(通过接口 javax.sql.XAConnection)和 Java 消息服务(Java Message Service,JMS)API(通过接口 javax.jms.XAConnection )。当一个容器中的 EJB 调用另一个容器中的 EJB 时,事务管理程序将使用 CORBA OTS。

为什么使用分布式事务?

当应用程序需要访问或更新事务资源中的数据时,它应该(通常是必须)使用事务来进行该项工作。在没有自动提交的标准 JDBC 代码中,应用程序使用一个连接来访问和更新数据,然后提交该连接以结束这个事务(并启动另一个事务)。当 JMS 客户端发送或接收消息时,消息传递提供方使用事务来向目标中添加消息或从目标中删除消息。JMS 客户端可以任意的显式控制该事务,比如在同一个消息传递系统中,协同处理从一个队列接收消息和向另一个队列发送消息。

当应用程序有一个单独的功能需要访问或更新多个事务资源中的数据时,应该使用分布式事务。您可以在每个资源中使用独立的简单事务,但是这种方法很容易出错。如果一个资源中的事务成功提交,而另一个资源中的事务提交失败且必须回滚,那么第一个事务将不再回滚,这将使应用程序的状态不一致。如果一个资源成功提交,但在另一个资源成功提交前系统崩溃,那么也会造成应用程序状态不一致。

哪些功能需要访问多个事务资源?以下是一些例子:

  • 在数据库之间移动数据-- 将数据从一个数据库移动到另一个数据库的应用程序需要使用分布式事务。否则,数据可能会重复(如果插入成功但删除失败)或丢失(如果删除成功但插入失败)。
  • 在数据库和消息之间移动数据-- 应用程序需要将数据从 JMS 消息移动到数据库表,或者相反。如果不使用分布式事务,数据可能会重复或丢失。
  • 将消息纪录到数据库-- 应用程序也许要使用数据库来保存“跟踪纪录”,“跟踪纪录”中记录了该应用程序与合作伙伴应用程序进行交换的消息。为了让纪录保持持久且准确,需要在发送或接收消息的分布式事务中记录这些消息。
  • 在消息传递系统之间移动消息-- 许多消息传递场景包括接收一个消息并发送另一个消息作为结果。当两个目的地在同一个消息传递系统中时,接收和发送可以在一个简单事务中进行,这是因为它们只涉及单一的事务资源。然而,当从一个消息传递系统接收消息,并将消息发送到另一个单独的消息传递系统时,应用程序应该在两个消息传递系统间的分布式事务中执行操作,以确保消息不会重复或丢失。
  • 与企业信息系统(EIS)协同工作-- J2EE 规范包含 J2EE 连接器体系结构,该体系结构用于实现适配器以访问企业信息系统(EIS),比如 CICS 或 SAP。适配器提供的事务支持级别 -- 无、本地或 XA -- 这取决于 EIS 被赋予的适配能力。如果适配器支持 XA 事务,应用程序就可以使用分布式事务来协调 EIS 资源与 JDBC 和 JMS 资源。

基本上,只要应用程序使用多个事务性持久资源,就需要使用分布式事务。操作多个资源的任何功能都应该使用分布式事务。

事务和 EJB

Enterprise JavaBeans (EJBs) 有许多优点:组件化、远程调用、安全性、持久性功能、消息传递功能等等。尽管这些功能都很有用,但使用 EJB 的最大好处可能就是事务管理。EJB 和 容器管理事务(container-managed transactions,CMTs)使事务管理对于 bean 开发人员来说实质上是透明的。

EJB 上的每个公共方法都定义了 EJB 容器和 EJB 客户端之间的事务边界,并在部署描述符指定该边界。这种方式的结果是客户端调用的所有 EJB 代码都运行于单个容器管理事务中(直到部署描述符指定另一个),这样代码的工作要么全部执行,要么全部回滚。此外,bean 开发人员实际上不需要编写任何用于处理事务的代码,比如确定何时调用 commit() 方法以及调用失败时该如何处理。容器通过事务边界和部署描述符表示了事务模型,并在运行时控制事务的提交和回滚。

即使多个 EJB 合作执行任务时,EJB 容器管理事务也可以工作。一旦线程中的第一个 EJB 建立了事务上下文,事务容器将使用事务传播来将该上下文传递到合作 EJB,这样所有的工作都在同一事务中执行(直到部署描述符指定另一个)。即使合作 EJB 存在于不同的容器中,CORBA OTS 也可以启用容器的事务管理程序进行协调,从而使事务跨容器传播。

EJB 容器管理事务对控制分布式事务特别有帮助。该方法允许代码操作多套数据,而不需要关心数据是来自单个资源(需要简单事务)还是多个资源(需要分布式事务)。应用程序只根据需要来使用 EJB 操作数据。EJB 容器的事务管理程序在运行时确定数据是来自单个资源(这种情况下它使用资源管理程序管理事务),还是来自多个资源(这种情况下使用事务管理程序管理事务,并协同资源管理程序)。不管数据来自于单个还是多个资源,EJB 代码保持不变,而由 EJB 容器适当的处理事务。







使用分布式事务进行开发

现在我们已经审查了想达到什么目标。我们确定了分布式事务是什么、有帮助且必需的场景,以及 EJB 技术如何使这些场景对 bean 开发人员大大简化。现在我们使用 WebSphere Studio Application Developer(以下简称 Application Developer)来创建并运行一个简单的范例。

这些代码片断向您展示了如何创建 Application Developer 测试服务器,以及如何为 JDBC 和 JMS 配置该服务器。它向您演示了使用特定于本范例的值来填写特定设置。每个表单前的描述说明了哪些值是适用于所有应用程序的,哪些值是特定于本范例的。

本范例也将说明如何为两个常用 JDBC 数据库产品(DB2® 和 Oracle)配置 XA 数据源。还将说明如何用 XA 连接来配置 JMS 目的地。

为开始本范例,我们需要创建一个新的测试服务器。为此,切换到 Servers 透视图。从 Server Configuration 视图的上下文(通过单击鼠标右键)菜单,选择 New =>Server and Server Configuration。在创建对话框中,输入服务器名称(例如“XA Example Server”),选择 Test Environment(对于本范例,WebSphere 5.0 版本或 WebSphere 5.1 版本都可以)作为服务器类型,然后单击 finish。


创建新服务器和服务器配置对话框
创建服务器对话框 

配置 Oracle XA 数据源

在创建数据源之前,我们将创建 JAAS 身份验证条目,以后我们将使该条目关联到数据源,以便登录数据库。在服务器配置编辑器中,切换到 Security 页面。在 JAAS Authentication Entries 列表中添加一个条目。您的条目必须对您的数据库有效,如本文所示,它能够创建表和添加数据。在本范例中,我们将使用一个注册范例,它是缺省 Oracle 安装的一部分。您可以通过特定的安装来使用不同的注册。

Oracle JAAS 身份验证条目设置

属性名称属性值是否缺省?AliasScott User IDscott Passwordtiger 
添加 JAAS 身份验证条目对话框
添加 JAAS 对话框 

对于本范例,服务器配置必须包含 JNDI 中注册的 JDBC 数据源,比如 jdbc/OracleXADS 。它是 JDBC 提供者,并将作为 Oracle XA 数据库而被访问。

首先,我们需要创建描述如何访问 Oracle XA 的 JDBC 提供者。在服务器配置编辑器中,切换到 Data Source 页面。在 JDBC 提供者列表中按下面所示添加一个提供者。名称可以任选,我们将使用与提供者类型相同的名称。

Oracle JDBC 提供者设置

属性名称属性值是否缺省?JDBC Provider typeOracle JDBC Driver (XA) NameOracle JDBC Driver (XA) Implementation class nameoracle.jdbc.xa.client.OracleXADataSourcedefaultClass path${ORACLE_JDBC_DRIVER_PATH}/ojdbc14.jardefault
创建 JDBC 提供者对话框
创建提供者对话框 

第二步,创建 JDBC 数据源以提供对特定 Oracle XA 数据库的访问。选择我们刚刚创建的 Oracle 提供者,然后用如以下所示的设置添加一个数据源。名称可以任选。JNDI 名是您的代码用来查找数据源的唯一标识符。本文的范例代码使用 jdbc/OracleXADS 作为 JNDI 名。对于容器管理的身份验证别名,选择我们 先前创建的 JAAS 身份验证条目。 URL 是所需的资源属性,它指定访问数据库的路径。

Oracle JDBC 数据源设置

属性名称属性值是否缺省?JDBC Provider typeOracle JDBC Driver (XA), version 5.0 NameExample Oracle XA Data Source JNDI namejdbc/OracleXADS Helper classcom.ibm.websphere.rsadapter.OracleDataStoreHelperdefaultAuthentication aliasScott URLjdbc:oracle:thin:@localhost:1521:example 
创建 JDBC 数据源对话框
创建数据源对话框 

我们刚才创建的 JDBC 提供者会在特定的目录中查找它的 JAR 文件,该目录通过变量 ORACLE_JDBC_DRIVER_PATH 指定。该变量必须设置为正确的目录,但 WebSphere Studio 并不知道您的 Oracle 客户端安装在哪里,因此您必须指定该目录。在服务器配置编辑器中,切换到 Substitution variables 页面。在 Node 设置下面是已定义变量列表。找到用于 Oracle 的变量,并将其设置为包含 ojdbc14.jar 文件的目录。

Oracle 定义的变量(对节点)设置

属性名称属性值是否缺省?ORACLE_JDBC_DRIVER_PATHC:/oracle/ora92/jdbc/lib 
编辑 Oracle JDBC 驱动程序路径变量对话框
编辑变量对话框 

现在您已经用 Oracle XA JDBC 数据源配置了服务器。保存服务器配置。

尽管您已经用 Oracle XA JDBC 数据源配置了您的测试服务器,但还需要对 Oracle 数据库自身进行配置以使其支持 XA 事务。Oracle DBA 可以帮助您确定是否需要进一步的配置,并可以帮助您执行这些步骤。在本文的后面,我们将讨论如果您的 Oracle 针对 XA 进行配置,将会出现什么错误,以及如何进行正确的配置。

配置 DB2 XA 数据源

本范例还包含了 DB2 XA 数据源。DB2 数据源的配置设置与 Oracle 数据源的配置设置很相似。

首先,定义一个 JAAS 标识用于登录到您的数据库。在本范例中,我们设置了一个名为 DB2 的标识。

DB2 JAAS 身份验证条目设置

属性名称属性值是否缺省?AliasDB2 User IDdb2 Passworda_password 

第二步,我们需要为 DB2 XA 数据源创建一个 JDBC 提供者。

DB2 JDBC 提供者设置

属性名称属性值是否缺省?JDBC Provider typeDB2 Universal JDBC Driver Provider (XA) 

Name

DB2 Universal JDBC Driver Provider (XA) Implementation class namecom.ibm.db2.jcc.DB2XADataSourcedefaultClass path

${DB2UNIVERSAL_JDBC_DRIVER_PATH}}/db2jcc.jar

${UNIVERSAL_JDBC_DRIVER_PATH}}/db2jcc_license_cu.jar

${DB2UNIVERSAL_JDBC_DRIVER_PATH}}/db2jcc_license_cisuz.jar

default

第三步,需要使用 JNDI 名称 jdbc/DB2XADS 创建 DB2 XA 数据源。我们将使用在 第 1 步中创建的 JAAS 标识。尽管 Oracle 数据源需要 URL 字符串来访问数据库,但 DB2 数据源只需要数据库名称,在本范例中数据库名称为“sample”。

DB2 JDBC 数据源设置

属性名称属性值是否缺省?JDBC Provider typeDB2 Universal JDBC Driver Provider (XA), version 5.0 NameExample DB2 XA Data Source JNDI namejdbc/DB2XADS Helper classcom.ibm.websphere.rsadapter.DB2UniversalDataStoreHelperdefaultAuthentication aliasDB2 databaseNamesample 

第四步,需要指定安装 DB2 驱动程序的路径,这是包含了 db2jcc.jar 文件的目录。另外,还需要核对数据源类路径中其他变量的缺省值。

DB2 定义的变量(对节点)设置

属性名称属性值是否缺省?DB2UNIVERSAL_JDBC_DRIVER_PATHC:/db2/SQLLIB/java UNIVERSAL_JDBC_DRIVER_PATH${WAS_INSTALL_ROOT}/universalDriver/libdefault

现在您已经配置了 DB2 XA 数据源,并将其注册为 jdbc/DB2XADS 。保存服务器配置。

配置 JMS 提供者

为了演示 XA 事务,我们还需要嵌入 JMS 资源。Application Developer 中内嵌了 JMS 模拟器,它是一个简单的 JMS 资源,但却包含了全部的 XA 功能。您的使用 DB2 或 Oracle XA 的 WebSphere 应用程序可以使用 JMS 以外的资源,但本范例需要使用 JMS。

在服务器配置编辑器中,切换到 JMS 页面。在 JMS Server Properties 下,添加队列名 XAExampleQ 。在 JMS Provider 下,选择 MQ Simulator for Java Developers。

在本范例中,服务器配置必须包括两个 JMS 资源:一个名为 jms/XAExampleQCF 的队列连接工厂(queue connection factory)和一个名为 jms/XAExampleQ 的队列。在服务器配置编辑器的 JMS 页面上,在 WASQueueConnectionFactories 条目列表中,按以下所示添加一个新的队列连接工厂。确保 XA 支持被启用,这是本范例中最重要的一点。

WAS 队列连接工厂设置

属性名称属性值是否缺省?NameXAExampleQCF JNDI Namejms/XAExampleQCF Enable XA SupportTrue (checked)default
添加 WAS 队列连接工厂对话框
添加 QCF 对话框 

在 WASQueue 条目列表中,用以下设置添加一个新队列:

WAS 队列设置

属性名称属性值是否缺省?NameXAExampleQ JNDI Namejms/XAExampleQ 
添加 WAS 队列对话框
添加队列对话框 

保存服务器配置。

现在服务器配置已经全部完成。它有一个用于通过 XA 访问 Oracle 的数据源,以及一个使用支持 XA 事务的队列连接工厂的 JMS 队列。您甚至现在就可以运行服务器。它不包含任何应用程序,但您可以检验它是否可以正常启动,并可以检验我们为该服务器配置的 JNDC 中的对 JDBC 和 JMS 资源的绑定。成功的启动将包含以下特定于 JMS 以及特定于 JDBC 的消息:

JMSMQJDProvid A MSGS0656I: Starting the MQJD JMS ProviderJMSMQJDProvid A MSGS0650I: MQJD JMS Provider open for businessResourceMgrIm I WSVR0049I:         Binding XAExampleQCF as jms/XAExampleQCFResourceMgrIm I WSVR0049I:         Binding XAExampleQ as jms/XAExampleQResourceMgrIm I WSVR0049I:         Binding Example Oracle XA Data Source as jdbc/OracleXADSResourceMgrIm I WSVR0049I:         Binding Example DB2 XA Data Source as jdbc/DB2XADSCacheServiceI I DYNA0048I: WebSphere Dynamic Cache initialized successfully.      

可选项:导入服务器配置

您确实应该按以上描述的方法练习设置服务器配置。但如果您配置不成功,并且希望运行下面将描述的范例应用程序,您可以导入配置。

为了开始导入,请下载并解压缩范例文件。它包含一个名为 XA Example Server.wsc 的目录,这是 WebSphere version 5.1 服务器配置。

在导入服务器配置之前,您首先需要创建一个服务器,这在本文的开始部分就已经描述过。由于该配置适用于 5.1 版本,因此确保该服务器是 WebSphere version 5.1 测试环境(在 Application Developer version 5.1.1 中有效)。

它还可以帮助您看到服务器列表和配置列表。在 Server 透视图的 Server Configuration 视图中,点击 Menu 按钮选择 View =>Advanced


高级服务器配置视图菜单
高级视图菜单 

下一步,通过下列步骤导入服务器配置:

  1. 从 Studio 菜单栏中,选择 File =>Import
  2. 在 Select 页面中打开 Import 向导。选择 Server Configuration并单击 Next
  3. 向导切换到它的 Import a Server Configuration 页面。
    1. 在 Configuration Name 字段中,输入一个名称,该名称必须不同于您刚才创建的服务器名称,比如“Imported Configuration”。
    2. Folder 字段将是您的服务器项目的缺省名称,通常名为“Servers”。
    3. 为 Configuration Type 选择 WebSphere version 5.1 =>Server Configuration
    4. 在 Location 字段中,单击 Browse 并选择您已经下载的 XA Example Server.wsc 文件。
  4. 单击 Finish以导入服务器配置。Server Configuration 视图将列出您指定的新配置名称。

导入服务器配置对话框
导入配置对话框 

现在我们需要切换到您已经创建好的服务器,这样就使用您刚才导入的服务器配置。在 Server Configuration 视图中,选择服务器,弹出上下文菜单(通过点击鼠标右键),选择 Switch configuration,然后选择您所导入的配置。

为您的服务器打开服务器配置编辑器,您将看到它现在已经有了早前描述的 JDBC 和 JMS 资源的设置。(如果您没有看到这些设置,请确保将您的服务器切换到导入的配置。)

启动服务器,您将看到早前描述的服务器启动日志。







设置范例应用程序

本文包括了一个 J2EE 应用程序范例,您可以下载该范例并导入到 Application Developer,然后运行。它将使用您早前配置的 JDBC 和 JMS 资源来执行一个 XA 事务。

在开始之前,下载并解压缩范例文件。它包含一个名为 XAExampleEAR.ear 的 Enterprise Archive (EAR) 文件。按照以下步骤将该文件导入到您的 WebSphere Studio 工作区:

  1. 从 Studio 菜单栏中选择 File =>Import
  2. 在 Select 页面中打开 Import 向导。选择 EAR file并单击 Next
  3. 向导切换到它的 Enterprise Application Import 页面。在 EAR File 字段中,单击 Browse 并选择您所下载的 XAExampleEAR.ear 文件。项目名称缺省为 XAExampleEAR 。
  4. 单击 Finish 以导入该 EAR 文件。您的工作区现在将包含三个项目,分别是 XAExampleEAR 、 XAExampleEJB 和 XAExampleWeb 。

XAExampleEJB 项目包含 EJB,因此您必须生成它们的部署代码。选择 XAExampleEJB 项目,弹出上下文菜单(通过点击鼠标右键),然后选择 Generate =>Deployment and RMIC code。使用该对话框为 XAExampleSession bean 生成代码。Application Developer 将生成一个名称类似于 EJSLocalStatelessXAExampleSession_5a0991c7 的新类。这是一个具体类,它与 WebSphere Application Server EJB 容器一同工作,使范例 EJB 实现 EJB 规范定义的所有特性。

您还必须将 EAR 项目添加倒测试服务器。这类似于部署 EAR,但是更灵活一些,因为当您修改 EAR 的代码时,您不必重新部署 EAR 以使用新代码。相反,您可以简单的重启服务器或者在运行中的服务器内重启 EAR 项目并开始测试您对代码的修改。

要将 EAR 项目添加到测试服务器,请在 Server Configurations 视图中选择服务器,弹出上下文菜单(通过点击鼠标右键),然后选择 Add and remove projects。在该对话框中,将 XAExampleEAR 添加到已配置项目列表中。在 Server Configuration 视图中展开导入的配置,您将看到该 EAR 成为配置的一部分。

下载的范例中还包含一个名为 XAExampleDB.sql 的文件,它创建了一个名为 XA_EXAMPLE 的数据库表。对于 Oracle,在 SQL*Plus 中运行该文件以创建表:

SQL> @C:/temp/XA Example Article/XAExampleDB.sqlDROP TABLE XA_EXAMPLE          *ERROR at line 1:ORA-00942: table or view does not exist Table created.

对于 DB2,在 DB2 命令行处理器中运行该文件:

C:/db2/SQLLIB/BIN>db2 -tvf C:/temp/XA Example Article/XAExampleDB.sqlDROP TABLE XA_EXAMPLEDB21034E  The command was processed as an SQL statement because it was not avalid Command Line Processor command.  During SQL processing it returned:SQL0204N  "DB2.XA_EXAMPLE" is an undefined name.  SQLSTATE=42704 CREATE TABLE XA_EXAMPLE ( TEXT VARCHAR(256) NOT NULL )DB20000I  The SQL command completed successfully.

范例代码

范例应用程序比较简单,它由两个类组成:

  1. XAExampleSessionBean -- 一个执行 XA 事务的会话 EJB。
  2. XAExampleServlet -- 一个调用 EJB 的 servlet。

该会话 bean 的公共方法 persistAndSend(String) 将参数写入两个 JDBC 表(DB2 和 Oracle)和 JMS 消息中。单独来看这不是一个非常有用的应用程序,但是,由于 JDBC 和 JMS 是事务性资源,因此执行所有三个任务是需要 XA 事务的。分布式(两阶段)事务确保这三个任务可以成功的执行。如果一个任务不能成功提交,其他的任务将回滚,不执行任何任务。这样就维护了两个资源之间的一致性。

             public        void persistAndSend(String data)         throws Exception {                  try {               DataSource oracleDS = getDataSource(        "java:comp/env/jdbc/OracleXADS");               persist(data, oracleDS);                DataSource db2DS = getDataSource(        "java:comp/env/jdbc/DB2XADS");               persist(data, db2DS);                QueueConnectionFactory factory =                   getQueueConnectionFactory(        "java:comp/env/jms/XAExampleQCF");               Queue queue = getQueue(        "java:comp/env/jms/XAExampleQ");               send(data, factory, queue);          }                  catch (Exception e) {               e.printStackTrace();                       this.getSessionContext().setRollbackOnly();                       throw e;          }     }      

该方法执行以下三个步骤:

  1. 使用名为 jdbc/OracleXADS 的数据源保持数据。
  2. 使用名为 jdbc/DB2XADS 的数据源保持数据。
  3. 将 JMS 消息中的数据发送到名为 jms/XAExampleQ 的队列。

如果发生任何错误,批处理块将捕捉该异常,将事务标记为回滚,并重新抛出该异常。为了代码的完整性,以下列出了实现该会话 bean 的其余代码:

             private        void persist(String data, DataSource datasource)                  throws SQLException {           System.out.println(        "Adding a new database row containing: " + data);           Connection connection =         null;                  try {               connection = datasource.getConnection();                PreparedStatement statement =                   connection.prepareStatement(                                       "INSERT INTO XA_EXAMPLE (TEXT) VALUES (?)");               statement.setString(1, data);               statement.execute();                System.out.println(        "Successfully added row: " + data);          }                  finally {                       if (connection !=         null)                   connection.close();          }     }              private        void send(String data, QueueConnectionFactory factory, Queue queue)                  throws JMSException {           System.out.println(        "Sending a message containing: " + data);           QueueConnection connection =         null;                  try {               connection = factory.createQueueConnection();               QueueSession session =                   connection.createQueueSession(        false, Session.AUTO_ACKNOWLEDGE);               QueueSender sender = session.createSender(queue);               TextMessage message = session.createTextMessage(data);                sender.send(message);                System.out.println(        "Successfully sent message: " + data);          }                  finally {                       if (connection !=         null)                   connection.close();          }     }              private DataSource getDataSource(String jndiName)         throws NamingException {                  return (DataSource)         this.getJNDIObject(jndiName);     }              private QueueConnectionFactory getQueueConnectionFactory(String jndiName)                  throws NamingException {                   return (QueueConnectionFactory)         this.getJNDIObject(jndiName);     }              private Queue getQueue(String jndiName)         throws NamingException {                  return (Queue)         this.getJNDIObject(jndiName);     }              private Object getJNDIObject(String jndiName)         throws NamingException {          Context root =         new InitialContext();                  return root.lookup(jndiName);     }      

这是用于使用 JDBC 和 JMS 的标准代码。甚至没有任何代码用于定义事务或者将事务变为 XA。但由于 WebSphere Application Server 的 EJB 容器将这些代码作为单独的公共 EJB 方法来调用,所以容器会在事务中自动运行这些代码。容器将正常使用简单(单阶段)事务,但当第二个事务资源更新时,容器将自动检测,并将简单事务转换为 XA 事务。作为该工作的一部分,容器将通知资源并为其协调 XA 事务,让这些资源参与该事务。所有这些行为都是自动完成的,不需要开发人员编写任何额外的代码,而仅仅通过实现 EJB 中的代码就可以完成。







运行范例应用程序

当您阅读本文时,最好应该在 Application Developer 中执行这些步骤。如果您是这样做的,那么现在您已经配置了一个测试服务器并安装了范例 EAR。

现在可以启动服务器以运行该应用程序。在 Server 透视图中,进入 Servers 视图。选择您的服务器并单击 Start 运行它。如果服务器正确运行,您将在控制台看到类似于以下纪录的启动日志:

JMSMQJDProvid A MSGS0656I: Starting the MQJD JMS ProviderJMSMQJDProvid A MSGS0650I: MQJD JMS Provider open for businessResourceMgrIm I WSVR0049I:         Binding XAExampleQCF as jms/XAExampleQCFResourceMgrIm I WSVR0049I:         Binding XAExampleQ as jms/XAExampleQResourceMgrIm I WSVR0049I:         Binding Example Oracle XA Data Source as jdbc/OracleXADSResourceMgrIm I WSVR0049I:         Binding Example DB2 XA Data Source as jdbc/DB2XADSCacheServiceI I DYNA0048I: WebSphere Dynamic Cache initialized successfully.. . .ApplicationMg A WSVR0200I:         Starting application: XAExampleEAREJBContainerI I WSVR0207I:         Preparing to start EJB jar: XAExampleEJB.jarEJBContainerI I WSVR0037I:         Starting EJB jar: XAExampleEJB.jarWebContainer A SRVE0169I:         Loading Web Module: XAExampleWeb.WebGroup I SRVE0180I: [XAExampleWeb] [/XAExampleWeb] [Servlet.LOG]: JSP 1.2 Processor: initWebGroup     I SRVE0180I: [XAExampleWeb] [/XAExampleWeb] [Servlet.LOG]: SimpleFileServlet: initWebGroup     I SRVE0180I: [XAExampleWeb] [/XAExampleWeb] [Servlet.LOG]: InvokerServlet: initApplicationMg A WSVR0221I:         Application started: XAExampleEAR      

现在我们可以运行范例应用程序。在您的 web 浏览器中,调用这个 URL:

 http://localhost:9080/XAExampleWeb/XAExampleServlet?data=hello%20world 

这将使用一个参数调用我们的 XAExampleServlet servlet,该参数的名称为 data ,值为“hello world”。如果一切运行正常,浏览器中将会显示以下结果:

Just persisted and sent data: hello world

同样,在服务器控制台,您将看到以下一些状态消息:

SystemOut     O Adding a new database row containing: hello worldSystemOut     O Successfully added row: hello worldSystemOut     O Adding a new database row containing: hello worldSystemOut     O Successfully added row: hello worldSystemOut     O Sending a message containing: hello worldSystemOut     O Successfully sent message: hello world

如果所有这些工作都完成,恭喜您,您已经成功运行了一个使用了 XA 事务的应用程序,该 XA 事务跨越 JDBC 和 JMS 资源。

范例应用程序的故障诊断

有几个潜在的问题可能导致范例的运行不成功。其中两个是部署范例时的常见错误,这很容易改正。另外两个是关于 Oracle 数据库中的配置问题,这需要 Oracle DBA 来修改。

以下部分列出了可能出现的问题,它说明了 Application Developer 控制台显示的异常,给出了关于为什么会发生问题的简短描述,并说明了如何修改每个问题。

ClassNotFoundException: EJSStatelessXAExampleSessionHomeBean

启动服务器时,如果应用程序成功启动,您将得到这些消息:

ApplicationMg A WSVR0200I:         Starting application: XAExampleEAREJBContainerI I WSVR0207I: Preparing to start EJB jar: XAExampleEJB.jarEJBContainerI I WSVR0037I: Starting EJB jar: XAExampleEJB.jarWebContainer  A SRVE0169I: Loading Web Module: XAExampleWeb.WebGroup      I SRVE0180I: [XAExampleWeb] [/XAExampleWeb] [Servlet.LOG]: JSP 1.2 Processor: initWebGroup      I SRVE0180I: [XAExampleWeb] [/XAExampleWeb] [Servlet.LOG]: SimpleFileServlet: initWebGroup      I SRVE0180I: [XAExampleWeb] [/XAExampleWeb] [Servlet.LOG]: InvokerServlet: initApplicationMg A WSVR0221I:         Application started: XAExampleEAR      

然而,您也可能会得到以下错误:

ApplicationMg A WSVR0200I:         Starting application: XAExampleEAREJBContainerI I WSVR0207I: Preparing to start EJB jar: XAExampleEJB.jarBeanMetaData  E CNTR0075E:         The user-provided class      "com.ibm.example.xa.EJSStatelessXAExampleSessionHomeBean_5a0991c7"      needed by the EnterpriseBean could not be found or loaded.EJBContainerI E WSVR0209E: Unable to prepare EJB jar XAExampleEJB.jar      [class com.ibm.ws.runtime.component.DeployedModuleImpl],      enterprise bean com.ibm.etools.ejb.impl.SessionImpl(XAExampleSession)      (transactionType: Container, sessionType: Stateless) java.lang.        ClassNotFoundException:           com.ibm.example.xa.        EJSStatelessXAExampleSessionHomeBean_5a0991c7     at com.ibm.ws.classloader.CompoundClassLoader.findClass(CompoundClassLoader.java:351)     at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:261)     at java.lang.ClassLoader.loadClass(ClassLoader.java(Compiled Code))     at com.ibm.ejs.container.BeanMetaData.loadExistedClass(BeanMetaData.java:2573)     at com.ibm.ejs.container.BeanMetaData.<init>(BeanMetaData.java:888)     at com.ibm.ws.runtime.component.EJBContainerImpl.createBeanMetaData(EJBContainerImpl.java:980)     . . . DeployedAppli W WSVR0206E: Module, XAExampleEJB.jar, of application,      XAExampleEAR.ear/deployments/XAExampleEAR, failed to startApplicationMg W WSVR0101W: An error occurred starting, XAExampleEARApplicationMg A WSVR0217I: Stopping application: XAExampleEARApplicationMg A WSVR0220I:         Application stopped: XAExampleEAR      

该错误是 ClassNotFoundException ,缺少的类是 com.ibm.example.xa.EJSStatelessXAExampleSessionHomeBean_5a0991c7 。该错误发生在试图启动 EJB jar XAExampleEJB.jar 的时候。

该错误指出了您没有生成 EJB 代码,因此容器无法为 XAExampleSession bean 找到所生成的 Home 类。要修改该问题,请先在指定的 EJB 项目中为所有的 bean 生成 Deployment and RMIC code,然后重新启动服务器。

SQLException: Table or view does not exist

在第一次运行范例时,您可能会得到以下错误:

WSRdbDataSour I DSRA8203I:         Database product name: OracleWSRdbDataSour I DSRA8204I: Database product version:      Personal Oracle9i Release 9.2.0.1.0 - ProductionWith the Partitioning, OLAP and Oracle Data Mining optionsJServer Release 9.2.0.1.0 - ProductionWSRdbDataSour I DSRA8205I: JDBC driver name  : Oracle JDBC driverWSRdbDataSour I DSRA8206I: JDBC driver version  : 9.2.0.1.0WebGroup      E SRVE0026E: [Servlet Error]-[ORA-00942: table or view does not exist]: java.sql.        SQLException:         ORA-00942:         table or view does not exist     at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:134)    at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:289)    at oracle.jdbc.ttc7.Oall7.receive(Oall7.java:573)    at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1891)    at oracle.jdbc.ttc7.TTC7Protocol.parseExecuteFetch(TTC7Protocol.java:1093)    at oracle.jdbc.driver.OracleStatement.executeNonQuery(OracleStatement.java:2047)    at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.java:1940)    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:2709)    at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:589)    at oracle.jdbc.driver.OraclePreparedStatement.execute(OraclePreparedStatement.java:656)    at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.execute(WSJdbcPreparedStatement.java:386)    at com.ibm.example.xa.XAExampleSessionBean.persist(XAExampleSessionBean.java:45)    at com.ibm.example.xa.XAExampleSessionBean.persistAndSend(XAExampleSessionBean.java:30)    at com.ibm.example.xa.EJSLocalStatelessXAExampleSession_5a0991c7.persistAndSend         (EJSLocalStatelessXAExampleSession_5a0991c7.java:23)    at XAExampleServlet.persistAndSend(XAExampleServlet.java:44)    at XAExampleServlet.doGet(XAExampleServlet.java:28)    at javax.servlet.http.HttpServlet.service(HttpServlet.java:740)    at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)    . . .      

该错误是 SQLException ,错误代码为 ORA-00942 。 很显然,是因为期望的数据库表或视图不存在。但是,该错误没有说明具体是哪个表或视图未找到。栈跟踪显示该错误发生在 XAExampleSessionBean.persist 方法中,它位于 .java 文件的第 45 行。点击那个栈跟踪行,编辑器将进入该代码的 execute 行:

          PreparedStatement statement =               connection.prepareStatement(        "INSERT INTO XA_EXAMPLE VALUES (?)");          statement.setString(1, data);          statement.execute();      

该问题发生在向 XA_EXAMPLE 表中插入纪录的时候。这说明您没有运行创建数据库表的 SQL 文件。要修改该问题,请运行 SQL 文件:

SQL> @C:/workspace/XAExampleEJB/XAExampleDB.sql

现在重新运行范例。

OracleXAException: XAER_RMERR (Internal XA Error)

在第一次运行范例时,您可能会得到以下错误:

WSRdbDataSour I DSRA8203I:         Database product name : OracleWSRdbDataSour I DSRA8204I: Database product version :      Personal Oracle9i Release 9.2.0.1.0 - ProductionWith the Partitioning, OLAP and Oracle Data Mining optionsJServer Release 9.2.0.1.0 - ProductionWSRdbDataSour I DSRA8205I: JDBC driver name  : Oracle JDBC driverWSRdbDataSour I DSRA8206I: JDBC driver version  : 9.2.0.1.0WSRdbXaResour E DSRA0304E:         XAException occurred. XAException contents and details are:The XA Error is            : -3The XA Error message is    : A resource manager error has occurred in the transaction branch.The Oracle Error code is   :         65535The Oracle Error message is:         Internal XA ErrorThe cause is               : null.WSRdbXaResour E DSRA0302E: XAException occurred.  Error code is:         XAER_RMERR.       Exception is: <null>RegisteredRes E WTRN0078E:         An attempt by the transaction manager to call start          on a transactional resource has resulted in an error.          The error code was XAER_RMERR. The exception stack trace follows:      oracle.jdbc.xa.        OracleXAException     at oracle.jdbc.xa.OracleXAResource.checkError(OracleXAResource.java:1157)     at oracle.jdbc.xa.client.OracleXAResource.start(OracleXAResource.java:295)     at com.ibm.ws.rsadapter.spi.WSRdbXaResourceImpl.start(WSRdbXaResourceImpl.java:927)     at com.ibm.ejs.j2c.XATransactionWrapper.start(XATransactionWrapper.java:1267)     at com.ibm.ws.Transaction.JTA.JTAResourceBase.start(JTAResourceBase.java:164)     at com.ibm.ws.Transaction.JTA.RegisteredResources.startRes(RegisteredResources.java:389)     at com.ibm.ws.Transaction.JTA.TransactionImpl.enlistResource(TransactionImpl.java:1903)     at com.ibm.ws.Transaction.JTA.TranManagerSet.enlist(TranManagerSet.java:494)     . . .      

该错误是因为事务管理程序不能将事务转换为 XA 事务。Oracle 明确的返回错误 XAER_RMERR ,错误代码编号为 65535。 ORA-65535 不是一个有效错误代码,并且 XAER_RMERR 实质上意味着 XA 发生了某些错误。这说明您的 Oracle 数据库没有被配置为支持 XA 事务。因此,当 WebSphere Application Server 事务管理程序命令 Oracle 事务管理程序参与这个 XA 事务时,Oracle 拒绝该命令并抛出这个异常。

解决方法是运行 Oracle 安装中包含的两个脚本。这很可能需要通过您的 Oracle DBA 来执行,您必须以 SYSOPER 或 SYSDBA 身份登录到 Oracle,以拥有足够的权限来运行这些脚本。这些脚本是:

  • directory: <ORACLE_HOME>/javavm/install
    • file: initxa.sql
    • file: initjvm.sql

initxa.sql 脚本配置 XA 数据库。一旦它成功运行,您的数据库就被配置为 XA。该脚本在您第一次运行的时候可能成功。不幸的是,由于一些数据库的内存空间太小,该脚本也可能无法成功运行。为修改该问题,运行 initjvm.sql 脚本。它可能也会运行失败,但是在失败时,该脚本会说明哪个参数需要调整。参数被保存在该文件中:

  • directory: <ORACLE_HOME>/database
    • file: init<DATABASE_SID>.ora

这个表说明了有两个参数的值特别需要增大。对于特定的数据库配置可能需要调整不同的参数。

Oracle Initialization Values

Parameter NameMinimum Valuejava_pool_size12000000shared_pool_size24000000

一旦 initjvm.sql 运行成功,则 initxa.sql 就应该也可以成功运行。Oracle 数据库需要重新启动,以使修改生效。您可以重新启动 WebSphere Studio 测试服务器并重新尝试运行该范例。

XAException: XAER_RMERR (xa_recover)

您也许能够很正常的运行该范例,起码在修改了这些问题之后变得正常。您可以开发通过 Oracle 使用 XA 事务的应用程序并正常运行该程序。但是可能有一天您会发现无法启动 WebSphere Application Server。该问题的发生可能是由于您没有正常的关闭服务器,导致 WebSphere 和/或 Oracle 崩溃。WebSphere 的启动错误类似于以下所示:

SecurityCompo I SECJ0243I: Security service started successfullySecurityCompo I SECJ0210I: Security enabled falseWSRdbXaResour E DSRA0304E:         XAException occurred.      XAException contents and details are: The cause is               : null.36185510 WSRdbXaResour E DSRA0302E:  XAException occurred.       Error code is:         XAER_RMERR.  Exception is: <null>XARminst      E WTRN0037W:         The transaction service encountered an error      on an xa_recover operation. The resource was J2CXAResourceInfo :cfName = XA Example Data SourceconfigProps = [Deployed Resource Adapter Properties]     OptionC_authDataAlias      java.lang.String             scott     UserName            java.lang.String                     scott     Password             java.lang.String             ********     TransactionResourceRegistration   java.lang.String             dynamic     InactiveConnectionSupport            java.lang.Boolean         true     secureMode         boolean            true     . . .The error code was XAER_RMERR. The exception stack trace follows:      javax.transaction.xa.XAException     at oracle.jdbc.xa.OracleXAResource.recover(OracleXAResource.java:626)     at com.ibm.ws.rsadapter.spi.WSRdbXaResourceImpl.recover(WSRdbXaResourceImpl.java:672)     at com.ibm.ws.Transaction.JTA.XARminst.recover(XARminst.java:130)     at com.ibm.ws.Transaction.JTA.XARecoveryData.recover(XARecoveryData.java:673)     at com.ibm.ws.Transaction.JTA.RecoveryManager.resync(RecoveryManager.java:1369)     at com.ibm.ws.Transaction.JTA.ResyncThread.run(RecoveryManager.java:1440) ApplicationMg A WSVR0200I: Starting application: IBMUTC      

该错误是由于事务管理程序无法执行 XA 恢复操作。Oracle 明确的返回错误 XAER_RMERR。作为最后一个错误, XAER_RMERR 指出了 XA 发生了一些问题。这表明 WebSphere 没有使用 Oracle 正常的关闭连接,可能是由于其中一个服务器没有正常关闭,或者是全都没有正常关闭。因为 WebSphere Application Server 试图重新建立连接,则 Oracle 需要回滚进程中的任何事务,但是使用 WebSphere 登录到数据库的 Oracle 用户(在本范例中是 scott )不能执行恢复工作。

解决方法是为 Oracle 用户赋予权限以执行恢复操作,特别是访问内部使用的 Oracle 表以管理恢复。在 SQL Plus 中以 SYSOPER 或 SYSDBA 身份运行下列命令:

 grant select on DBA_PENDING_TRANSACTIONS to PUBLIC 

如果您不希望将该权限授予所有的用户,可以仅指定错误中列出的用户(在本范例中是 scott)。然后重新启动数据库,这一次您应该能够成功的重启 WebSphere 服务器。







结束语

本文向您演示了如何开发一个 WebSphere Studio Application Developer 测试服务器,该服务器使用用于 Oracle XA 的 JDBC 数据源、用于 DB2 XA 的数据源,以及一个 JMS 队列连接工厂和一个队列进行配置。它描述了通过一个可以更新 JDBC 和 JMS 数据的简单会话 EJB 来实现 XA 事务。您已经看到这个简单的应用程序在正确运行时的状况,也看到了可能发生的一些错误以及如何进行修改。通过这些知识,您就可以编写使用 XA 事务的 WebSphere Java 应用程序。

原创粉丝点击