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 和更高版本上:
单击“开始”按钮,在“开始搜索”框中键入 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 文件。
对每个要参与分布式事务的 SQL Server 实例执行数据库脚本 xa_install.sql。此脚本将安装 sqljdbc_xa.dll 调用的扩展存储过程。这些扩展存储过程为 Microsoft JDBC Driver for SQL Server 实现分布式事务和 XA 支持。需要以 SQL Server 实例管理员的身份来运行此脚本。
若要为特定用户授予使用 JDBC 驱动程序参与分布式事务的权限,请将该用户添加至 SqlJDBCXAUser 角色。
在每个 SQL Server 实例上,一次只能配置一个版本的 sqljdbc_xa.dll 程序集。应用程序可能需要使用不同版本的 JDBC 驱动程序,才能使用 XA 连接来连接到同一个 SQL Server 实例。在这种情况下,必须在 SQL Server 实例上安装最新的 JDBC 驱动程序附带的 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 的版本。
按照下一节的代码示例所示设置日志记录功能。在输出日志文件中搜索“服务器 XA DLL 版本:...”这一短语。
升级 sqljdbc_xa.dll
您在安装新版本的 JDBC 驱动程序时,也应该使用新版本中的 sqljdbc_xa.dll 来升级服务器上的 sqljdbc_xa.dll。
您应该在维护窗口期间或进程中没有 MS DTC 事务时升级 sqljdbc_xa.dll。
使用 Transact-SQL 命令 DBCC sqljdbc_xa (FREE) 卸载 sqljdbc_xa.dll。
将新 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);
}
}