Microsoft JDBC Driver for SQL Server 提供對 Java 平台企業版/JDBC 2.0 選擇性分散式交易的支援。 從 SQLServerXADataSource 類別取得的 JDBC 連線可參與標準分散式交易處理環境,例如 Java Platform Enterprise Edition (Java EE) 應用程式伺服器。
在此文章中,XA 代表延伸架構。
警告
Microsoft JDBC Driver 4.2 版及更新版本的 SQL Server 驅動程式,為現有的未準備交易自動回復功能新增了逾時選項。 如需更多詳細資訊,請參閱本主題稍後的設定伺服器端逾時設定,以便自動回復未準備的交易。
備註
分散式交易實作的類別如下:
| 類別 | 實作 | 描述 |
|---|---|---|
| com.microsoft.sqlserver.jdbc.SQLServerXADataSource | javax.sql.XADataSource | 用於分散式連線的類別工廠。 |
| com.microsoft.sqlserver.jdbc.SQLServerXAResource | javax.transaction.xa.XAResource | 供交易管理員使用的資源配接器。 |
注意
XA 分散式交易連接會預設為「讀取認可」隔離等級。
使用 XA 交易時的指導方針和限制
下列額外指導方針適用於緊密繫結的交易:
當您將 XA 交易與 Microsoft Distributed Transaction Coordinator (MS DTC) 一起使用時,可能會注意到目前版本的 MS DTC 不支援緊密繫結的 XA 分支行為。 例如,MS DTC 在 XA 分支交易識別碼 (XID) 與 MS DTC 交易識別碼之間擁有一對一的對應,而且由鬆散繫結之 XA 分支所執行的工作會彼此隔離。
MS DTC 支援緊密耦合的 XA 分支,將多個具有相同全域交易 ID(GTRID)的 XA 分支映射到單一的 MS DTC 交易 ID。 此支援讓多個緊密結合的 XA 分支可以在資源管理員 (例如 SQL Server) 中查看彼此的變更。
SSTRANSTIGHTLYCPLD 旗標允許應用程式使用緊密結合的 XA 交易,這些交易的 XA 分支交易識別碼 (BQUAL) 各不相同,但具有相同的全域交易識別碼 (GTRID) 和格式識別碼 (FormatID)。 若要使用該功能,您必須在 XAResource.start 方法的 flags 參數上,設定 SSTRANSTIGHTLYCPLD:
xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);當多個緊密耦合的 XA 分支參與同一全域交易時,微軟 DTC 不支援單階段提交優化。 將
onePhase設為true時,XAResource.commit會因XAER_PROTO(-6)而失敗。 由於XAResource.prepare會對加入現有全域交易的分支傳回XA_RDONLY,因此交易管理員可以判定僅剩一個分支,並選擇單階段最佳化。 請將交易管理器設定為使用兩階段提交來處理這些交易,或改用鬆耦合分支。
設定指示
如果要將 XA 資料來源與 Microsoft 分散式交易協調器 (MS DTC) 搭配使用,來處理分散式交易,則需要下列步驟。 高階步驟為:
- 確定 MS DTC 服務正在執行並自動啟動。
- 設定伺服器端元件。
- 設定伺服器端逾時 (選用)。
- 授與使用者的存取權。
執行 MS DTC 服務
應該在服務管理員中將 MS DTC 服務標示為自動,以確定啟動 SQL Server 服務時,便執行該服務。 若要啟用 MS DTC 以用於 XA 交易,您必須遵循下列步驟:
在 Windows Vista 及更新的版本上:
選取開始按鈕,在開始搜尋方塊中鍵入 dcomcnfg,然後按下 ENTER 開啟元件服務。 您也可以在 StartSearch 搜尋方塊中輸入
%windir%\system32\comexp.msc,以開啟 元件服務。依序展開 [元件服務]、[電腦]、[我的電腦],接著展開 [分散式交易協調器]。
以滑鼠右鍵按一下 本機 DTC,然後選取 內容。
選取本機 DTC 內容對話方塊中的安全性索引標籤。
選取啟用 XA 交易核取方塊,然後按一下確定。 此動作會導致 MS DTC 服務隨即重新啟動。
再次選取確定以關閉內容對話方塊,然後關閉元件服務。
停止然後重新啟動 SQL Server,以確保其與 MS DTC 變更保持同步。 (此步驟對於 SQL Server 2019 和 SQL Server 2017 CU 16 和更新版本而言是選擇性的。)
設定 JDBC 分散式交易元件
設定伺服器端元件的步驟會根據您的目標伺服器版本而有所不同。 若要檢查伺服器版本,請對伺服器執行查詢 SELECT @@VERSION 並檢視輸出。 針對 SQL Server 2017 累積更新 (CU) 16 和更新版本,請遵循 SQL Server 2017 CU16 和更高版本的指示。 針對較舊的 SQL Server 版本,請遵循 SQL Server 2017 CU15 及更早版本的指示。
SQL Server 2017 CU16 及更新版本
若要使用 JDBC 驅動程式啟用必要元件以執行 XA 分散式交易,請執行下列預存程序。
EXEC sp_sqljdbc_xa_install
若要停用元件,請執行下列預存程序。
EXEC sp_sqljdbc_xa_uninstall
略過前往設定已取消準備交易的自動回復之伺服器端逾時設定章節。
SQL Server 2017 CU15 和更低版本
注意
這僅適用於 SQL Server 2017 CU15 和更低版本。 sqljdbc_xa.dll 所提供的功能已包含在 SQL Server 2017 CU16 和更新版本中。
當連線至 SQL Server 2017 CU16 或更高版本時,使用 JDBC Driver 13.6 及更新版本的 XA 功能則不需要這些元件。 因此,驅動程式套件不再包含sqljdbc_xa目錄及其xa_install.sql與 sqljdbc_xa.dll 元件。 這些舊有指令僅適用於 13.6 之前的驅動程式套件。
您可以遵循下列步驟,來設定 JDBC 驅動程式分散式交易元件:
將新的 sqljdbc_xa.dll 從 JDBC 驅動程式安裝目錄複製至將參與分散式交易之每一部 SQL Server 電腦的 Binn 目錄。
注意
如果您搭配使用 XA 交易與 32 位元 SQL Server (僅適用於 SQL Server 2014 或更舊版本),則即使 SQL Server 安裝在 x64 處理器上,也請使用 x86 資料夾中的 sqljdbc_xa.dll 檔案。 如果您在 x64 處理器上搭配使用 XA 交易與 64 位元 SQL Server,則請使用 x64 資料夾中的 sqljdbc_xa.dll 檔案。
在每個將參與分散式交易的 SQL Server 執行個體上執行資料庫指令碼 xa_install.sql。 這個指令碼就會安裝 sqljdbc_xa.dll 所呼叫的擴充預存程序。 這些擴充預存程序會實作適用於 SQL Server 的 Microsoft JDBC 驅動程式的分散式交易和 XA 支援。 您需要以 SQL Server 執行個體管理員身分來執行此指令碼。
若要授與權限給特定使用者以使用 JDBC Driver 參與分散式交易,請將該使用者新增至 SqlJDBCXAUser 角色。
您一次只能針對每個 SQL Server 執行個體設定單一 sqljdbc_xa.dll 組件版本。 應用程式可能需要使用不同的 JDBC 驅動程式版本,透過使用 XA 連接來連接至相同的 SQL Server 執行個體。 在該情況下,您就必須在 SQL Server 執行個體上安裝隨附於最新 JDBC Driver 的 sqljdbc_xa.dll。
有三種方式可確認目前安裝在 SQL Server 執行個體上的 sqljdbc_xa.dll 版本:
開啟將參與分散式交易之 SQL Server 電腦的 LOG 目錄。 選取並開啟 SQL Server "ERRORLOG" 檔案。 在「ERRORLOG」檔案中搜尋「Using 'SQLJDBC_XA.dll' version ...」字串。
開啟將參與分散式交易之 SQL Server 電腦的 Binn 目錄。 選取 sqljdbc_xa.dll 組件。
- 在 Windows Vista 或更新的版本上:以滑鼠右鍵按一下 sqljdbc_xa.dll,然後選取 [內容]。 然後選取詳細資料索引標籤。檔案版本欄位會顯示目前安裝在 SQL Server 執行個體上的 sqljdbc_xa.dll 版本。
依照下一節的程式碼範例所示,設定記錄功能。 在輸出記錄檔案中搜尋 "Server XA DLL version:..." 片語。
升級 sqljdbc_xa.dll
注意
這僅適用於 SQL Server 2017 CU15 和更低版本。 sqljdbc_xa.dll 所提供的功能已包含在 SQL Server 2017 CU16 和更新版本中。
對於 13.6 之前的驅動程式套件,升級驅動程式時請使用最新的可用 sqljdbc_xa.dll。 JDBC Driver 13.6 及以後版本沒有包含這個 DLL。
重要
您應利用維護時段,或沒有任何 MS DTC 交易正在進行時,升級 sqljdbc_xa.dll。
使用 Transact-SQL 命令來卸載 sqljdbc_xa.dll:
DBCC sqljdbc_xa (FREE)將新的 sqljdbc_xa.dll 從 JDBC 驅動程式安裝目錄複製至將參與分散式交易之每一部 SQL Server 電腦的 Binn 目錄。
當系統呼叫 sqljdbc_xa.dll 中的擴充程序時,就會載入新的 DLL。 您不需要重新啟動 SQL Server,即可載入新的定義。
設定已取消準備交易的自動回復之伺服器端逾時設定
有兩個登錄設定 (DWORD 值) 用來控制分散式交易的逾時行為:
XADefaultTimeout(以秒為單位):若使用者未指定任何逾時,此時所要使用的預設逾時值。 預設值是 0。XAMaxTimeout(以秒為單位):使用者可以設定的逾時最大值。 預設值是 0。
這些是 SQL Server 執行個體的特定設定,而且應該建立在下列登錄機碼底下:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout
注意
在 64 位元機器中執行的 32 位元 SQL Server (僅適用於 SQL Server 2014 或更舊版本),應在下列金鑰底下建立登錄設定:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\MSSQL<version>.<instance_name>\XATimeout
每筆交易在啟動時都會設定逾時值;如果逾時期限已到,SQL Server 就會復原該交易。 逾時時間取決於這些登錄機碼設定,以及使用者透過 XAResource.setTransactionTimeout() 指定的值。 這些逾時值解譯方式的幾個範例,如下所示:
,
表示不使用預設的逾時,在用戶端上將不強制執行最大逾時。 在此情況下,只有當用戶端使用 XAResource.setTransactionTimeout 設定逾時,交易才會有逾時。
,
表示如果用戶端未指定任何逾時,則所有交易都有 60 秒逾時。 如果用戶端指定了逾時,則會使用該逾時值。 未對逾時時間設最大值限制。
,
表示如果用戶端未指定任何逾時,則所有交易都有 30 秒逾時。 如果用戶端指定了任何逾時值,且該值小於 60 秒(最大值),則會使用用戶端的逾時值。
,
表示如果用戶端未指定任何逾時,則所有交易都有 30 秒逾時 (最大值)。 如果客戶端有指定任何逾時值,且該值小於 30 秒(最大值),則會使用客戶端指定的逾時值。
設定使用者定義角色
若要授與權限給特定使用者以使用 JDBC Driver 參與分散式交易,請將該使用者新增至 SqlJDBCXAUser 角色。 例如,使用下列 Transact-SQL 程式碼,將名為 'shelly' (名為 'shelly' 的 SQL 標準登入使用者) 的使用者新增至 SqlJDBCXAUser 角色:
USE master
GO
EXEC sp_grantdbaccess 'shelly', 'shelly'
GO
EXEC sp_addrolemember [SqlJDBCXAUser], 'shelly'
每一個資料庫都會定義 SQL 使用者自訂角色。 若要基於安全目的來建立您自己的角色,您必須在每一個資料庫中定義角色,然後在每一個資料庫中新增使用者。 SqlJDBCXAUser 角色嚴格定義於 master 資料庫中,因為它是用來授與位於 master 資料庫中的 SQL JDBC 延伸預存程序存取權限。 您必須先授與個別使用者存取 master 的權限,然後在您登入 master 資料庫時,再授與他們存取 SqlJDBCXAUser 角色的權限。
範例
import java.net.Inet4Address;
import java.sql.*;
import java.util.Random;
import javax.sql.XAConnection;
import javax.transaction.xa.*;
import com.microsoft.sqlserver.jdbc.*;
public class testXA {
public static void main(String[] args) throws Exception {
// Create variables for the connection string.
String prefix = "jdbc:sqlserver://";
String serverName = "localhost";
int portNumber = 1433;
String databaseName = "AdventureWorks";
String user = "UserName";
String password = "<password>";
String connectionUrl = prefix + serverName + ":" + portNumber + ";encrypt=true;databaseName=" + databaseName + ";user="
+ user + ";password=" + password;
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
try (Connection con = DriverManager.getConnection(connectionUrl); Statement stmt = con.createStatement()) {
stmt.executeUpdate("CREATE TABLE XAMin (f1 int, f2 varchar(max))");
}
// Create the XA data source and XA ready connection.
SQLServerXADataSource ds = new SQLServerXADataSource();
ds.setUser(user);
ds.setPassword(password);
ds.setServerName(serverName);
ds.setPortNumber(portNumber);
ds.setDatabaseName(databaseName);
XAConnection xaCon = ds.getXAConnection();
try (Connection con = xaCon.getConnection()) {
// Get a unique Xid object for testing.
XAResource xaRes = null;
Xid xid = null;
xid = XidImpl.getUniqueXid(1);
// Get the XAResource object and set the timeout value.
xaRes = xaCon.getXAResource();
xaRes.setTransactionTimeout(0);
// Perform the XA transaction.
System.out.println("Write -> xid = " + xid.toString());
xaRes.start(xid, XAResource.TMNOFLAGS);
PreparedStatement pstmt = con.prepareStatement("INSERT INTO XAMin (f1,f2) VALUES (?, ?)");
pstmt.setInt(1, 1);
pstmt.setString(2, xid.toString());
pstmt.executeUpdate();
// Commit the transaction.
xaRes.end(xid, XAResource.TMSUCCESS);
xaRes.commit(xid, true);
}
xaCon.close();
// Open a new connection and read back the record to verify that it worked.
try (Connection con = DriverManager.getConnection(connectionUrl); Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM XAMin")) {
rs.next();
System.out.println("Read -> xid = " + rs.getString(2));
stmt.executeUpdate("DROP TABLE XAMin");
}
}
}
class XidImpl implements Xid {
public int formatId;
public byte[] gtrid;
public byte[] bqual;
public byte[] getGlobalTransactionId() {
return gtrid;
}
public byte[] getBranchQualifier() {
return bqual;
}
public int getFormatId() {
return formatId;
}
XidImpl(int formatId, byte[] gtrid, byte[] bqual) {
this.formatId = formatId;
this.gtrid = gtrid;
this.bqual = bqual;
}
public String toString() {
int hexVal;
StringBuffer sb = new StringBuffer(512);
sb.append("formatId=" + formatId);
sb.append(" gtrid(" + gtrid.length + ")={0x");
for (int i = 0; i < gtrid.length; i++) {
hexVal = gtrid[i] & 0xFF;
if (hexVal < 0x10)
sb.append("0" + Integer.toHexString(gtrid[i] & 0xFF));
else
sb.append(Integer.toHexString(gtrid[i] & 0xFF));
}
sb.append("} bqual(" + bqual.length + ")={0x");
for (int i = 0; i < bqual.length; i++) {
hexVal = bqual[i] & 0xFF;
if (hexVal < 0x10)
sb.append("0" + Integer.toHexString(bqual[i] & 0xFF));
else
sb.append(Integer.toHexString(bqual[i] & 0xFF));
}
sb.append("}");
return sb.toString();
}
// Returns a globally unique transaction id.
static byte[] localIP = null;
static int txnUniqueID = 0;
static Xid getUniqueXid(int tid) {
Random rnd = new Random(System.currentTimeMillis());
txnUniqueID++;
int txnUID = txnUniqueID;
int tidID = tid;
int randID = rnd.nextInt();
byte[] gtrid = new byte[64];
byte[] bqual = new byte[64];
if (null == localIP) {
try {
localIP = Inet4Address.getLocalHost().getAddress();
} catch (Exception ex) {
localIP = new byte[] {0x01, 0x02, 0x03, 0x04};
}
}
System.arraycopy(localIP, 0, gtrid, 0, 4);
System.arraycopy(localIP, 0, bqual, 0, 4);
// Bytes 4 -> 7 - unique transaction id.
// Bytes 8 ->11 - thread id.
// Bytes 12->15 - random number generated by using seed from current time in milliseconds.
for (int i = 0; i <= 3; i++) {
gtrid[i + 4] = (byte) (txnUID % 0x100);
bqual[i + 4] = (byte) (txnUID % 0x100);
txnUID >>= 8;
gtrid[i + 8] = (byte) (tidID % 0x100);
bqual[i + 8] = (byte) (tidID % 0x100);
tidID >>= 8;
gtrid[i + 12] = (byte) (randID % 0x100);
bqual[i + 12] = (byte) (randID % 0x100);
randID >>= 8;
}
return new XidImpl(0x1234, gtrid, bqual);
}
}