事务
本小节内容是IBM developerworks上两篇文章的简略版,并附上了一些自己的总结。
JTA即Java Transaction API(JTA)。我们平时对事务的了解止步于ACID
- 原子性,可以由代码实现,比如try catch
- 一致性,一致性的概念是基于特定于业务的,比如转账。
- 隔离性,意味着一个事务的效果不影响正在同时执行的其他事务。从事务的角度讲,它意味着事务按顺序执行而不是并行执行。在数据库系统中,通常通过使用锁机制来实现隔离性。为了使应用程序获得最佳性能,有时也会对某些事务放松隔离性的要求。
- 持久性,意味着一旦成功完成某个事务,对应用程序状态所做的更改将 “经得起失败”。这个就需要我们对事务进行记录,比如日志(undo/redo log等)或数据库中。
实现ACID特性需要多个参与者共同作用
- 应用程序
- 事务监视器,TPM协调RM的活动,以确保事务的“要么全有要么全无”属性。
- 资源管理器(RM,即我们要操作的对象,诸如数据库之类)。并且根据管理RM个数的不同,事务分为local Transaction和global Transaction(管理两个及以上RM)
与Java程序进行类比,事务在应用程序级别所提供的一些优势与 catch 和 finally 块在方法级别所提供的优势相同;它们使我们不用编写很多错误复原代码,即可执行可靠的错误复原。
JTA接口
java提供一个javax.transaction
包,定义了事务(包括分布式事务)的一些接口。这些接口定义的组件间的通信用到了XA协议(javax.transaction.xa
),X/Open XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。
JTA提供了以下三个接口
1.javax.transaction.UserTransaction
,是面向开发人员的接口,能够编程地控制事务处理。
2.javax.transaction.TransactionManager
,允许应用程序服务器来控制代表正在管理的应用程序的事务。
3.javax.transaction.xa.XAResource
,面向提供商的实现接口,是一个基于X/Open CAE Specification的行业标准XA接口的Java映射。提供商在提供访问自己资源的驱动时,必须实现这样的接口。
来一个经典的转账例子,假设我们要操作账户a和账户b,本地事务处理(在一个数据库中)流程:
Connection conn = null;
try{
//若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
conn.setAutoCommit(false);
// 将 A 账户中的金额减少 500
// 将 B 账户中的金额增加 500
conn.commit();
}catch(){
conn.rollback();
}
如果账户a和b不在一个数据库中,可以应用UserTransaction接口:
UserTransaction userTx = null;
Connection connA = null;
Connection connB = null;
try{
userTx.begin();
// 将 A 账户中的金额减少 500
// 将 B 账户中的金额增加 500
userTx.commit();
}catch(){
userTx.rollback();
}
此时,connection就得支持XAResource接口了。XAResource和Transaction如何关联呢?增强exec方法。Connection的exec方法除了处理数据之外,还包含和Transaction关联的操作。
public void execute(String sql) {
// 对于每次数据库操作都检查此会话所在的数据库连接是否已经被加入到事务中
associateWithTransactionIfNecessary();
try{
// 处理数据库操作的代码
} catch(SQLException sqle){
// 处理异常代码
} catch(Exception ne){
e.printStackTrace();
}
}
public void associateWithTransactionIfNecessary(){
// 获得 TransactionManager
TransactionManager tm = getTransactionManager();
Transaction tx = tm.getTransaction();
// 检查当前线程是否有分布式事务
if(tx != null){
// 在分布式事务内,通过 tx 对象判断当前数据连接是否已经被包含在事务中,
// 如果不是那么将此连接加入到事务中
Connection conn = this.getConnection();
//tx.hasCurrentResource, xaConn.getDataSource() 不是标准的 JTA
// 接口方法,是为了实现分布式事务而增加的自定义方法
if(!tx.hasCurrentResource(conn)){
XAConnection xaConn = (XAConnection)conn;
XADataSource xaSource = xaConn.getDataSource();
// 调用 Transaction 的接口方法,将数据库事务资源加入到当前事务中
tx.enListResource(xaSource.getXAResource(), 1);
}
}
}
TransactionManager本身并不承担实际的事务处理功能,它更多的是充当用户接口和实现接口之间的桥梁。此接口中的大部分事务方法与UserTransaction和 Transaction 相同。
Transaction代表了一个物理意义上的事务,在开发人员调用UserTransaction.begin()
方法时TransactionManager 会创建一个Transaction事务对象(标志着事务的开始)并把此对象通过ThreadLocal关联到当前线程。同样UserTransaction.commit()
会调用 TransactionManager.commit()
,方法将从当前线程下取出事务对象Transaction并把此对象所代表的事务提交.其它方法诸如“rollback(),getStatus()”也是如此。
我以前的文章有说过,ThreadLocal算是在线程的方法间传递参数的一种方式。此处,TransactionManager和Transaction的关系也非常值得学习,Transaction负责实现接口操作,至于这些接口方法什么时候被调用,包括它从线程上被“拿上”还是“拿下”,这个活儿由TransactionManager干。然后用户就可以不分线程的使用TransactionManager,也无需知道自己用的是哪个Transaction。
重新审视下段代码,可以翻译为:
// 创建一个Transaction,挂到当前线程上
UserTransaction userTx = null;
Connection connA = null;
Connection connB = null;
try{
userTx.begin();
// 将Connection对应的XAResource挂到当前线程对应的Transaction
connA.exec("xxx")
connB.exec("xxx")
// 找到Transaction关联的XAResource,让它们都提交
userTx.commit();
}catch(){
// 找到Transaction关联的XAResource,让它们都回滚
userTx.rollback();
}
通过上述内容,我们可以得到一个链,UserTransaction ==> TransactionManager ==> Transaction ==> 与其关联的XAResource。begin,commit,rollback操作就是这样一步步传导下来,其中Threadlocal扮演了关键角色。