示例参考:github上seata-sample
业务代码层面和xa完全相同,仅数据库代理层面替换成DataSourceProxyXA即可,更多内容,参考示例。
此过程和AT模式一样,使用@GlobalTransactional即可。
因为DataSource使用了代理,所以所有DB操作均交个DataSourceProxyXA完成,当执行db操作时,请求将会由ExecuteTemplateXA执行。
在以下代码中我们可以看到,其主要逻辑如下:
public static T execute(AbstractConnectionProxyXA connectionProxyXA, StatementCallback statementCallback, S targetStatement, Object... args) throws SQLException {
boolean autoCommitStatus = connectionProxyXA.getAutoCommit();
if (autoCommitStatus) {
// XA Start
connectionProxyXA.setAutoCommit(false);
}
try {
T res = null;
try {
// execute SQL
res = statementCallback.execute(targetStatement, args);
} catch (Throwable ex) {
if (autoCommitStatus) {
// XA End & Rollback
try {
connectionProxyXA.rollback();
} catch (SQLException sqle) {
// log and ignore the rollback failure.
LOGGER.warn(
"Failed to rollback xa branch of " + connectionProxyXA.xid +
"(caused by SQL execution failure(" + ex.getMessage() + ") since " + sqle.getMessage(),
sqle);
}
}
if (ex instanceof SQLException) {
throw ex;
} else {
throw new SQLException(ex);
}
}
if (autoCommitStatus) {
try {
// XA End & Prepare
connectionProxyXA.commit();
} catch (Throwable ex) {
LOGGER.warn(
"Failed to commit xa branch of " + connectionProxyXA.xid + ") since " + ex.getMessage(),
ex);
// XA End & Rollback
if (!(ex instanceof SQLException) || !AbstractConnectionProxyXA.SQLSTATE_XA_NOT_END.equalsIgnoreCase(((SQLException) ex).getSQLState())) {
try {
connectionProxyXA.rollback();
} catch (SQLException sqle) {
// log and ignore the rollback failure.
LOGGER.warn(
"Failed to rollback xa branch of " + connectionProxyXA.xid +
"(caused by commit failure(" + ex.getMessage() + ") since " + sqle.getMessage(),
sqle);
}
}
if (ex instanceof SQLException) {
throw ex;
} else {
throw new SQLException(ex);
}
}
}
return res;
} finally {
if (autoCommitStatus) {
connectionProxyXA.setAutoCommit(true);
}
}
}
public void setAutoCommit(boolean autoCommit) throws SQLException {
if (currentAutoCommitStatus == autoCommit) {
return;
}
if (autoCommit) {
// According to JDBC spec:
// If this method is called during a transaction and the
// auto-commit mode is changed, the transaction is committed.
if (xaActive) {
commit();
}
} else {
if (xaActive) {
throw new SQLException("should NEVER happen: setAutoCommit from true to false while xa branch is active");
}
// Start a XA branch
long branchId = 0L;
try {
// 1. register branch to TC then get the branchId
branchId = DefaultResourceManager.get().branchRegister(BranchType.XA, resource.getResourceId(), null, xid, null,
null);
} catch (TransactionException te) {
cleanXABranchContext();
throw new SQLException("failed to register xa branch " + xid + " since " + te.getCode() + ":" + te.getMessage(), te);
}
// 2. build XA-Xid with xid and branchId
this.xaBranchXid = XAXidBuilder.build(xid, branchId);
try {
// 3. XA Start
xaResource.start(this.xaBranchXid, XAResource.TMNOFLAGS);
} catch (XAException e) {
cleanXABranchContext();
throw new SQLException("failed to start xa branch " + xid + " since " + e.getMessage(), e);
}
// 4. XA is active
this.xaActive = true;
}
currentAutoCommitStatus = autoCommit;
}
public void commit() throws SQLException {
if (currentAutoCommitStatus) {
// Ignore the committing on an autocommit session.
return;
}
if (!xaActive || this.xaBranchXid == null) {
throw new SQLException("should NOT commit on an inactive session", SQLSTATE_XA_NOT_END);
}
try {
// XA End: Success
xaResource.end(xaBranchXid, XAResource.TMSUCCESS);
// XA Prepare
xaResource.prepare(xaBranchXid);
// Keep the Connection if necessary
keepIfNecessary();
} catch (XAException xe) {
try {
// Branch Report to TC: Failed
DefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(),
BranchStatus.PhaseOne_Failed, null);
} catch (TransactionException te) {
LOGGER.warn("Failed to report XA branch commit-failure on " + xid + "-" + xaBranchXid.getBranchId()
+ " since " + te.getCode() + ":" + te.getMessage() + " and XAException:" + xe.getMessage());
}
throw new SQLException(
"Failed to end(TMSUCCESS)/prepare xa branch on " + xid + "-" + xaBranchXid.getBranchId() + " since " + xe
.getMessage(), xe);
} finally {
cleanXABranchContext();
}
}
public void rollback() throws SQLException {
if (currentAutoCommitStatus) {
// Ignore the committing on an autocommit session.
return;
}
if (!xaActive || this.xaBranchXid == null) {
throw new SQLException("should NOT rollback on an inactive session");
}
try {
// Branch Report to TC
DefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(), BranchStatus.PhaseOne_Failed, null);
} catch (TransactionException te) {
// log and ignore the report failure
LOGGER.warn("Failed to report XA branch rollback on " + xid + "-" + xaBranchXid.getBranchId()
+ " since " + te.getCode() + ":" + te.getMessage());
}
try {
// XA End: Fail
xaResource.end(xaBranchXid, XAResource.TMFAIL);
} catch (XAException xe) {
throw new SQLException(
"Failed to end(TMFAIL) xa branch on " + xid + "-" + xaBranchXid.getBranchId() + " since " + xe
.getMessage(), xe);
} finally {
cleanXABranchContext();
}
}
这个过程和AT模式完全相同。
这个过程和AT模式完全相同。
AbstractRMHandler收到请求后,最终交给ResourceManagerXA处理,具体的Commit和Rollback交个xaCommit、xaRollback完成,他们直接触发xaResource的commit、rollback方法。
public void xaCommit(String xid, long branchId, String applicationData) throws XAException {
XAXid xaXid = XAXidBuilder.build(xid, branchId);
xaResource.commit(xaXid, false);
releaseIfNecessary();
}
public void xaRollback(String xid, long branchId, String applicationData) throws XAException {
XAXid xaXid = XAXidBuilder.build(xid, branchId);
xaResource.rollback(xaXid);
releaseIfNecessary();
}
异常补偿流程和AT模式完全相同。
从上面源码层面可以看到,Seata的XA模式是基于jdk的XAResource接口实现的。以下是jdk文档对此接口的描述。
XAResource 接口是基于X/Open CAE规范(分布式事务处理:XA 规范)的工业标准XA接口的Java映射。
在分布式事务处理 (DTP) 环境中,XA接口定义资源管理器和事务管理器之间的协定。JDBC驱动程序或JMS提供者实现此接口,以支持全局事务与数据库或消息服务连接之间的关联。
可由应用程序在外部事务管理器控制事务的环境中使用的任何事务资源均可支持XAResource接口。数据库管理系统就属于此类资源。应用程序可以通过多个数据库连接访问数据。通过事务管理器将每个数据库连接作为事务资源添加到列表中。事务管理器为参与全局事务的每个连接获取XAResource。事务管理器使用start方法建立全局事务与资源之间的关联,而使用end方法取消事务与资源之间的关联。资源管理器负责将全局事务关联到在start与end方法调用之间对其数据执行的所有工作。
在事务提交时,事务管理器通知资源管理器根据二阶段提交协议准备、提交或回滚事务。
返回结果 | 方法签名 | 描述 |
---|---|---|
void | commit(Xid xid, boolean onePhase) | 提交 xid 指定的全局事务。 |
void | end(Xid xid, int flags) | 终止代表事务分支所执行的工作。 |
void | forget(Xid xid) | 告知资源管理器忽略以启发式完成的事务分支。 |
int | getTransactionTimeout() | 获取为此 XAResource 实例设置的当前事务超时值。 |
boolean | isSameRM(XAResource xares) | 调用此方法,以确定目标对象表示的资源管理器实例是否与参数xares表示的资源管理器实例相同。 |
int | prepare(Xid xid) | 请求资源管理器准备好 xid 中指定的事务的事务提交工作。 |
Xid[] | recover(int flag) | 从资源管理器获取准备的事务分支的列表。 |
void | rollback(Xid xid) | 通知资源管理器回滚代表事务分支执行的工作。 |
boolean | setTransactionTimeout(int seconds) | 为此 XAResource 实例设置当前事务超时值。 |
void | start(Xid xid, int flags) | 代表 xid 中指定的事务分支开始工作。 |
类型 | 属性 | 描述 |
---|---|---|
static int | TMENDRSCAN | 终止恢复扫描。 |
static int | TMFAIL | 取消关联调用者,并将事务分支标记为只回滚。 |
static int | TMJOIN | 调用者正连接现有事务分支。 |
static int | TMNOFLAGS | 使用 TMNOFLAGS 指示不选择任何标志值。 |
static int | TMONEPHASE | 调用者正在使用一阶段优化。 |
static int | TMRESUME | 调用者正在恢复与挂起的事务分支的关联。 |
static int | TMSTARTRSCAN | 启动恢复扫描。 |
static int | TMSUCCESS | 取消调用者与事务分支的关联。 |
static int | TMSUSPEND | 调用者正挂起(不是终止)其与事务分支的关联。 |
static int | XA_OK | 事务工作正常准备就绪。 |
static int | XA_RDONLY | 事务分支是只读的,并且已提交。 |
Seata XA 模式
Seata 原理
Seata-AT模式 原理
Seata-TCC模式 原理
Seata-Saga模式 原理
Seata-XA模式 原理
TCC-Transaction原理