上一篇文章介绍了JDBC事务,JDBC可以处理单数据源的事务,满足大部分事务处理的需求,但是JDBC事务不能解决多数据源和分布式事务问题,Java平台给我们提供了解决方案--JTA。本文将探讨JTA的一些细节。
一 分布式事务
通常把一个数据库内部的事务处理,如对多个表的操作,作为本地事务看待。数据库和JDBC的事务处理对象是本地事务,而分布式事务处理的对象是全局事务。
所谓全局事务,是指分布式事务处理环境中,多个数据库可能需要共同完成一个工作,这个工作即是一个全局事务,例如,一个事务中可能更新几个不同的数据库。对数据库的操作发生在系统的各处,但必须全部被提交或回滚。此时一个数据库对自己内部所做操作的提交不仅依赖本身操作是否成功,还要依赖与全局事务相关的其它数据库的操作是否成功,如果任一数据库的任一操作失败,则参与此事务的所有数据库所做的所有操作都必须回滚。
一般情况下,某一数据库无法知道其它数据库在做什么,因此,在一个 DTP 环境中,交易中间件是必需的,由它通知和协调相关数据库的提交或回滚。而一个数据库只将其自己所做的操作(可恢复)影射到全局事务中,这个环境就是分布式事务处理模型。
二 XA规范
X/Open 组织(即现在的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。
一般常见的事务管理器( TM )是交易中间件,例如JDBC或者hibernate提供的transactionmanager,常见的资源管理器( RM )是数据库,通常就是数据源,例如JDBC或第三方提供的datasource,常见的通信资源管理器( CRM )是消息中间件,如JMS。
典型的DTP模型如下:
三 JTA实现
我们还是以前文提到的转账为例,说明JTA的具体实现。
<span style="font-size:18px;"> public void transferAccount() { UserTransaction userTx = null; Connection connA = null; Statement stmtA = null; Connection connB = null; Statement stmtB = null; try{ // 获得 Transaction 管理对象 userTx = (UserTransaction)getContext().lookup(" java:comp/UserTransaction"); // 从数据库 A 中取得数据库连接 connA = getDataSourceA().getConnection(); // 从数据库 B 中取得数据库连接 connB = getDataSourceB().getConnection(); // 启动事务 userTx.begin(); // 将 A 账户中的金额减少 500 stmtA = connA.createStatement(); stmtA.execute(" update t_account set amount = amount - 500 where account_id = 'A'"); // 将 B 账户中的金额增加 500 stmtB = connB.createStatement(); stmtB.execute(" update t_account set amount = amount + 500 where account_id = 'B'"); // 提交事务 userTx.commit(); // 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新) } catch(SQLException sqle){ try{ // 发生异常,回滚在本事务中的操纵 userTx.rollback(); // 事务回滚:转账的两步操作完全撤销 //( 数据库 A 和数据库 B 中的数据更新被同时撤销) stmt.close(); conn.close(); ... }catch(Exception ignore){ } sqle.printStackTrace(); } catch(Exception ne){ e.printStackTrace(); } }</span>
注意:我们在上述示例中,使用了两个数据库,即connA 和 connB 是来自不同数据库的连接。
四 JTA实现原理
JTA 究竟是通过何种方式来实现事务管理器呢? 要理解 JTA 的实现原理首先需要了解其架构:它包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。 根据所面向对象的不同,我们可以将 JTA 的事务管理器和资源管理器理解为两个方面:面向开发人员的使用接口(事务管理器)和面向服务提供商的实现接口(资源管理器)。其中开发接口的主要部分即为上述示例中引用的
UserTransaction 对象,开发人员通过此接口在信息系统中实现分布式事务;而实现接口则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。以数据库为例,IBM 公司提供了实现分布式事务的数据库驱动程序,Oracle 也提供了实现分布式事务的数据库驱动程序, 在同时使用 DB2 和 Oracle 两种数据库连接时, JTA 即可以根据约定的接口协调者两种事务资源从而实现分布式事务。正是基于统一规范的不同实现使得 JTA
可以协调与控制不同数据库或者 JMS 厂商的事务资源,其架构如下图所示:
开发人员使用开发人员接口,实现应用程序对全局事务的支持;各提供商(数据库,JMS 等)依据提供商接口的规范提供事务资源管理功能;事务管理器( TransactionManager )将应用对分布式事务的使用映射到实际的事务资源并在事务资源间进行协调与控制。 下面,本文将对包括 UserTransaction、Transaction 和 TransactionManager
在内的三个主要接口以及其定义的方法进行介绍。
面向开发人员的接口为 UserTransaction (使用方法如上例所示),开发人员通常只使用此接口实现 JTA 事务管理,其定义了如下的方法:
begin()- 开始一个分布式事务
commit()- 提交事务
rollback()- 回滚事务
getStatus()- 返回关联到当前线程的分布式事务的状态
setRollbackOnly()- 标识关联到当前线程的分布式事务将被回滚
面向提供商的实现接口主要涉及到 TransactionManager 和 Transaction 两个对象
Transaction 代表了一个物理意义上的事务,在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程。UserTransaction 接口中的 commit()、rollback(),getStatus() 等方法都将最终委托给 Transaction
类的对应方法执行。
registerSynchronization(Synchronization sync)- 回调接口,Hibernate 等 ORM 工具都有自己的事务控制机制来保证事务, 但同时它们还需要一种回调机制以便在事务完成时得到通知从而触发一些处理工作,如清除缓存等。这就涉及到了 Transaction 的回调接口 registerSynchronization。工具可以通过此接口将回调程序注入到事务中,当事务成功提交后,回调程序将被激活。
TransactionManager 本身并不承担实际的事务处理功能,它更多的是充当用户接口和实现接口之间的桥梁。下面列出了 TransactionManager 中定义的方法,可以看到此接口中的大部分事务方法与 UserTransaction 和 Transaction 相同。 在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过
ThreadLocale 关联到当前线程上;同样 UserTransaction.commit() 会调用 TransactionManager.commit(), 方法将从当前线程下取出事务对象 Transaction 并把此对象所代表的事务提交, 即调用 Transaction.commit()
下面将通过具体的代码向读者介绍 JTA 实现原理。下图列出了示例实现中涉及到的 Java 类,其中UserTransactionImpl 实现了 UserTransaction 接口,TransactionManagerImpl 实现了 TransactionManager 接口,TransactionImpl 实现了 Transaction 接口
JTA实现类图如下:
为什么必须从支持事务的数据源中获得的数据库连接才支持分布式事务呢?其实支持事务的数据源与普通的数据源是不同的,它实现了额外的 XADataSource 接口。我们可以简单的将 XADataSource 理解为普通的数据源(继承了 java.sql.PooledConnection),只是它为支持分布式事务而增加了 getXAResource 方法。另外,由 XADataSource 返回的数据库连接与普通连接也是不同的,此连接除了实现
java.sql.Connection 定义的所有功能之外还实现了 XAConnection 接口。我们可以把 XAConnection 理解为普通的数据库连接,它支持所有 JDBC 规范的数据库操作,不同之处在于 XAConnection 增加了对分布式事务的支持。通过下面的类图读者可以对这几个接口的关系有所了解:
应用程序从支持分布式事务的数据源获得的数据库连接是 XAConnection 接口的实现,而由此数据库连接创建的会话(Statement)也为了支持分布式事务而增加了功能,如下代码所示:
JTA事务资源处理
<span style="font-size:18px;">public void transferAccount() { UserTransaction userTx = null; Connection conn = null; Statement stmt = null; try{ // 获得 Transaction 管理对象 userTx = (UserTransaction)getContext().lookup(" java:comp/UserTransaction"); // 从数据库中取得数据库连接, getDataSourceB 返回支持分布式事务的数据源 conn = getDataSourceB().getConnection(); // 会话 stmt 已经为支持分布式事务进行了功能增强 stmt = conn.createStatement(); // 启动事务 userTx.begin(); stmt.execute("update t_account ... where account_id = 'A'"); userTx.commit(); } catch(SQLException sqle){ // 处理异常代码 } catch(Exception ne){ e.printStackTrace(); } }</span>
五 总结
JTA的实现其实来源于JDBC的事务,JTA区别于JDBC的是:资源管理器需要实现XADatasource接口,只要实现了这个接口,就可以通过JTA的事务管理器实现事务管理。