XA 트랜잭션 이해

Microsoft SQL Server JDBC 드라이버는 Java Platform, Enterprise Edition 5/JDBC 2.0 분산 트랜잭션(옵션)을 지원합니다. SQLServerXADataSource 클래스에서 가져온 JDBC 연결은 Java Platform, Enterprise Edition 5 (Java EE 5) 응용 프로그램 서버와 같은 표준 분산 트랜잭션 처리 환경에 참여할 수 있습니다.

분산 트랜잭션을 구현하는 데 필요한 클래스는 다음과 같습니다.

클래스 구현 설명

com.microsoft.sqlserver.jdbc.SQLServerXADataSource

javax.sql.XADataSource

분산 연결용 클래스 팩토리입니다.

com.microsoft.sqlserver.jdbc.SQLServerXAResource

javax.transaction.xa.XAResource

트랜잭션 관리자용 리소스 어댑터입니다.

참고

XA 분산 트랜잭션 연결의 기본값은 커밋된 읽기 격리 수준입니다.

XA 트랜잭션 사용과 관련된 지침 및 제한 사항

다음 목록에서는 XA 트랜잭션 사용과 관련된 구체적인 제한 사항 및 지침을 보여 줍니다.

  • Windows XP:

    Microsoft SQL Server JDBC 드라이버를 통해 SQL Server에서 XA 트랜잭션을 사용할 경우 XA 트랜잭션이 작동하지 않을 수 있습니다. 이 문제는 XA 트랜잭션에 참여하는 SQL Server가 Windows XP에서 실행 중인 경우에만 발생합니다. 반면 Windows XP에서 실행 중인 클라이언트 응용 프로그램이 Windows XP에서 실행 중이지 않은 원격 SQL Server에 연결되는 경우 해당 응용 프로그램은 XA 트랜잭션에 참여할 수 있습니다. 이 문제를 해결하는 방법은 Windows XP 및 XA 트랜잭션에서 제공하는 핫픽스를 참조하십시오.

  • Windows Server 2003:

    Windows Server 2003에서 XA 트랜잭션을 MS DTC(Microsoft Distributed Transaction Coordinator)와 함께 사용할 경우 XAResource.setTransactionTimeout 메서드가 작동하지 않습니다. 이 문제를 해결하려면 XA 트랜잭션에 참여하는 각각의 SQL Server 컴퓨터에 MSDTC 및 XA 트랜잭션에서 제공하는 핫픽스를 적용해야 합니다. 이 픽스가 없을 경우 기본값인 0(제한 시간 무한대)만 유효한 제한 시간 값입니다.

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

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

    MSDTC 및 밀접하게 결합된 트랜잭션에서 제공하는 핫픽스는 같은 GTRID(전역 트랜잭션 ID)를 사용하는 여러 개의 XA 분기가 한 개의 MS DTC 트랜잭션 ID에 매핑되는 밀접하게 결합된 XA 분기를 지원합니다. 이러한 지원을 통해 밀접하게 결합된 XA 분기가 SQL Server와 같은 리소스 관리자에서 다른 XA 분기의 변경 내용을 확인할 수 있습니다.

  • Microsoft SQL Server 2005 JDBC 드라이버 버전 1.2 이상에서는 SSTRANSTIGHTLYCPLD 플래그를 제공하므로 XID(XA 분기 트랜잭션 ID)는 다르지만 GTRID(전역 트랜잭션 ID)는 같은 밀접하게 결합된 XA 트랜잭션을 응용 프로그램에서 사용할 수 있습니다. 이 기능을 사용하려면 XAResource.start 메서드의 flags 매개 변수에 SSTRANSTIGHTLYCPLD를 설정해야 합니다.

    xaRes.start(xid, SQLServerXAResource.SSTRANSTIGHTLYCPLD);
    

구성 지침

MS DTC(Microsoft Distributed Transaction Coordinator)와 XA 데이터 원본을 함께 사용하여 분산 트랜잭션을 처리하려면 다음과 같은 단계가 필요합니다.

참고

JDBC 분산 트랜잭션 구성 요소는 JDBC 드라이버 설치의 xa 디렉터리에 있습니다. 이 구성 요소에는 xa_install.sql 및 sqljdbc_xa.dll 파일이 포함됩니다.

MS DTC 서비스 실행

MS DTC 서비스는 서비스 관리자에서 자동으로 표시하여 SQL Server 서비스를 시작할 때 자동으로 실행되도록 해야 합니다. XA 트랜잭션에 대해 MS DTC를 활성화하려면 다음 단계를 따릅니다.

Windows XP 및 Windows Server 2003:

  1. 제어판에서 관리 도구를 연 다음 구성 요소 서비스를 클릭합니다. 시작 단추와 실행을 차례로 클릭하고 열기 상자에 dcomcnfg를 입력한 다음 확인을 눌러 구성 요소 서비스를 열 수도 있습니다.

  2. 구성 요소 서비스, 컴퓨터를 확장하고 내 컴퓨터를 마우스 오른쪽 단추로 클릭한 다음 속성을 선택합니다.

  3. MSDTC 탭을 클릭하고 보안 구성을 클릭합니다.

  4. XA 트랜잭션 사용 확인란을 선택하고 확인을 클릭합니다. 이렇게 하면 MS DTC 서비스가 다시 시작됩니다.

  5. 확인을 다시 클릭하여 속성 대화 상자를 닫은 후 구성 요소 서비스를 닫습니다.

  6. SQL Server를 중지한 후 다시 시작하여 MS DTC 변경 내용과 동기화되도록 합니다.

