JTA (Java Transaction API) 讓應用程式能執行分散式事務——在兩個或多個網路資源上原子性地存取與更新資料。JDBC 驅動的 JTA 支援極大增強了資料存取能力。本文提供 JTA 高層概述、分散式事務處理 (DTP) 模型組件、XAResource 介面實作與程式碼範例。
本地事務:應用 → JDBC 驅動 → 單一資料庫。
引入應用伺服器:增加操作層。
分散式事務:跨多個資源管理器 (多資料庫/多實例),需事務管理器協調,採用兩階段提交協定 (2PC)。
| 介面 | 套件 | 角色 | 使用者 |
|---|---|---|---|
UserTransaction | javax.transaction | 應用程式以程式化方式控制事務邊界 | 應用開發者 |
TransactionManager | javax.transaction | 應用伺服器代表受管應用控制事務範圍 | 應用伺服器廠商 |
XAResource | javax.transaction.xa | X/Open XA 協定的 Java 映射,資源管理器參與事務的介面 | JDBC 驅動開發者 |
⚠️ 應用程式碼禁忌:在分散式事務範圍內,絕不可呼叫 Connection.commit()、rollback()、setAutoCommit(true),否則破壞事務基礎結構管理。
前置條件:驅動需實作 JDBC 2.0 Optional Package (XADataSource, XAConnection) 與 JTA XAResource。應用伺服器需提供 DataSource 實作與連線池。
public class MyXid implements Xid {
protected int formatId; protected byte[] gtrid, bqual;
public MyXid() {}
public MyXid(int formatId, byte[] gtrid, byte[] bqual) {
this.formatId = formatId; this.gtrid = gtrid; this.bqual = bqual;
}
public int getFormatId() { return formatId; }
public byte[] getGlobalTransactionId() { return gtrid; }
public byte[] getBranchQualifier() { return bqual; }
}
formatId:0 表示 OSI CCR 命名;>0 自定義格式;-1 無效gtrid (全域事務 ID) + bqual (分支修飾詞) 各 64 bytes,需全域唯一XADataSource xaDS = getDataSource();
XAConnection xaCon = xaDS.getXAConnection("user", "pass");
XAResource xaRes = xaCon.getXAResource();
Connection con = xaCon.getConnection();
Statement stmt = con.createStatement();
Xid xid = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
try {
xaRes.start(xid, XAResource.TMNOFLAGS);
stmt.executeUpdate("INSERT INTO test_table VALUES (100)");
xaRes.end(xid, XAResource.TMSUCCESS);
int ret = xaRes.prepare(xid);
if (ret == XAResource.XA_OK) xaRes.commit(xid, false);
} catch (XAException e) { e.printStackTrace(); }
finally { stmt.close(); con.close(); xaCon.close(); }
// ... start/end 同範例1 ...
int ret = xaRes.prepare(xid);
if (ret == XAResource.XA_OK) xaRes.rollback(xid);
xaRes.start(xid, XAResource.TMNOFLAGS);
stmt.executeUpdate("INSERT INTO test_table VALUES (100)");
xaRes.end(xid, XAResource.TMSUSPEND); // 暫停分散式事務
stmt.executeUpdate("INSERT INTO test_table2 VALUES (111)"); // 本地事務,不受 XA 影響
xaRes.start(xid, XAResource.TMRESUME); // 恢復分散式事務
stmt.executeUpdate("INSERT INTO test_table VALUES (200)");
xaRes.end(xid, XAResource.TMSUCCESS);
xaRes.prepare(xid);
xaRes.rollback(xid);
Xid xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
Xid xid2 = new MyXid(100, new byte[]{0x11}, new byte[]{0x22});
xaRes.start(xid1, XAResource.TMNOFLAGS);
stmt.executeUpdate("INSERT INTO test_table1 VALUES (100)");
xaRes.end(xid1, XAResource.TMSUCCESS);
xaRes.start(xid2, XAResource.TMNOFLAGS);
xaRes.prepare(xid1);
xaRes.commit(xid2, false); // 先提交 xid2
stmt.executeUpdate("INSERT INTO test_table2 VALUES (200)");
xaRes.end(xid2, XAResource.TMSUCCESS);
xaRes.prepare(xid2);
xaRes.rollback(xid2);
// 連線 1
XAConnection xaCon1 = xaDS.getXAConnection(...);
XAResource xaRes1 = xaCon1.getXAResource();
Xid xid1 = new MyXid(...);
xaRes1.start(xid1, XAResource.TMNOFLAGS);
stmt1.executeUpdate("INSERT INTO test_table1 VALUES (100)");
xaRes1.end(xid1, XAResource.TMSUCCESS);
// 連線 2 - 檢查是否同一資源管理器
XAConnection xaCon2 = xaDS.getXAConnection(...);
XAResource xaRes2 = xaCon2.getXAResource();
if (xaRes2.isSameRM(xaRes1)) { // 同一 RM → 加入既有分支
xaRes2.start(xid1, XAResource.TMJOIN);
stmt2.executeUpdate("INSERT INTO test_table2 VALUES (100)");
xaRes2.end(xid1, XAResource.TMSUCCESS);
} else { /* 建立新分支 */ }
xaRes1.prepare(xid1);
xaRes1.commit(xid1, false);
Xid[] xids = xaRes.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
for (Xid xid : xids) {
try { xaRes.rollback(xid); }
catch (XAException ex) {
try { xaRes.forget(xid); } // 放棄無法回滾的分支
catch (XAException ex1) { System.out.println("rollback/forget failed: " + ex1.errorCode); }
}
}
XADataSource, XAConnection, XAResourceDataSource、連線池、整合事務管理器企業級應用建議使用成熟應用伺服器 (WebLogic, WebSphere, JBoss 等) 內建事務管理器,而非自行實作應用層事務協調邏輯。