XA トランザクションについて
SQL Server 用 Microsoft JDBC ドライバー は、Java Platform, Enterprise Edition /JDBC 2.0 のオプションの分散トランザクションをサポートします。 SQLServerXADataSource クラスから取得される JDBC 接続は、Java Platform, Enterprise Edition (Java EE) アプリケーション サーバーなどの標準分散トランザクション処理の環境に参加できます。
この記事では、XA は拡張アーキテクチャを意味します。
警告
Microsoft JDBC Driver 4.2 (以降) for SQL には、準備されていないトランザクションを自動ロールバックする既存の機能に関する新しいタイムアウト オプションが含まれています。 詳細については、このトピックの後に出てくる「準備されていないトランザクションを自動ロールバックするためのサーバー側のタイムアウト設定の構成」を参照してください。
解説
分散トランザクションを実装するためのクラスは、次のとおりです。
クラス | 実装 | 説明 |
---|---|---|
com.microsoft.sqlserver.jdbc.SQLServerXADataSource | javax.sql.XADataSource | 分散された接続用のクラス ファクトリです。 |
com.microsoft.sqlserver.jdbc.SQLServerXAResource | javax.transaction.xa.XAResource | トランザクション マネージャー用のリソース アダプターです。 |
注意
XA 分散トランザクション接続の既定の分離レベルは Read Committed です。
XA トランザクションを使用する場合のガイドラインと制限
密に結合されたトランザクションには、次のガイドラインがさらに適用されます。
XA トランザクションを Microsoft 分散トランザクション コーディネーター (MS DTC) と組み合わせて使用すると、現在のバージョンの MS DTC では、密に結合された XA ブランチ動作がサポートされない場合もあります。 たとえば、MS DTC では、XA ブランチ トランザクション ID (XID) と MS DTC トランザクション ID とが一対一でマップされ、疎結合の XA ブランチによって実行される作業が相互に分離されます。
MS DTC では、密に結合された XA ブランチもサポートされ、同じグローバル トランザクション ID (GTRID) を持つ複数の XA ブランチが 1 つの MS DTC トランザクション ID にマップされます。 このサポートにより、密に結合された複数の XA ブランチが、SQL Server などのリソース マネージャーで相互の変更を認識できるようになります。
SSTRANSTIGHTLYCPLD フラグにより、アプリケーションでは、XA ブランチ トランザクション ID (BQUAL) は異なるが、グローバル トランザクション ID (GTRID) と形式 ID (FormatID) は同じである、密に結合された XA トランザクションを使用できるようになります。 この機能を使用するには、XAResource.start メソッドの flags パラメーターで、SSTRANSTIGHTLYCPLD を次のように設定する必要があります。
xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
構成の手順
以下の手順は、分散トランザクションを処理するために XA データ ソースを Microsoft 分散トランザクション コーディネーター (MS DTC) と組み合わせて使用する場合に必要になります。 大まかな手順は次のとおりです。
- MS DTC サービスが実行されていて自動的に開始することを確認します。
- サーバー側コンポーネントを構成します。
- サーバー側のタイムアウトを構成します (省略可能)。
- ユーザーにアクセスを許可します。
MS DTC サービスを実行する
MS DTC サービスは、SQL Server サービスが開始されるときに確実に実行しているように、Service Manager で [自動] とマークされている必要があります。 XA トランザクションで使用するために MS DTC を有効にするには、次の手順を実行する必要があります。
Windows Vista 以降の場合:
[スタート] ボタンを選び、[検索の開始] ボックスに「dcomcnfg」と入力して Enter キーを押して、[コンポーネント サービス] を開きます。 [検索の開始] ボックスに「
%windir%\system32\comexp.msc
」と入力して [コンポーネント サービス] を開くこともできます。[コンポーネント サービス]、[コンピューター]、[マイ コンピューター]、[分散トランザクション コーディネーター] の順に展開します。
[ローカル DTC] を右クリックし、 [プロパティ] を選択します。
[ローカル DTC のプロパティ] ダイアログ ボックスの [セキュリティ] タブを選びます。
[XA トランザクションを有効にする] チェック ボックスをオンにして、[OK] を選びます。 この操作により、MS DTC サービスが再開されます。
もう一度 [OK] を選んで [プロパティ] ダイアログ ボックスを閉じ、次に [コンポーネント サービス] を閉じます。
MS DTC の変更が反映されるように、SQL Server を停止してから開始します。 (この手順は、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 以前
Note
これは、SQL Server 2017 CU15 以前にのみ適用されます。 sqljdbc_xa.dll が提供する関数は、SQL Server 2017 CU16 以降に既に含まれています。
JDBC 分散トランザクション コンポーネントは、JDBC Driver のインストール先の xa ディレクトリに含まれます。 これらのコンポーネントには、xa_install.sql および sqljdbc_xa.dll のファイルが含まれます。 さまざまなクライアントで JDBC ドライバーのさまざまなバージョンがある場合、サーバーで最新の sqljdbc_xa.dll を使用することをお勧めします。
次の手順を実行して、JDBC Driver 分散トランザクション コンポーネントを構成できます。
JDBC Driver のインストール ディレクトリにある新しい sqljdbc_xa.dll を、分散トランザクションに参加するすべての SQL Server コンピューターの Binn ディレクトリにコピーします。
注意
32 ビットの SQL Server で XA トランザクションを使う場合は (SQL Server 2014 以前にのみ適用)、x64 プロセッサ上に SQL Server がインストールされていても、x86 フォルダーの sqljdbc_xa.dll ファイルを使います。 x64 プロセッサ上にある 64 ビットの SQL Server で XA トランザクションを使用する場合は、x64 フォルダーの sqljdbc_xa.dll ファイルを使用してください。
xa_install.sql データベース スクリプトを、分散トランザクションに参加するすべての SQL Server インスタンスで実行します。 このスクリプトによって、sqljdbc_xa.dll から呼び出される拡張ストアド プロシージャがインストールされます。 これらの拡張ストアド プロシージャは、SQL Server 用 Microsoft JDBC ドライバー に必要な分散トランザクションと XA サポートを実装します。 このスクリプトは、SQL Server インスタンスの管理者として実行する必要があります。
JDBC Driver を使用して分散トランザクションに参加する、特定のユーザーに対してアクセス許可を与えるには、そのユーザーを SqlJDBCXAUser ロールに追加します。
1 つの SQL Server インスタンスに対して同時に構成できる sqljdbc_xa.dll アセンブリのバージョンは 1 つだけです。 アプリケーションで複数バージョンの JDBC ドライバーを使用し、XA 接続を使用して、同じ SQL Server インスタンスに接続する必要が生じる場合もあります。 そのような場合は、最新の JDBC Driver に付属する sqljdbc_xa.dll が SQL Server インスタンスにインストールされている必要があります。
現在、SQL Server インスタンスにインストールされている sqljdbc_xa.dll のバージョンは、次の 3 とおりの方法で確認できます。
分散トランザクションに参加する 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 のバージョンが [ファイル バージョン] フィールドに表示されます。
次のセクションのコード例に従ってログ機能を設定します。 出力ログ ファイルで、"Server XA DLL バージョン:..." というフレーズを探します。
sqljdbc_xa.dll のアップグレード
Note
これは、SQL Server 2017 CU15 以前にのみ適用されます。 sqljdbc_xa.dll が提供する関数は、SQL Server 2017 CU16 以降に既に含まれています。
新しいバージョンの JDBC Driver をインストールする場合は、新しいバージョンに含まれる sqljdbc_xa.dll を使用して、サーバー上の sqljdbc_xa.dll もアップグレードする必要があります。
重要
sqljdbc_xa.dll のアップグレードは、メンテナンス ウィンドウ内で行うか、進行中の MS DTC トランザクションがないときに行ってください。
次の Transact-SQL コマンドを使って、sqljdbc_xa.dll をアンロードします。
DBCC sqljdbc_xa (FREE)
JDBC Driver のインストール ディレクトリにある新しい sqljdbc_xa.dll を、分散トランザクションに参加するすべての SQL Server コンピューターの Binn ディレクトリにコピーします。
sqljdbc_xa.dll の拡張プロシージャが呼び出されると、新しい DLL が読み込まれます。 新しい定義を読み込むために SQL Server を再起動する必要はありません。
準備されていないトランザクションを自動ロールバックするためのサーバー側のタイムアウト設定を構成します。
分散トランザクションのタイムアウトの動作を制御する 2 つのレジストリ設定 (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() での指定によって決定されます。 これらのタイムアウト値の解釈のいくつかの例を以下に示します。
XADefaultTimeout = 0
、XAMaxTimeout = 0
つまり、既定のタイムアウトは使われず、クライアントではタイムアウトの最大値は適用されません。 この場合、クライアントで XAResource.setTransactionTimeout を使ってタイムアウトが設定されている場合にのみ、トランザクションはタイムアウトします。
XADefaultTimeout = 60
、XAMaxTimeout = 0
つまり、クライアントでタイムアウトが指定されていない場合は、すべてのトランザクションに 60 秒のタイムアウトが適用されます。 クライアントでタイムアウトが指定されている場合は、そのタイムアウト値が使われます。 タイムアウトの最大値は適用されません。
XADefaultTimeout = 30
、XAMaxTimeout = 60
つまり、クライアントでタイムアウトが指定されていない場合は、すべてのトランザクションに 30 秒のタイムアウトが適用されます。 クライアントで何らかのタイムアウトが指定されている場合、それが 60 秒 (最大値) より短い場合に限り、クライアントのタイムアウトが使われます。
XADefaultTimeout = 0
、XAMaxTimeout = 30
つまり、クライアントでタイムアウトが何も指定されていない場合は、すべてのトランザクションに 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 ロールは、マスターに存在する SQL JDBC 拡張ストアド プロシージャへのアクセス許可に使用されるため、master データベースで厳密に定義されています。 まず、個々のユーザーに 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);
}
}