Windows Vista:

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

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

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

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

  5. XA 트랜잭션 사용 확인란을 선택하고 확인을 클릭합니다. 이렇게 하면 MS DTC 서비스가 다시 시작됩니다.

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

  7. SQL Server를 중지한 후 다시 시작하여 MS DTC 변경 내용과 동기화되도록 합니다.

JDBC 분산 트랜잭션 구성 요소 구성

다음과 같은 단계를 통해 JDBC 드라이버 분산 트랜잭션 구성 요소를 구성할 수 있습니다.

  1. JDBC 설치 디렉터리에 있는 sqljdbc_xa.dll을 분산 트랜잭션에 참여할 모든 SQL Server 컴퓨터의 Binn 디렉터리에 복사합니다.

    참고

    32비트 SQL Server에서 XA 트랜잭션을 사용할 경우 SQL Server가 x64 프로세서에 설치되었더라도 x86 폴더에 있는 sqljdbc_xa.dll을 사용하십시오. x64 프로세서의 64비트 SQL Server에서 XA 트랜잭션을 사용할 경우 x64 폴더에 있는 sqljdbc_xa.dll을 사용하십시오. Itanium 프로세서의 64비트 SQL Server에서 XA 트랜잭션을 사용할 경우 IA64 폴더에 있는 sqljdbc_xa.dll을 사용하십시오.

  2. 분산 트랜잭션에 참여할 모든 SQL Server 인스턴스에서 데이터베이스 스크립트 xa_install.sql을 실행합니다. 이 스크립트는 sqljdbc_xa.dll에서 호출하는 확장 저장 프로시저를 설치합니다. 이 확장 저장 프로시저는 Microsoft SQL Server JDBC 드라이버 2.0에 대한 분산 트랜잭션 및 XA 지원을 구현합니다. 이 스크립트를 실행하려면 SQL Server 인스턴스의 관리자 권한이 필요합니다.

  3. 특정 사용자에게 JDBC 드라이버를 통해 분산 트랜잭션에 참여할 권한을 부여하려면 해당 사용자를 SqlJDBCXAUser 역할에 추가합니다.

SQL Server 인스턴스마다 한 번에 하나의 sqljdbc_xa.dll 어셈블리 버전만 구성할 수 있습니다. 응용 프로그램에서 여러 버전의 JDBC 드라이버를 사용하여 XA 연결을 통해 동일한 SQL Server 인스턴스에 연결해야 하는 경우가 있을 수 있습니다. 이 경우 최신 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 어셈블리를 선택합니다.

    1. Windows Vista: sqljdbc_xa.dll을 마우스 오른쪽 단추로 클릭한 다음 속성을 선택합니다. 그런 다음 자세히 탭을 클릭합니다. 파일 버전 필드에 현재 SQL Server 인스턴스에 설치되어 있는 sqljdbc_xa.dll 버전이 표시됩니다.

    2. Windows XP 및 Windows 2003 Server: sqljdbc_xa.dll을 마우스 오른쪽 단추로 클릭한 다음 속성을 선택합니다. 그런 다음 버전 탭을 클릭합니다. 파일 버전 필드에 현재 SQL Server 인스턴스에 설치되어 있는 sqljdbc_xa.dll 버전이 표시됩니다.

  3. 다음 섹션의 코드 예제에서와 같이 로깅 기능을 설정합니다. 출력 로그 파일에서 "Server XA DLL version:..." 구를 검색합니다.

사용자 정의 역할 구성

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

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

SQL 사용자 정의 역할은 데이터베이스별로 정의합니다. 보안을 위해 고유한 역할을 만들려면 각 데이터베이스마다 역할을 정의하고 각 데이터베이스의 방식대로 사용자를 추가해야 합니다. SqlJDBCXAUser 역할은 master에 상주하는 SQL JDBC 확장 저장 프로시저에 대한 액세스 권한을 부여하는 데 사용되므로 master 데이터베이스에서만 정의할 수 있습니다. 먼저 개별 사용자에게 master에 대한 액세스 권한을 부여한 다음 master 데이터베이스에 로그인된 상태에서 SqlJDBCXAUser 역할에 대한 액세스 권한을 부여해야 합니다.

import java.net.Inet4Address;
import java.sql.*;
import java.util.Random;
import javax.transaction.xa.*;
import javax.sql.*;
import com.microsoft.sqlserver.jdbc.*;
import java.util.logging.*;

public class testXA {

   public static void main(String[] args) throws Exception {

      // Create a variable for the connection string.
      String connectionUrl = "jdbc:sqlserver://localhost:1433;"
         +"databaseName=AdventureWorks;user=UserName;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 a logger.
         Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.XA");
         FileHandler fh = new FileHandler("outputLog.txt");
         logger.addHandler(fh);
         logger.setLevel(Level.FINE);

         // Create the XA data source and XA ready connection.
         SQLServerXADataSource ds = new SQLServerXADataSource();
         ds.setUser("UserName");
         ds.setPassword("*****");
         ds.setServerName("localhost");
         ds.setPortNumber(1433);
         ds.setDatabaseName("AdventureWorks");
         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.
         pstmt.close();
         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);
   }
}

참고

관련 자료

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