다음을 통해 공유


XA 트랜잭션 이해

JDBC 드라이버 다운로드

Microsoft JDBC Driver for SQL Server는 Java 플랫폼인 Enterprise Edition/JDBC 2.0 선택적 분산 트랜잭션을 지원합니다. SQLServerXADataSource 클래스에서 가져온 JDBC 연결은 Java 플랫폼인 Java 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 분산 트랜잭션 연결은 기본적으로 커밋된 읽기 격리 수준으로 설정됩니다.

XA 트랜잭션을 사용할 때의 지침 및 제한 사항

밀접하게 결합된 트랜잭션에는 다음과 같은 추가 지침이 적용됩니다.

  • Microsoft Distributed Transaction Coordinator(MS DTC)와 함께 XA 트랜잭션을 사용하는 경우, 현재 버전의 MS DTC가 밀접하게 결합된 XA 분기 동작을 지원하지 않는다는 것을 알게 될 수도 있습니다. 예를 들어 XA 분기 트랜잭션 ID(XID)와 MS DTC 트랜잭션 ID 사이에서 MS DTC에 일대일 매핑이 있고, 느슨하게 결합된 XA 분기에서 수행하는 작업은 서로 격리됩니다.

  • 또한 MS DTC는 같은 GTRID(전역 트랜잭션 ID)를 사용하는 여러 개의 XA 분기가 한 개의 MS DTC 트랜잭션 ID에 매핑되는 밀접하게 결합된 XA 분기도 지원합니다. 이 지원을 사용하면 밀접하게 결합된 여러 XA 분기에서 SQL Server와 같은 리소스 관리자를 통해 서로의 변경 내용을 볼 수 있습니다.

  • SSTRANSTIGHTLYCPLD 플래그를 사용하면 애플리케이션에서 BQUAL(XA 분기 트랜잭션 ID)은 다르지만 GTRID(글로벌 트랜잭션 ID) 및 FormatID(형식 ID)는 동일한, 밀접하게 결합된 XA 트랜잭션을 사용할 수 있습니다. 이 기능을 사용하려면 XAResource.start 메서드의 flags 매개 변수에 SSTRANSTIGHTLYCPLD를 설정해야 합니다.

    xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
    

구성 지침

분산 트랜잭션을 처리하기 위해 Microsoft Distributed Transaction Coordinator(MS DTC)와 함께 XA 데이터 원본을 사용하려는 경우 다음 단계가 필요합니다. 개략적인 단계는 다음과 같습니다.

  1. MS DTC 서비스가 실행 중이고 자동으로 시작되는지 확인합니다.
  2. 서버 쪽 구성 요소를 구성합니다.
  3. 서버 쪽 시간 제한(선택 사항)을 구성합니다.
  4. 사용자에게 액세스 권한을 부여합니다.

MS DTC 서비스 실행

SQL Server 서비스 시작 시 MS DTC 서비스가 실행 중이도록 하려면 Service Manager에서 서비스가 자동으로 표시되어야 합니다. XA 트랜잭션에 MS DTC를 사용하도록 설정하려면 다음 단계를 수행해야 합니다.

Windows Vista 이상:

  1. 시작 단추를 클릭하고 검색 시작 상자에 dcomcnfg를 입력한 다음 Enter 키를 눌러 구성 요소 서비스를 엽니다. StartSearch 상자에 %windir%\system32\comexp.msc를 입력하여 구성 요소 서비스를 열 수도 있습니다.

  2. 구성 요소 서비스, 컴퓨터, 내 컴퓨터를 확장한 다음 Distributed Transaction Coordinator를 확장합니다.

  3. 마우스 오른쪽 단추로 로컬 DTC를 클릭한 다음 속성을 선택합니다.

  4. 로컬 DTC 속성 대화 상자의 보안 탭을 클릭합니다.

  5. XA 트랜잭션 사용 확인란을 선택하고 확인을 선택합니다. 이 작업으로 MS DTC 서비스를 다시 시작합니다.

  6. 확인을 다시 선택하여 속성 대화 상자를 닫은 다음 구성 요소 서비스를 닫습니다.

  7. 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 이상에 이미 포함되어 있습니다.

JDBC 분산 트랜잭션 구성 요소는 JDBC 드라이버 설치의 xa 디렉터리에 있습니다. 이러한 구성 요소에는 xa_install.sql 및 sqljdbc_xa.dll 파일이 포함됩니다. 다른 클라이언트에 다른 버전의 JDBC 드라이버가 있는 경우 서버에서 최신 sqljdbc_xa.dll을 사용하는 것이 좋습니다.

다음 단계를 수행하여 JDBC 드라이버 분산 트랜잭션 구성 요소를 구성할 수 있습니다.

  1. JDBC 드라이버 설치 디렉터리의 새 sqljdbc_xa.dll을 분산 트랜잭션에 참여하는 모든 SQL Server 컴퓨터의 Binn 디렉터리로 복사합니다.

    참고 항목

    32비트 SQL Server(SQL Server 2014 이상에만 해당)와 함께 XA 트랜잭션을 사용하는 경우, x64 프로세서에 SQL Server가 설치되어 있더라도 x86 폴더의 sqljdbc_xa.dll 파일을 사용합니다. x64 프로세서의 64비트 SQL Server에서 XA 트랜잭션을 사용할 경우 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 어셈블리만 구성할 수 있습니다. 응용 프로그램은 XA 연결을 사용해 동일한 SQL Server 인스턴스에 연결하는 데 다른 버전의 JDBC 드라이버를 사용해야 할 수 있습니다. 이 경우, 최신 JDBC 드라이버와 함께 제공되는 sqljdbc_xa.dll은 SQL Server 인스턴스에 설치해야 합니다.

