到目前为止,对于事务的讨论基本上都聚焦在本地事务上,本地事务只会涉及到一个单一的数据源。本章开始介绍分布式事务,分布式事务会在单个事务内涉及多个数据源。以下内容主要包括:
- 分布识事务基础设施
- 事务管理器和资源管理器
XADataSource
,XAConnection
和XAResource
接口- 两阶段提交
JDBC 的事务管理 API 与 JTA 规范是兼容的。
基础设施
分布式事务的基础设施包括以下几个部分:
- 事务管理器,用来控制事务的边界和管理两阶段提交协议。它也应该是 JTA 的一个经典实现。
- 实现了
XADataSource
,XAConnection
和XAResource
接口的 JDBC 驱动。 - 一个对于应用层完全可见的
DataSource
实现,利用它来操作XADataSource
,并与事务管理器交互。通常这个实现由应用服务器提供。 - 用来管理底层数据的资源管理器。在 JDBC 的语境中,资源管理器指的是数据库服务器。“资源管理器” 这个术语实际上来自于 JTA,在这里使用这个术语是为了强调 JDBC 中的分布式事务是遵循 JTA 规范的架构来处理的。
通常会以“三层架构”来实现这个基础设施,包括:
- 客户端
- 一个包含应用程序、EJB 服务器、JDBC 驱动集合的中间层
- 多个资源管理器
分布式事务也可以实现为“两层架构”。在两层架构中,应用层本身就会扮演事务管理器的角色,并且直接操作 XADataSource
API。下图阐述了分布式事务的基础设施:
后续的内容将会对基础设施的各个部分进行详细的说明
XADataSource
和 XAConnection
XADataSource
和 XAConnection
接口,定义在 javax.sql 包中。支持分布式事务的数据库驱动需要实现这两个接口。XAConnection
继承了 PooledConnection
接口,添加了一个 getXAResource
方法,这个方法会生成一个 XAResource
对象,事务管理器可以利用这个对象来完成分布式事务。以下是 XAConnection
接口的定义:
public interface XAConnection extends PooledConnection {
javax.transaction.xa.XAResource getXAResource()
throws SQLException;
}
因为继承了 PooledConnection
接口,所以所有的 XAConnection
对象也支持 PooledConnection
中定义的方法。这些对象代表着与底层数据源的一条可重用的物理连接,应用层也可以通过这个对象操作这条连接。
XAConnection
对象由 XADataSource
生成。ConnectionPoolDataSource
和 XADataSource
有一些相似的地方,他们都实现了 DataSource
接口。这样就允许分布式事务的底层实现对于应用层来说是透明的。XADataSource
接口定义如下:
public interface XADataSource {
XAConnection getXAConnection() throws SQLException;
XAConnection getXAConnection(String user,
String password) throws SQLException;
//...
}
通常,一个基于 XADataSource
之上的 DataSource
实现,也会包含一个连接池化的模块。
部署 XADataSource
对象
部署一个 XADataSource
与先前所提到的 ConnectionPoolDataSource
是一样的。流程总共分两步,如下代码所示:
// com.acme.jdbc.XADataSource implements the
// XADataSource interface.
// Create an instance and set properties.
com.acme.jdbc.XADataSource xads = new com.acme.jdbc.XADataSource();
xads.setServerName(“bookstore”);
xads.setDatabaseName(“bookinventory”);
xads.setPortNumber(9040);
xads.setDescription(“XADataSource for inventory”);
// First register xads with a JNDI naming service, using the
// logical name “jdbc/xa/inventory_xa”
Context ctx = new InitialContext();
ctx.bind(“jdbc/xa/inventory_xa”, xads);
// Next register the overlying DataSource object for application
// access. com.acme.appserver.DataSource is an implementation of
// the DataSource interface.
// Create an instance and set properties.
com.acme.appserver.DataSource ds =
new com.acme.appserver.DataSource();
ds.setDescription(“Datasource supporting distributed transactions”);
// Reference the previously registered XADataSource
ds.setDataSourceName(“jdbc/xa/inventory_xa”);
// Register the DataSource implementation with a JNDI naming service,
// using the logical name “jdbc/inventory”.
ctx.bind(“jdbc/inventory”, ds);
获取连接
调用 DataSource.getConnection 方法返回一个底层实现为 XAConnection 的逻辑 Connection 对象:
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup(“jdbc/inventory”);
Connection con = ds.getConnection(“myID”,“mypasswd”);
DataSource.getConnection 方法的底层实现如下所示:
// Assume xads is a driver’s implementation of XADataSource
XADataSource xads = (XADataSource)ctx.lookup(“jdbc/xa/" +
"inventory_xa”);
// xacon implements XAConnection
XAConnection xacon = xads.getXAConnection(“myID”, “mypasswd”);
// Get a logical connection to pass back up to the application
Connection con = xacon.getConnection();
CODE EXAMPLE 12-5 Getting a logical connection from an
XAResource
XAResource
接口是 JTA 规范中的一个定义,也是 Java 语言中对应 X/Open 组织的 XA 的对等概念。 XAResource
对象通过调用 XAConnection.getXAResource
方法获得,通过调用该方式将 XAConnection
与一个分布式事务绑定。同一时刻,一个 XAConnection
只能与一个分布式事务绑定。JDBC 驱动应维护 XAResource
与 XAConnection
的一对一关系,也就是说对于 getXAResource 方法的多次调用,都返回同一个 XAResource
对象。
通常,中间应用服务器调用 XAConnection.getXAResource
方法得到一个 XAResource
对象后,会将它交给外部的事务管理器,事务管理器并不需要直接与 XAConnection
交互,它直接利用 XAResource
对象。
事务管理器管理多个 XAResource
对象,每一个都代表一个参与到分布式事务的资源管理器,注意,不同的 XAResource
对象可能指向同一个资源管理器,当不同的事务参与者使用同一个 XADataSource
,就有可能出现这种情况。
XAResource
定义了以下的方法,来完成两阶段提交协议,每个方法都要求有一个 xid 参数,用来标识一个分布式事务。
- start 方法。通知资源管理器后续的操作来处于一个分布式事务中。
- end 方法。通知资源管理器事务结束。
- prepare 方法。获取资源管理器关于事务应该回滚还是提交的投票。
- commit 方法。通知资源管理器提交它的事务分支。只有当所有的参与者都投票提交全局事务时,这个方法才能被调用。
- rollback 方法。通知资源管理器回滚它的事务分支。只有当至少一个事务参与者投票回滚全局事务时,这个方法才会被调用。
JTA 规范中有对 XAResource 完整的描述。
事务管理
通过 XAResource.start 和 XAResource.end 方法来定义事务边界。边界内事务模式为全局事务。边界外的事务为本地事务。
除了一些约束,一个事务参与者的应用代码应该怎么写,与它是否参与分布式事务没有什么关系。分布式事务的边界一般都是由外部的事务管理者来定义的,事务的参与者不能调用 Connection
类的某些方法:
- setAutoCommit(true)
- commit
- rollback
- setSavepoint
如果事务参与者试图调用这些方法,JDBC 驱动应抛出 SQLException
。当分布式事务结束后,这些方法的调用就不再有限制,并且应用于本地事务。
在事务边界内,事务参与者应该避免调用 Connection.setTransactionIsolation
方法,这个方法的行为不做约束,由驱动自主决定。
如果一个连接在参与分布式事务前的 autocommit 属性已经为 true,那么当它参与分布式事务时,这个属性会被忽略,当事务结束后,属性才重新生效。
两阶段提交
以下几个步骤阐述了事务管理器如何利用 XAResource
对象实现二阶段提交协议。这些步骤是基于使用具有外部事务管理器的应用服务器的“三层架构”实现的。
应用服务器从两个不同的连接中获取
XAResource
对象// XAConA connects to resource manager A javax.transaction.xa.XAResource resourceA = XAConA.getXAResource(); // XAConB connects to resource manager B javax.transaction.xa.XAResource resourceB = XAConB.getXAResource();
- 应用服务器传递
XAResource
对象给事务管理器,事务管理器不直接与XAConnection
对象交互。 事务管理器利用
XAResource
将资源管理器纳入事务中,整个事务以一个 xid 作为标识,由事务管理器在启动事务时负责生成。// Send work to resource manager A. The TMNOFLAGS argument indicates // we are starting a new branch of the transaction, not joining or // resuming an existing branch. resourceA.start(xid, javax.transaction.xa.TMNOFLAGS); // do work with resource manager A ... // tell resource manager A that it’s done, and no errors have occurred resourceA.end(xid, javax.transaction.xa.TMSUCCESS); // do work with resource manager B. resourceB.start(xid, javax.transaction.xa.TMNOFLAGS); // B’s part of the distributed transaction ... resourceB.end(xid, javax.transaction.xa.TMSUCCESS);
事务管理器启动两阶段提交协议,请求两个参与者进行投票:
resourceA.prepare(xid); resourceB.prepare(xid);
如果一个事务参与者想要投票 rollback,它需要抛出一个 javax.transaction.xa.XAException
。
如果两个事务参与者都投票 commit,事务参与者通知两个参与者提交他们的事务分支:
resourceA.commit(xid, false); resourceB.commit(xid, false);
如果任何一个事务参与者投票 rollback,事务管理器则通知各个参与者回滚它们的事务分支:
resourceA.rollback(xid); resourceB.rollback(xid);
事务管理器在处理一个事务分支的不同阶段的时候,不一定要用的是同一个 XAResource
对象,只要这两个对象的连接是来自于同一个事务管理器。
连接关闭
当在分布式事务环境中,应用层使用完一条连接后,中间层服务器应该得到通知。在先前对 PooledConnection
对象的讨论中,我们指出了当 Connection.close
方法被调用时,中间件服务器会作为一个 ConnectionEventListener
得到通知。在这时,事务管理器也会得到通知,并且结束对应的事务分支。如果 DataSource
包含池化模块,那么池化模块也必须得到通知,以便将 XAConnection
归还。
XAResource 接口的局限性
XAResource
接口只定义了 X/Open XA 规范中要求定义的方法。如果一个资源管理器支持了一些在 XA 规范中没有定义的特性,那么应用层无法明显地通过 API 去使用这些特性,只能交给具体的驱动在底层去处理。如果应用间接利用了这个特性,这又会带来一个可移植性的问题。