目录
1 背景说明
前面文章提到我们使用atomikos+druid搭建了一个示例demo分析XA事务管理,那么atomikos有哪些对象,是如何配合Druid使用的,Druid又是如何执行XA的呢?接下来将分析这些流程涉及到的主要对象,以及会对这些对象做些说明,将工作原理给解析出来。
2 Atomikos Connection相关对象
Atomikos Connection相关对象依赖关系图如下图所示:
-
AtomikosDataSourceBean是我们配置的入口对象,该对象管理调用init()方法初始化,继承AbstractDataSourceBean。
-
AbstractDataSourceBean是atomikos的核心对象,类似DruidDataSource的地位,作用有初始化处理,获取连接处理等。
-
ConnectionPoolWithConcurrentValidation实现了ConnectionPool对象。
-
ConnectionPool对象开始创建连接,并初始化连接池。
-
List<XPooledConnection>则是Atomikos的连接池的设计。
-
XPooledConnection为暴露给客户端使用的连接对象,包装Druid创建的XAConnection对象。
-
DruidXADataSource则是Druid交给Atomikos管理的XA对象了,该对象会创建Druid的XAConnection对象。
-
DruidPooledXAConnection为DruidPooledConnection对象和XAConnection的包装。
3 Druid XA相关对象
Atomikos Connection相关对象依赖关系可以看出底层的DruidXADataSource和DruidPooledXAConnection由Druid提供,接下来再来看出Druid的XA相关对象的依赖关系如下:
-
DruidXADataSource是Druid交给Atomikos管理的XA数据源对象,该对象会创建Druid的DruidPooledXAConnection对象。
-
DruidPooledXAConnection为DruidPooledConnection对象和XAConnection的包装。
-
DruidPooledConnection为客户端获取Druid连接提供出去的连接对象的封装,关闭以及创建PreparedStatement都由该对象提供。
-
DruidConnectionHolder[]为连接池,每次创建的DruidConnectionHolder的连接的对象都会缓存到该数据处理,DruidDataSource.init()执行后会先初始化所有连接对象放到该数组。客户端要获取连接会从该数组(池子)里获取,回收线程会循环扫描该数组里是否已存在待回收的对象进行回收。如果识别到连接为空或者不够最小连接会开启创建连接线程创建连接后放入该池子。
-
DruidConnectionHolder是Druid对Connection、Datasource、PreparedStatementPool等封装的对象。
-
Connection是java.sql提供的连接对象。
-
XAConnection是javax.sql提供的XA连接对象。
-
XAResource是javax.transaction.xa提供的事务管理对象,由于我们连接用的是MySql数据库,所以将会由MySql提供XA API方法完成整个的XA事务控制,即两阶段的控制提交。
4 MySql XAResource对象分析
接下来就是分析MySql如何进行XA事务的控制了,整个执行的流程如下:
-
start()方法启动一个XA事务,并把它置于ACTIVE状态。
-
excuteSql则会执行构成事务的多条SQL语句,也就是指定分支事务的边界,然后执行end()方法。
-
end()方法会把事务放入IDLE状态,也就是结束事务边界,在xa start和xa end之间的语句就构成了本分支事务的一个事务范围。
-
prepare()方法对于一个IDLE状态XA事务,可以执行一个XA PREPARE语句把事务放入PREPARED状态,此时一阶段提交结束。
-
commit()方法对于处于PREPARED状态的XA事务可以进行提交操作。
-
rollback()方法对于处于PREPARED状态的XA事务如果出错可以进行回滚操作。
这里贴一个代码示例进一步做下流程的说明(该示例来源:MySQL 中基于 XA 实现的分布式事务 - 简书):
public class XaDemo {
public static MysqlXADataSource getDataSource(String connStr, String user, String pwd) {
try {
MysqlXADataSource ds = new MysqlXADataSource();
ds.setUrl(connStr);
ds.setUser(user);
ds.setPassword(pwd);
return ds;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] arg) {
String connStr1 = "jdbc:mysql://192.168.0.1:3306/test";
String connStr2 = "jdbc:mysql://192.168.0.2:3306/test";
try {
//从不同数据库获取数据库数据源
MysqlXADataSource ds1 = getDataSource(connStr1, "root", "123456");
MysqlXADataSource ds2 = getDataSource(connStr2, "root", "123456");
//数据库1获取连接
XAConnection xaConnection1 = ds1.getXAConnection();
XAResource xaResource1 = xaConnection1.getXAResource();
Connection connection1 = xaConnection1.getConnection();
Statement statement1 = connection1.createStatement();
//数据库2获取连接
XAConnection xaConnection2 = ds2.getXAConnection();
XAResource xaResource2 = xaConnection2.getXAResource();
Connection connection2 = xaConnection2.getConnection();
Statement statement2 = connection2.createStatement();
//创建事务分支的xid
Xid xid1 = new MysqlXid(new byte[] { 0x01 }, new byte[] { 0x02 }, 100);
Xid xid2 = new MysqlXid(new byte[] { 0x011 }, new byte[] { 0x012 }, 100);
try {
//事务分支1关联分支事务sql语句
xaResource1.start(xid1, XAResource.TMNOFLAGS);
int update1Result = statement1.executeUpdate("update account_from set money=money - 50 where id=1");
xaResource1.end(xid1, XAResource.TMSUCCESS);
//事务分支2关联分支事务sql语句
xaResource2.start(xid2, XAResource.TMNOFLAGS);
int update2Result = statement2.executeUpdate("update account_to set money= money + 50 where id=1");
xaResource2.end(xid2, XAResource.TMSUCCESS);
// 两阶段提交协议第一阶段
int ret1 = xaResource1.prepare(xid1);
int ret2 = xaResource2.prepare(xid2);
// 两阶段提交协议第二阶段
if (XAResource.XA_OK == ret1 && XAResource.XA_OK == ret2) {
xaResource1.commit(xid1, false);
xaResource2.commit(xid2, false);
System.out.println("reslut1:" + update1Result + ", result2:" + update2Result);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}