介紹
JTA和JTS
Java事務API(JTA:Java Transaction API)和它的同胞Java事務服務(JTS:Java Transaction Service),為J2EE平台提供了分散式事務服務(distributed transaction)。
一個分散式事務(distributed transaction)包括一個事務管理器(transaction manager)和一個或多個資源管理器(resource manager)。
一個資源管理器(resource manager)是任意類型的持久化數據存儲。
事務管理器(transaction manager)承擔著所有事務參與單元者的相互通訊的責任。
JTA與JDBC
JTA事務比JDBC事務更強大。一個JTA事務可以有多個參與者,而一個JDBC事務則被限定在一個單一的資料庫連線。下列任一個Java平台的組件都可以參與到一個JTA事務中:JDBC連線、JDO PersistenceManager 對象、JMS 佇列、JMS 主題、企業JavaBeans(EJB)、一個用J2EE Connector Architecture 規範編譯的資源分配器。
劃分
要用JTA來劃分一個事務,應用程式調用javax.transaction.UserTransaction接口中的方法。
示例4顯示了一個典型的JNDI搜尋的UseTransaction對象。
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup(";java:comp/UserTransaction";);
UserTransaction utx = (UserTransaction) txObj;
應用程式有了UserTransaction對象的引用之後,就可以象示例5那樣來起動事務。
utx.begin(); // ... DataSource ds = obtainXADataSource(); Connection conn = ds.getConnection(); pstmt = conn.prepareStatement(";UPDATE MOⅥES ...";); pstmt.setString(1, ";Spinal Tap";); pstmt.executeUpdate(); // ... utx.commit(); // ... |
當應用程式調用commit()時,事務管理器使用兩段提交協定來結束事務。
JTA事務控制的方法:
.javax.transaction.UserTransaction接口提供
使用
應用程式調用begin()來起動事務,即可調用commit()也可以調用rollback()來結束事務。
開發人員經常使用JDBC來作為DAO類中的底層數據操作。
如果計畫使用JTA來劃分事務,你將需要一個實現了javax.sql.XADataSource,javax.sql.XAConnection和javax.sql.XAResource接口JDBC的驅動。
實現了這些接口的驅動將有能力參與到JTA事務中。
一個XADataSource對象是一個XAConnection對象的工廠。XAConnections是參與到JTA事務中的連線。你需要使用應用程式伺服器管理工具來建立XADataSource對象。
特殊指令
對於特殊的指令請參考應用程式伺服器文檔和JDBC驅動文檔。
J2EE應用程式使用JNDI來查找數據源。
一旦應用程式有了一個數據源對象的引用,這會調用javax.sql.DataSource.getConnection()來獲得資料庫的連線。
連線
XA連線區別於非XA連線。要記住的是XA連線是一個JTA事務中的參與者。這就意味著XA連線不支持JDBC的自動提交特性。也就是說應用程式不必在XA連線上調用java.sql.Connection.commit()或java.sql.Connection.rollback()。相反,應用程式應該使用UserTransaction.begin()、UserTransaction.commit()和UserTransaction.rollback().
DAO類總結
我們已經討論了JDBC和JTA是怎樣劃分事務的。每一種方法都有它的優點,因此你需要決定為你的應用程式選擇一個最適應的方法。在我們團隊許多最近的對於事務劃分的項目中使用JDBC API來創建DAO類。
這DAO類總結如下:
⒈事務劃分代碼被嵌入到DAO類內部
⒉DAO類使用JDBC API來進行事務劃分
⒊調用者沒有劃分事務的方法
⒋事務範圍被限定在一個單一的JDBC連線
應用程式
JDBC事務對複雜的企業應用程式不總是有效的。如果你的事務將跨越多個DAO對象或多個資料庫,那么下面的實現策略可能會更恰當:
⒈用JTA對事務進行劃分
⒉事務劃分代碼被DAO分開
⒊調用者承擔劃分事務的責任
⒋DAO參與一個全局的事務中
JDBC方法由於它的簡易性而具有吸引力,JTA方法提供了更多靈活性。你選擇什麼樣的實現將依賴於你的應用程式的特定需求。
JTA(Java Transaction API) 為 J2EE 平台提供了分散式事務服務。
要用 JTA 進行事務界定,應用程式要調用 javax.transaction.UserTransaction 接口中的方法。例如:
utx.begin();
// ...
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement("UPDATE MOⅥES ...");
pstmt.setString(1,"Spinal Tap");
pstmt.executeUpdate();
// ...
utx.commit();
注意
讓我們來關注下面的話:
“用 JTA 界定事務,那么就需要有一個實現 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驅動程式。一個實現了這些接口的驅動程式將可以參與 JTA 事務。一個 XADataSource 對象就是一個 XAConnection 對象的工廠。XAConnection s 是參與 JTA 事務的 JDBC 連線。”
要使用JTA事務,必須使用XADataSource來產生資料庫連線,產生的連線為一個XA連線。
XA連線(javax.sql.XAConnection)和非XA(java.sql.Connection)連線的區別在於:XA可以參與JTA的事務,而且不支持自動提交。
Note:
Oracle,Sybase,DB2,SQL Server等大型資料庫才支持XA,支持分布事務。
My SQL 連本地都支持不好,更別說分布事務了。
MySql 在5.0的版本後增加了對xa的支持
實例
用XADataSource產生的XAConnection它擴展了一個getXAResource()方法,事務通過這個方法把它加入到事務容器中進行管理.對於調用者來說,根本看不到事務是如何管理的,你只要聲明開始事務,告訴容器我下面的操作要求事務參與了,最後告訴事務說到這兒可以提交或回滾了,別的都是暗箱操作。
首先,實現一個Xid類用來標識事務:
在使用JTA之前,你必須首先實現一個Xid類用來標識事務(在普通情況下這將由事務管理程式來處理)。Xid包含三個元素:formatID、gtrid(全局事務標識符)和bqual(分支修飾詞標識符)。
下面的例子說明Xid的實現:
import javax.transaction.xa.*;
public class MyXid implements Xid
{
protected int formatId;
protected byte gtrid[];
protected byte 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[] getBranchQualifier()
{
return bqual;
}
public byte[] getGlobalTransactionId()
{
return gtrid;
}
}
其次,創建數據源:
其次,你需要創建一個你要使用的資料庫的數據源:
public DataSource getDataSource()
throws SQLException
{
SQLServerDataSource xaDS = new
com.merant.datadirect.jdbcx.sqlserver.SQLServerDataSource();
xaDS.setDataSourceName("SQLServer");
xaDS.setServerName("server");
xaDS.setPortNumber(1433);
xaDS.setSelectMethod("cursor");
return xaDS;
}
例1“這個例子是用“兩步提交協定”來提交一個事務分支:
XADataSource xaDS;
XAConnection xaCon;
XAResource xaRes;
Xid xid;
Connection con;
Statement stmt;
int ret;
xaDS = getDataSource();
xaCon = xaDS.getXAConnection("jdbc_user","jdbc_password");
xaRes = xaCon.getXAResource();
con = xaCon.getConnection();
stmt = con.createStatement();
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);
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();
}
因為所有這些例子中的初始化代碼相同或者非常相似,僅僅是一些重要的地方的代碼由不同。
例2:這個例子,與例1相似,說明了一個返回過程:
xaRes.start(xid,XAResource.TMNOFLAGS);
stmt.executeUpdate("insert into test_table values (100)");
xaRes.end(xid,XAResource.TMSUCCESS);
ret = xaRes.prepare(xid);
if (ret == XAResource.XA_OK) {
xaRes.rollback(xid);
}
例3:這個例子說明一個分散式事務分支如何中止,讓相同的連線做本地事務處理,以及它們稍後該如何繼續這個分支。分散式事務的兩步提交作用不影響本地事務。
xid = new MyXid(100,new byte[]{0x01},new byte[]{0x02});
xaRes.start(xid,XAResource.TMNOFLAGS);
stmt.executeUpdate("insert into test_table values (100)");
xaRes.end(xid,XAResource.TMSUSPEND);
這個更新在事務範圍之外完成,所以它不受XA返回影響。
stmt.executeUpdate("insert into test_table2 values (111)");
xaRes.start(xid,XAResource.TMRESUME);
stmt.executeUpdate("insert into test_table values (200)");
xaRes.end(xid,XAResource.TMSUCCESS);
ret = xaRes.prepare(xid);
if (ret == XAResource.XA_OK) {
xaRes.rollback(xid);
}
例4:這個例子說明一個XA資源如何分擔不同的事務。創建了兩個事務分支,但是它們不屬於相同的分散式事務。JTA允許XA資源在第一個分支上做一個兩步提交,雖然這個資源仍然與第二個分支相關聯。
xid1 = new MyXid(100,new byte[]{0x01},new byte[]{0x02});
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);
ret = xaRes.prepare(xid1);
if (ret == XAResource.XA_OK) {
xaRes.commit(xid2,false);
}
stmt.executeUpdate("insert into test_table2 values (200)");
xaRes.end(xid2,XAResource.TMSUCCESS);
ret = xaRes.prepare(xid2);
if (ret == XAResource.XA_OK) {
xaRes.rollback(xid2);
}
例5:這個例子說明不同的連線上的事務分支如何連線成為一個單獨的分支,如果它們連線到相同的資源管理程式。這個特點改善了分散式事務的效率,因為它減少了兩步提交處理的數目。兩個連線到資料庫伺服器上的XA將被創建。每個連線創建它自己的XA資源,正規的JDBC連線和語句。在第二個XA資源開始一個事務分支之前,它將察看是否使用和第一個XA資源使用的是同一個資源管理程式。如果這是實例,它將加入在第一個XA連線上創建的第一個分支,而不是創建一個新的分支。稍後,這個事務分支使用XA資源來準備和提交。
xaDS = getDataSource();
xaCon1 = xaDS.getXAConnection("jdbc_user","jdbc_password");
xaRes1 = xaCon1.getXAResource();
con1 = xaCon1.getConnection();
stmt1 = con1.createStatement();
xid1 = new MyXid(100,new byte[]{0x01},new byte[]{0x02});
xaRes1.start(xid1,XAResource.TMNOFLAGS);
stmt1.executeUpdate("insert into test_table1 values (100)");
xaRes1.end(xid,XAResource.TMSUCCESS);
xaCon2 = xaDS.getXAConnection("jdbc_user","jdbc_password");
xaRes2 = xaCon1.getXAResource();
con2 = xaCon1.getConnection();
stmt2 = con1.createStatement();
if (xaRes2.isSameRM(xaRes1)) {
xaRes2.start(xid1,XAResource.TMJOIN);
stmt2.executeUpdate("insert into test_table2 values (100)");
xaRes2.end(xid1,XAResource.TMSUCCESS);
}
else {
xid2 = new MyXid(100,new byte[]{0x01},new byte[]{0x03});
xaRes2.start(xid2,XAResource.TMNOFLAGS);
stmt2.executeUpdate("insert into test_table2 values (100)");
xaRes2.end(xid2,XAResource.TMSUCCESS);
ret = xaRes2.prepare(xid2);
if (ret == XAResource.XA_OK) {
xaRes2.commit(xid2,false);
}
}
ret = xaRes1.prepare(xid1);
if (ret == XAResource.XA_OK) {
xaRes1.commit(xid1,false);
}
例6:這個例子說明在錯誤恢復的階段,如何恢復準備好的或者快要完成的事務分支。它首先試圖返回每個分支;如果它失敗了,它嘗試著讓資源管理程式丟掉關於事務的訊息。
MyXid[] xids;
xids = xaRes.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
for (int i=0; xids!=null && i try {
xaRes.rollback(xids[i]);
}
catch (XAException ex) {
try {
xaRes.forget(xids[i]);
}
catch (XAException ex1) {
System.out.println("rollback/forget failed: " + ex1.errorCode);
}
}
}