了解 XA 事务
Microsoft SQL Server JDBC 提供程序提供对 Java Platform, Enterprise Edition/JDBC 2.0 可选分布式事务的支持。从 SQLServerXADataSource 类获取的 JDBC 连接可以参与标准分布式事务处理环境,例如 Java Platform, Enterprise Edition (Java EE) 应用程序服务器。
用于此分布式事务实现的类如下:
类 | 实现 | 说明 |
---|---|---|
com.microsoft.sqlserver.jdbc.SQLServerXADataSource | javax.sql.XADataSource | 分布式连接的类工厂。 |
com.microsoft.sqlserver.jdbc.SQLServerXAResource | javax.transaction.xa.XAResource | 事务管理器的资源适配器。 |
![]() |
---|
XA 分布式事务连接默认使用“提交读”隔离级别。 |
下面的列表提供了有关使用 XA 事务时的具体限制和准则的信息:
- 在 Windows XP 上:
当您通过 Microsoft SQL Server JDBC Driver 将 XA 事务用于 SQL Server 时,可能会注意到 XA 事务不起作用。仅当正参与 XA 事务的 SQL Server 正在 Windows XP 上运行时,才会出现此问题。另一方面,在 Windows XP 上运行且与不在 Windows XP 上运行的远程 SQL Server 相连的客户端应用程序可以参与 XA 事务。有关如何解决此问题的详细信息,请参阅 Windows XP COM+ 修复程序总成包 14 的可用性中提供的修补程序。
- 在 Windows Server 2003 上:
当您在 Windows Server 2003 上将 XA 事务与 Microsoft 分布式事务处理协调器 (MS DTC) 一起使用时,您可能会注意到 XAResource.setTransactionTimeout 方法不起作用。若要解决此问题,必须将 MSDTC 和 XA 事务中提供的一个修补程序应用于正在参与 XA 事务的每一台 SQL Server 计算机。如果不应用此修补程序,则唯一有效的超时值为默认值 0,这意味着无限超时。
以下附加准则适用于紧密耦合的事务:
- 当您将 XA 事务与 Microsoft 分布式事务处理协调器 (MS DTC) 一起使用时,您可能会注意到 MS DTC 的当前版本不支持紧密结合的 XA 分支行为。例如,MS DTC 在 XA 分支事务 ID (XID) 与 MS DTC 事务 ID 之间具有一对一的映射,由松散耦合的 XA 分支执行的工作彼此之间是隔离的。
借助于在 MSDTC 和紧密结合的事务中提供的修补程序,可以支持紧密结合的 XA 分支,其中,多个具有相同全局事务 ID (GTRID) 的 XA 分支映射到单一 MS DTC 事务 ID。这种支持使得多个紧密结合的 XA 分支可以在资源管理器(如 SQL Server)中看到彼此发生的变化。
- SSTRANSTIGHTLYCPLD 标志允许应用程序使用紧密结合的 XA 事务,这些事务具有不同的 XA 分支事务 ID (XID),但具有相同的全局事务 ID (GTRID)。为了使用该功能,必须对 XAResource.start 方法的 flags 参数设置 SSTRANSTIGHTLYCPLD:
xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
如果要同时使用 XA 数据源和 Microsoft 分布式事务处理协调器 (MS DTC) 来处理分布式事务,则需要执行以下步骤。
![]() |
---|
JDBC 分布式事务组件包含在 JDBC 驱动程序安装的 xa 目录中。这些组件包括 xa_install.sql 和 sqljdbc_xa.dll 文件。 |
运行 MS DTC 服务
在服务管理器中,MS DTC 服务应标记为“自动”,以确保其在启动 SQL Server 服务时运行。若要为 XA 事务启用 MS DTC,必须执行以下步骤:
在 Windows XP 和 Windows Server 2003 上:
- 在“控制面板”中,打开“管理工具”,然后打开“组件服务”。也可以单击“开始”按钮,单击“运行”,在“打开”框中键入 dcomcnfg,然后按“确定”打开“组件服务”。
- 展开“组件服务”、“计算机”,右键单击“我的电脑”,然后选择“属性”。
- 单击“MSDTC”选项卡,再单击“安全性配置”。
- 选中“启用 XA 事务”复选框,然后单击“确定”。这将使 MS DTC 服务重新启动。
- 再次单击“确定”以关闭“属性”对话框,然后关闭“组件服务”。
- 停止 SQL Server,然后重新启动,以确保它与 MS DTC 更改同步。
在 Windows Vista 上:
- 单击“开始”按钮,在“开始搜索”框中键入 dcomcnfg,然后按 Enter 打开“组件服务”。也可以在“开始搜索”框中键入 %windir%\system32\comexp.msc 打开“组件服务”。
- 依次展开“组件服务”、“计算机”、“我的电脑”和“分布式事务处理协调器”。
- 右键单击“本地 DTC”,再选择“属性”。
- 单击“本地 DTC 属性”对话框上的“安全”选项卡。
- 选中“启用 XA 事务”复选框,然后单击“确定”。这将使 MS DTC 服务重新启动。
- 再次单击“确定”以关闭“属性”对话框,然后关闭“组件服务”。
- 停止 SQL Server,然后重新启动,确保它与 MS DTC 更改同步。
配置 JDBC 分布式事务组件
可通过以下步骤配置 JDBC 驱动程序分布式事务组件:
- 将 sqljdbc_xa.dll 从 JDBC 安装目录复制到每台要参与分布式事务的 SQL Server 计算机的 Binn 目录中。
注意:
如果您将 XA 事务用于 32 位 SQL Server,则使用 x86 文件夹中的 sqljdbc_xa.dll 文件,即使 SQL Server 安装在 x64 处理器上也不例外。如果您在 x64 处理器上将 XA 事务用于 64 位 SQL Server,则使用 x64 文件夹中的 sqljdbc_xa.dll 文件。如果您是在 Itanium 处理器上将 XA 事务用于 64 位 SQL Server,则使用 IA64 文件夹中的 sqljdbc_xa.dll 文件。 - 对每个要参与分布式事务的 SQL Server 实例执行数据库脚本 xa_install.sql。此脚本将安装 sqljdbc_xa.dll 调用的扩展存储过程。这些扩展存储过程为 Microsoft SQL Server JDBC Driver 实现分布式事务和 XA 支持。需要以 SQL Server 实例管理员的身份来运行此脚本。
- 若要为特定用户授予使用 JDBC Driver 参与分布式事务的权限,请将该用户添加至 SqlJDBCXAUser 角色。
在每个 SQL Server 实例上,一次只能配置一个版本的 sqljdbc_xa.dll 程序集。应用程序可能需要使用不同版本的 JDBC driver,才能使用 XA 连接来连接到同一个 SQL Server 实例。在这种情况下,必须在 SQL Server 实例上安装最新的 JDBC driver 附带的 sqljdbc_xa.dll。
可以通过三种方法来验证 SQL Server 实例上当前安装的 sqljdbc_xa.dll 的版本:
- 打开将参与分布式事务处理的 SQL Server 计算机的 LOG 目录。选择并打开 SQL Server 的“ERRORLOG”文件。在“ERRORLOG”文件中搜索“使用‘SQLJDBC_XA.dll’版本 ...”这一短语。
- 打开将参与分布式事务处理的 SQL Server 计算机的 Binn 目录。选择 sqljdbc_xa.dll 程序集。
- 在 Windows Vista 上:右键单击 sqljdbc_xa.dll,然后选择“属性”。然后单击“详细信息”选项卡。“文件版本”字段显示 SQL Server 实例上当前安装的 sqljdbc_xa.dll 的版本。
- 在 Windows XP 和 Windows 2003 Server 上:右键单击 sqljdbc_xa.dll,然后选择“属性”。接下来,单击“版本”选项卡。“文件版本”字段显示 SQL Server 实例上当前安装的 sqljdbc_xa.dll 的版本。
- 在 Windows Vista 上:右键单击 sqljdbc_xa.dll,然后选择“属性”。然后单击“详细信息”选项卡。“文件版本”字段显示 SQL Server 实例上当前安装的 sqljdbc_xa.dll 的版本。
- 按照下一节的代码示例所示设置日志记录功能。在输入日志文件中搜索“服务器 XA DLL 版本:...”这一短语。
配置用户定义的角色
若要为特定用户授予使用 JDBC Driver 参与分布式事务的权限,请将该用户添加至 SqlJDBCXAUser 角色。例如,使用以下 Transact-SQL 代码将名为“shelby”(SQL 标准登录用户名为“shelby”)的用户添加至 SqlJDBCXAUser 角色:
USE master GO EXEC sp_grantdbaccess 'shelby', 'shelby' GO EXEC sp_addrolemember [SqlJDBCXAUser], 'shelby'
SQL 用户定义的角色是按数据库定义的。若要出于安全目的创建自己的角色,必须在每个数据库中定义角色,并在每个数据库中添加用户。主数据库中 SqlJDBCXAUser 角色的定义非常严格,因为该角色用于授予对主数据库中驻留的 SQL JDBC 扩展存储过程的访问权限。登录到主数据库后,必须先授予单个用户对主数据库的访问权限,然后再授予这些用户对 SqlJDBCXAUser 角色的访问权限。
import java.net.Inet4Address; import java.sql.*; import java.util.Random; import javax.transaction.xa.*; import javax.sql.*; 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 = "*****"; String connectionUrl = prefix + serverName + ":" + portNumber + ";databaseName=" + databaseName + ";user=" + user + ";password=" + password; try { // Establish the connection. Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); Connection con = DriverManager.getConnection(connectionUrl); // Create a test table. Statement stmt = con.createStatement(); try { stmt.executeUpdate("DROP TABLE XAMin"); } catch (Exception e) { } stmt.executeUpdate("CREATE TABLE XAMin (f1 int, f2 varchar(max))"); stmt.close(); con.close(); // 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(); con = xaCon.getConnection(); // Get a unique Xid object for testing. XAResource xaRes = null; Xid xid = null; xid = XidImpl.getUniqueXid(1); // Cleanup. con.close(); xaCon.close(); // Open a new connection and read back the record to verify that it worked. con = DriverManager.getConnection(connectionUrl); ResultSet rs = con.createStatement().executeQuery("SELECT * FROM XAMin"); rs.next(); System.out.println("Read -> xid = " + rs.getString(2)); rs.close(); con.close(); } // Handle any errors that may have occurred. catch (Exception e) { e.printStackTrace(); } } } 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); } }