Microsoft JDBC Driver for SQL Server 提供对 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 事务的准则和限制

以下附加准则适用于紧密耦合的事务:

  • 当您将 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 (BQUAL),但具有相同的全局事务 ID (GTRID) 和格式 ID (FormatID)。为了使用该功能,必须对 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 Vista 和更高版本上:

  1. 单击“开始”按钮,在“开始搜索”框中键入 dcomcnfg,然后按 Enter 打开“组件服务”。也可以在“开始搜索”框中键入 %windir%\system32\comexp.msc 打开“组件服务”

  2. 依次展开“组件服务”、“计算机”、“我的电脑”和“分布式事务处理协调器”。

  3. 右键单击“本地 DTC”,再选择“属性”

  4. 单击“本地 DTC 属性”对话框上的“安全”选项卡。

  5. 选中“启用 XA 事务”复选框,然后单击“确定”。这将使 MS DTC 服务重新启动。

  6. 再次单击“确定”以关闭“属性”对话框,然后关闭“组件服务”

  7. 停止 SQL Server,然后重新启动,确保它与 MS DTC 更改同步。

配置 JDBC 分布式事务组件

可通过以下步骤配置 JDBC 驱动程序分布式事务组件:

  1. 将新 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 文件。

  2. 对每个要参与分布式事务的 SQL Server 实例执行数据库脚本 xa_install.sql。此脚本将安装 sqljdbc_xa.dll 调用的扩展存储过程。这些扩展存储过程为 Microsoft JDBC Driver for SQL Server 实现分布式事务和 XA 支持。需要以 SQL Server 实例管理员的身份来运行此脚本。

  3. 若要为特定用户授予使用 JDBC 驱动程序参与分布式事务的权限,请将该用户添加至 SqlJDBCXAUser 角色。

在每个 SQL Server 实例上,一次只能配置一个版本的 sqljdbc_xa.dll 程序集。应用程序可能需要使用不同版本的 JDBC 驱动程序,才能使用 XA 连接来连接到同一个 SQL Server 实例。在这种情况下,必须在 SQL Server 实例上安装最新的 JDBC 驱动程序附带的 sqljdbc_xa.dll。

可以通过三种方法来验证 SQL Server 实例上当前安装的 sqljdbc_xa.dll 的版本:

  1. 打开将参与分布式事务处理的 SQL Server 计算机的 LOG 目录。选择并打开 SQL Server 的“ERRORLOG”文件。在“ERRORLOG”文件中搜索“使用‘SQLJDBC_XA.dll’版本 ...”这一短语。

  2. 打开将参与分布式事务处理的 SQL Server 计算机的 Binn 目录。选择 sqljdbc_xa.dll 程序集。

    • 在 Windows Vista 或更高版本上:右键单击 sqljdbc_xa.dll,然后选择“属性”。然后单击“详细信息”选项卡。“文件版本”字段显示 SQL Server 实例上当前安装的 sqljdbc_xa.dll 的版本。

  3. 按照下一节的代码示例所示设置日志记录功能。在输出日志文件中搜索“服务器 XA DLL 版本:...”这一短语。

升级 sqljdbc_xa.dll

您在安装新版本的 JDBC 驱动程序时,也应该使用新版本中的 sqljdbc_xa.dll 来升级服务器上的 sqljdbc_xa.dll。

您应该在维护窗口期间或进程中没有 MS DTC 事务时升级 sqljdbc_xa.dll。

  1. 使用 Transact-SQL 命令 DBCC sqljdbc_xa (FREE) 卸载 sqljdbc_xa.dll。

  2. 将新 sqljdbc_xa.dll 从 JDBC 驱动程序安装目录复制到每台要参与分布式事务的 SQL Server 计算机的 Binn 目录中。

    当 sqljdbc_xa.dll 中调用扩展过程时,将会加载新 DLL。您不需要重新启动 SQL Server 来加载新定义。

配置用户定义的角色

若要为特定用户授予使用 JDBC 驱动程序参与分布式事务的权限,请将该用户添加至 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);

         // 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);

         // 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);
   }
}

请参阅