讨论组
:http://group.qqread.com
正如上面所提到的,如果存在应用程序服务器(Application Server),还可以由它来进行这一初始化。
在用正确的参数初始化 XADataSource 之后,就将 XADataSource 返回给方法调用者。
85 db2xacon = initDB2XAConnection (db2ds);
86 ifxxacon = initIfxXAConnection (ifxds);
|
在第 85 和 86 行的代码中,创建了到数据库的 XA Connection。下面描述了如何初始化这些 XA Connection。
329 XAConnection initIfxXAConnection (IfxXADataSource ifxdatasource) {
330
331 XAConnection xacon = null;
332
333
334 try {
335 System.out.print ("Set up IDS XA connection: ");
336 xacon = ifxdatasource.getXAConnection (
337 props.getProperty ("ifx.connection.username"),
338 props.getProperty ("ifx.connection.password"));
339
340 System.out.println ("Okay.");
341 }
342 catch (SQLException e) {
343 sqlerr (e);
344 }
345
346 return xacon;
347 }
|
为了设置 XAConnection,要使用前面初始化的 DataSource 对象。第 336 行使用 XADataSource 创建了 XAConnection。为了完成 XAConnection,只需要将身份验证信息传递给该对象。
87 db2xares = initXAResource (db2xacon);
88 ifxxares = initXAResource (ifxxacon);
|
现在,您准备创建 XAResource 对象了。这些对象将允许您操作两阶段提交(Two-Phase-Commit)。
388 XAResource initXAResource (XAConnection xacon) {
389
390 XAResource xares = null;
391
392
393 try {
394 System.out.print ("Setting up a XA resource: ");
395 xares = xacon.getXAResource ();
396 System.out.println ("Okay.");
397 }
398 catch (SQLException e) {
399 sqlerr (e);
400 }
401
402 return xares;
403 }
|
XAResource 对象的安装没有什么特别的。该对象是通过调用 XAConnection 中的 getXAResource() 来创建的。
在完成所有关于 XA 的准备之后,就创建到数据库的 JDBC 连接。
89 db2con = getDatabaseConnection (db2xacon);
90 ifxcon = getDatabaseConnection (ifxxacon);
|
在 getDatabaseConnection() 方法中,建立了一个 JDBC 数据库连接。
250 Connection getDatabaseConnection (XAConnection xacon) {
251
252 Connection con = null;
253
254 try {
255 System.out.print ("Establish database connection: ");
256 con = xacon.getConnection ();
257 System.out.println ("Okay.");
258 }
259 catch (SQLException e) {
260 sqlerr (e);
261 }
262
263 return con;
264 }
|
这看上去有些混乱。既然已经在第 336 行中设置了 XAConnection,我们为何还需要 JDBC 连接呢?我们为何仍然需要一个“传统”连接的理由是所有其他 JDBC 操作和类(Statement、ResultSet ...)都基于或使用 Connection 对象。如果您看一看 JDBC 类的层次结构图,将会发现 XAConnection 并非是 Connection,反之亦然。XAConnection(实际上,它是 ConnectionPool 的子类)使用 Connection(层次化)。
93 db2xid = createDB2XID ();
94 ifxxid = createIfxXID ();
|
启动 XA 事务之前的最后一步就是为数据库创建 XA ID 对象。在分布式事务(Distributed Transaction)中进行操作时,总是要使用这个 xid。
183 Xid createIfxXID () {
184
185 Xid xid = null;
186
187 byte [] gid = new byte[1];
188 byte [] bid = new byte[1];
189
190 gid[0] =
191 (Byte.decode (props.getProperty ("xid.global"))).byteValue ();
192 bid[0] =
193 (Byte.decode (props.getProperty ("xid.branch.ifx"))).byteValue ();
194
195 System.out.print ("Creating an XID (" + Byte.toString (gid[0]) + ", " +
196 Byte.toString (bid[0]) + ") for Informix: ");
197
198 xid = new IfxXid (0, gid, bid);
199 System.out.println ("Okay.");
200 return xid;
201 }
|
createIfxXID 方法创建一个 XID(这里:用于 IDS 连接)。正如“两阶段提交协议简介”小节中提到的,XA 事务包含定义该事务的两个元素。上面例子中的重要部分在第 198 行中。IDS XID 是同三个参数创建的。第一个参数是 format ID,它描述在什么格式中构建分布式事务(Distributed Transaction)。您可以省略这一格式信息。第二个参数定义了全局事务 ID(global transaction ID)。该 ID 对于所有参与数据库来说是惟一的。第三个参数表示该全局事务中的事务分支。
在(为 DB2 和 IDS)构建 XID 之后,我们可以使用它们来修改单个事务中的数据。
98 execBranch (db2con, db2xares, db2xid);
99 execBranch (ifxcon, ifxxares, ifxxid);
|
execBranch() 方法包含了上面为每个连接所定义的 XA 事务中的修改。
215 void execBranch (Connection con, XAResource xares, Xid xid) {
216
217 String sql = props.getProperty ("sql.statement");
218
219 try {
220 xares.start (xid, javax.transaction.xa.XAResource.TMNOFLAGS);
221
222 Statement stmt = con.createStatement ();
223 stmt.executeUpdate (sql);
224
225 xares.end (xid, javax.transaction.xa.XAResource.TMSUCCESS);
226 }
227 catch (XAException e) {
228 System.err.println ("XA exception caught:");
229 System.err.println ("Cause : " + e.getCause ());
230 System.err.println ("Message: " + e.getMessage ());
231 e.printStackTrace ();
232 }
233 catch (SQLException e) {
234 sqlerr (e);
235 }
236 }
|
第 219-226 行代码包含了分布式事务(Distributed Transaction)中为相应分支所使用的真正 SQL 语句。分支边界在第 220 行中以 start 方法开始。传递给该方法的参数就是我们已经知道的事务 ID,而第二个参数包含了用于该 XA 事务的一些附加信息。因为这是第一个两阶段提交(Two-Phase-Commit)协议操作,所以不需要向该方法传递任何特殊信息。TMNOFLAGS 说明了这一事实。分支边界终止于第 225 行。标志 TMSUCCESS 描述所有操作都成功。
在 IDS 和 DB2 的分支都执行之后,全局事务就准备提交这些修改。当然,在可以向数据库传送最后的提交之前,必须询问数据库是否准备进行提交。
104 if (prepareCommit (db2xares, db2xid) == XAResource.XA_OK &&
105 prepareCommit (ifxxares, ifxxid) == XAResource.XA_OK) {
106 // both branches are ready to commit
107 commitBranch (db2xares, db2xid);
108 commitBranch (ifxxares, ifxxid);
109 }
110 else {
111 // a resource reported an error
112 rollbackBranch (db2xares, db2xid);
113 rollbackBranch (ifxxares, ifxxid);
114 }
116 } // end of constructor
|
第 104 和 105 行通知数据库准备提交。如果数据库报告 XAResource.XA_OK,就可以提交整个事务。否则,该事务就将被 ROLLBACK 中止。
417 int prepareCommit (XAResource xares, Xid xid) {
418
419 int rc = 0;
420
421 System.out.print ("Prepare XA branch (" +
422 Byte.toString ((xid.getGlobalTransactionId ())[0]) + ", " +
423 Byte.toString ((xid.getBranchQualifier ())[0]) + "): ");
424
425 try {
426 xares.prepare (xid);
427 }
428 catch (XAException e) {
429 xaerr (e);
430 }
431
432 System.out.println ("Okay.");
433 return rc;
434 }
|
prepareCommit() 方法中最重要的一行在第 426 行中。prepare 方法引起数据库调用两阶段提交协议(Two-Phase-Commit)的“第 1 阶段”。
根据“第 1 阶段”的结果,将提交或中止该分布式事务(Distributed Transaction)。下面是将用于发出这些必要操作的两个方法。
128 void commitBranch (XAResource xares, Xid xid) {
129
130 System.out.print ("Commit XA branch (" +
131 Byte.toString ((xid.getGlobalTransactionId ())[0]) + ", " +
132 Byte.toString ((xid.getBranchQualifier ())[0]) + "): ");
133
134 try {
135 // second parameter is 'false' since we have a two phase commit
136 xares.commit (xid, false);
137 }
138 catch (XAException e) {
139 xaerr (e);
140 }
141
142 System.out.println ("Okay.");
143 }
|
如果“第 1 阶段”未报告任何错误,就在第 136 行中为 xid 所描述的事务分支提交“第 2 阶段”。方法 commit() 中的第二个参数区分单阶段或两阶段提交操作。因为我们具有一个两阶段提交操作,所以必须将该值设置为 false。
下面的例子展示了如何为数据库回滚事务分支。
446 void rollbackBranch (XAResource xares, Xid xid) {
447
448 System.out.print ("Rollback XA branch (" +
449 Byte.toString ((xid.getGlobalTransactionId ())[0]) + ", " +
450 Byte.toString ((xid.getBranchQualifier ())[0]) + "): ");
451
452 try {
453 xares.rollback (xid);
454 }
455 catch (XAException e) {
456 xaerr (e);
457 }
458
459 System.out.println ("Okay.");
460 }
|
问题解答
本文中的例子演示了如何在 Java 中使用 JTA 实现两阶段提交(Two-Phase-Commit)协议。在该应用程序中,如果一个事务分支报告了错误,您就要负责进行错误处理。但是“两阶段提交协议简介”小节中提到仍然存在一个问题,那就是如果第 2 阶段中一个事务分支发生故障,该怎么办呢?
如果再次查看程序代码,您可以看到在“第 1 阶段”和“第 2 阶段”之间有一个很小的时间间隔。在这一时间间隔中,出于某种理由,其中某一参与数据库可能崩溃。如果发生了,我们将陷入分布式事务已经部分提交的情形中。
假定下列情形:在“第 1 阶段”之后,您从 DB2 和 IDS 数据库中都收到了“okay”。在下一步中,应用程序成功提交了 DB2 的事务分支。接着,应用程序通知 DB2 事务分支提交事务。现在,在应用程序可以通知 IDS 事务分支提交它这一部分之前,IDS 引擎由于断电发生崩溃。这就是一种部分提交全局事务的情形。您现在该怎么办呢?
在重启之后,DB2 和 IDS 都将尝试恢复打开的事务分支。该引擎等待来自应用程序的提示如何做。如果应用程序没有准备重新发送“第 2 阶段”的提交,该事务分支将被引擎所启动的试探性回滚中止。这是非常糟糕的,因为这将使该全局事务处于不一致状态。
一种解决方案是用一个小型应用程序连接引擎中打开的事务分支,并通知引擎提交或回滚这一打开的事务。如果您使用 IDS 作为后端,那么还有一个隐藏的 onmode 标志,允许您结束打开的事务分支。(onmode -Z xid)。
在 DB2 UDB 中,您可以发出 LIST INDOUBT TRANSACTIONS 来获得打开的 XA 事务的有关信息。您必须查看 DB2 Information Center 中的描述来解决该问题。
上面描述的情形是一个很好的例子,也是使用应用程序服务器(Application Server)或事务监控器(Transaction Monitor)的理由。在使用一个中间层服务器时,就由该服务器负责保持事情正常。
备选方案
清单 1 演示了在应用程序中从数据库读取数据并处理结果的可行方法。如果您的应用程序是“只读”应用程序,IBM? 就提供了另一种解决方案,称作 WebSphere? Information Integrator。WebSphere Information Integrator 使用来自 DB2 UDB(或 DB2 Data Joiner、DB2 Relational Connect)的联邦数据库技术,以将多个数据库(通常:数据源)虚拟化(virtualize)到一个数据库中。不同的、非本地的数据库中的表都链接到 DB2 UDB 中。该操作对于客户机应用程序是完全透明的。客户机可以访问其他数据库中的所有远程表,就像它们是本地 DB2 UDB 表一样。正如 清单 1 中引用的,不再需要连接两个数据库。到 DB2 UDB 的单个连接就已经足够了,因为 DB2 中可以看到 IDS 数据库中的所有表。
目前,WebSphere Information Integrator 不支持两阶段提交,然而,将来的版本将支持两阶段提交协议;这将带来实现企业应用程序的新方法

更多内容请看
informix专题,或
进入讨论组讨论。
【深 度 阅 读】 相 关 文 章