SQL Server 인스턴스에 현재 설치된 sqljdbc_xa.dll 버전을 확인하는 세 가지 방법이 있습니다.

  1. 분산 트랜잭션에 참여하는 SQL Server 컴퓨터의 LOG 디렉터리를 엽니다. SQL Server "ERRORLOG" 파일을 선택하고 엽니다. "ERRORLOG" 파일에서 "Using 'SQLJDBC_XA.dll' version ..."이라는 문구를 검색합니다.

  2. 분산 트랜잭션에 참여하는 SQL Server 컴퓨터의 Binn 디렉터리를 엽니다. sqljdbc_xa.dll 어셈블리를 선택합니다.

    • Windows Vista 이상의 경우 sqljdbc_xa.dll을 마우스 오른쪽 단추로 클릭한 다음 속성을 선택합니다. 그런 다음 세부 정보 탭을 선택합니다. 파일 버전 필드에는 현재 SQL Server 인스턴스에 설치된 sqljdbc_xa.dll 버전이 표시됩니다.
  3. 다음 섹션의 코드 예제와 같이 로깅 기능을 설정합니다. 출력 로그 파일에서 "Server XA DLL version:..."이라는 문구를 검색합니다.

sqljdbc_xa.dll 업그레이드

참고 항목

SQL Server 2017 CU15 이하에만 적용됩니다. sqljdbc_xa.dll에서 제공된 함수는 SQL Server 2017 CU16 이상에 이미 포함되어 있습니다.

새로운 JDBC 드라이버 버전을 설치하는 경우 새 버전의 sqljdbc_xa.dll을 사용하여 서버의 sqljdbc_xa.dll을 업그레이드해야 합니다.

Important

기본 유지 관리 기간 동안, 또는 진행 중인 MS DTC 트랜잭션이 없을 때 sqljdbc_xa.dll을 업그레이드해야 합니다.

  1. Transact-SQL 명령을 사용하여 sqljdbc_xa.dll을 언로드합니다.

    DBCC sqljdbc_xa (FREE)
    
  2. JDBC 드라이버 설치 디렉터리의 새 sqljdbc_xa.dll을 분산 트랜잭션에 참여하는 모든 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()을 통해 지정한 내용에 따라 결정됩니다. 이러한 제한 시간 값이 해석되는 방식에 대한 몇 가지 예제는 다음과 같습니다.

  • XADefaultTimeout = 0: XAMaxTimeout = 0

    기본 시간 제한이 사용되지 않으며 클라이언트에 최대 시간 제한이 적용되지 않음을 의미합니다. 이 경우 클라이언트에서 XAResource.setTransactionTimeout을 사용하여 제한 시간을 설정하는 경우에만 트랜잭션의 제한 시간이 설정됩니다.

  • XADefaultTimeout = 60: XAMaxTimeout = 0

    클라이언트가 시간 제한을 지정하지 않는 경우 모든 트랜잭션에 60초의 시간 제한이 주어진다는 의미입니다. 클라이언트가 시간 제한을 지정하는 경우 해당 시간 제한 값이 사용됩니다. 시간 제한에 대한 최댓값은 적용되지 않습니다.

  • XADefaultTimeout = 30: XAMaxTimeout = 60

    클라이언트가 시간 제한을 지정하지 않는 경우 모든 트랜잭션에 30초의 시간 제한이 주어진다는 의미입니다. 클라이언트에서 시간 제한을 지정하면 60초(최댓값)보다 작은 경우에 한해 클라이언트의 시간 제한이 적용됩니다.

  • XADefaultTimeout = 0: XAMaxTimeout = 30

    클라이언트가 시간 제한을 지정하지 않는 경우 모든 트랜잭션에 30초(최댓값)의 시간 제한이 주어진다는 의미입니다. 클라이언트에서 시간 제한을 지정하면 30초(최댓값)보다 작은 경우에 한해 클라이언트의 시간 제한이 적용됩니다.

사용자 정의 역할 구성

JDBC 드라이버를 사용하여 분산 트랜잭션에 참여할 수 있는 권한을 특정 사용자에게 부여하려면 SqlJDBCXAUser 역할에 사용자를 추가합니다. 예를 들어 다음과 같은 Transact-SQL 코드를 사용하여 이름이 ‘shelly’인 사용자(이름이 ‘shelly’인 SQL 표준 로그인 사용자)를 SqlJDBCXAUser 역할에 추가합니다.

USE master
GO
EXEC sp_grantdbaccess 'shelly', 'shelly'
GO
EXEC sp_addrolemember [SqlJDBCXAUser], 'shelly'

SQL 사용자 정의 역할은 데이터베이스별로 정의됩니다. 보안 목적상 고유한 역할을 만들려면 각 데이터베이스에서 역할을 정의하고 데이터베이스별로 사용자를 추가해야 합니다. SqlJDBCXAUser 역할은 master에 있는 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);
    }
}

참고 항목

JDBC 드라이버로 트랜잭션 수행