내용 목차
본 장에서는 tbJDBC에서 제공하는 분산 트랜잭션(Distributed Transaction) 기능을 설명한다. 분산 트랜잭션의 대표적인 예로는 XA(Extended Architecture)가 있으며 이와 관련된 설명을 주로 한다.
분산 트랜잭션은 보통 전역 트랜잭션이라고도 하는데, 통합으로 관리되는 하나 이상의 트랜잭션 집합을 말한다. 분산 트랜잭션에 참여하는 트랜잭션은 동일한 데이터베이스에 존재할 수도 있고, 다른 데이터베이스에 존재할 수도 있다. 이러한 각각의 트랜잭션을 트랜잭션 브랜치(Transaction Branch)라고 한다.
분산 트랜잭션은 JDBC 2.0 표준의 확장 API로서 접속 풀링 기능을 기반으로 제공되거나 X/Open DTP(Distributed Transaction Processing) 규약의 XA 표준으로 제공되기도 한다.
다음은 XA 구성요소별 기능 설명이다.
XADataSource는 Connection Pool DataSource나 다른 DataSource와 비슷한 개념과 기능을 가진다. 분산 트랜잭션에서 사용되는 각각의 리소스 매니저(데이터베이스)에는 하나의 XADataSource 객체가 존재하고, 이 XADataSource가 XAConnection을 생성한다.
XAConnection은 Pooled Connection의 확장이며, 개념이나 기능면에서는 비슷하다.
XAConnection은 물리적인 데이터베이스 연결에 대한 임시 핸들이 되고, 하나의 XA Connection은 하나의 데이터베이스 세션에 해당한다.
XAResource는 분산 트랜잭션의 각 트랜잭션 브랜치를 조합하기 위해 트랜잭션 관리자에 의해 사용된다. 각각의 XAConnection으로부터 하나의 XAResource 객체를 얻어올 수 있고, 1:1 관계를 가진다.
따라서 하나의 XAResource 객체는 하나의 데이터베이스 세션에 해당된다. 하나의 XAResource 객체는 실행 중인 오직 하나의 트랜잭션 브랜치만 있을 수 있다. 그러나 실행 중인 트랜잭션과는 별개로 정지된 트랜잭션도 있을 수 있다. 각각의 XAResource 객체는 해당 세션에서 수행되며 시작, 종료, 준비, 커밋, 롤백 등의 기능이 있다.
각각의 트랜잭션 브랜치를 구분하기 위해 XID(트랜잭션 ID)를 사용하고, 하나의 XID는 트랜잭션 브랜치 ID와 분산 트랜잭션 ID로 구성된다.
JDBC 3.0 표준에서는 전역 트랜잭션과 지역 트랜잭션 사이에서 커넥션을 공유할 수 있고, 각각으로 변환할 수 있다.
일반적으로 커넥션은 다음의 세 가지 모드 중에 반드시 하나를 갖는다.
각 커넥션은 실행 상태에 따라 다음과 같이 세 가지 모드 사이에서 자동으로 바뀐다. 단, 커넥션이 초기화될 때에는 항상 NO_TXN 모드로 동작한다.
현재 모드 | NO_TXN으로 변환 | LOCAL_TXN으로 변환 | GLOBAL_TXN으로 변환 |
---|---|---|---|
NO_TXN | - | auto-commit 모드를 비활성화시키고 DML 문을 수행했을 때 | XAConnection에서 얻은 XAResource에 end() 메소드를 호출했을 때 |
LOCAL_TXN | DDL 문이 수행되거나 commit() 또는 rollback() 메소드를 호출했을 때 | - | 불가능 |
GLOBAL_TXN | XAConnection에서 얻은 XAResource에 end() 메소드를 호출했을 때 | 불가능 | - |
Tibero에서는 XA 표준에 따른 분산 트랜잭션 패키지인 com.tmax.tibero.jdbc.ext 내부에 다음과 같은 클래스를 지원한다.
TbXAConnection
TbXADataSource
TbXAException
TbXAResource
TbXid
본 절에서는 JDBC 2.0 표준 패키지에 정의된 XA 인터페이스의 구성요소를 설명한다.
javax.sql.XADataSource에 정의된 인터페이스는 다음과 같다.
public interface XADataSource { XAConnection getXAConnection() throws SQLException; XAConnection getXAConnection(String user, String password) throws SQLException; ... }
tbJDBC에서는 com.tmax.tibero.jdbc.ext.TbXADataSource 클래스로 제공된다. 또한 TbConnectionPoolDataSource를 확장하여 일반적인 DataSource에서 사용할 수 있는 커넥션의 특성을 사용할 수도 있다.
javax.sql.XAConnection에 정의된 인터페이스는 다음과 같다.
public interface XAConnection extends PooledConnection { javax.transaction.xa.XAResource getXAResource() throws SQLException; }
tbJDBC에서는 com.tmax.tibero.jdbc.ext.TbXAConnection 클래스로 제공된다.
javax.transaction.xa.XAResource에 정의된 인터페이스는 다음과 같다.
public interface XAResource { void commit(Xid xid, boolean onePhase) throws XAException; void end(Xid xid, int flags) throws XAException; void forget(Xid xid) throws XAException; int prepare(Xid xid) throws XAException; int rollback(Xid xid) throws XAException; void start(Xid xid, int flags) throws XAException; boolean isSameRM(XAResource xares) throws XAException; }
tbJDBC에서는 com.tmax.tibero.jdbc.ext.TbXAResource 클래스로 제공된다.
XAResource 인터페이스에서 설정할 수 있는 메소드는 다음과 같다.
메소드 | 설명 |
---|---|
Start | XID와 관련된 트랜잭션의 특정 브랜치를 시작하거나 이미 존재하는 트랜잭션을 재시작 또는 변경 사항을 조인한다. |
End | XID와 관련된 트랜잭션의 특정 브랜치에 대한 종료 상태(정상 또는 실패)를 알리거나 이미 존재하는 트랜 잭션을 멈춘다. |
Prepare | 현재의 트랜잭션 브랜치에서 수행된 변경사항을 적용하기 위해 준비한다. |
Commit | 현재의 트랜잭션 브랜치의 변화를 반영한다. |
Rollback | 현재의 트랜잭션 브랜치의 변화를 롤백시킨다. |
Forget | 리소스 매니저가 지정한 트랜잭션 브랜치를 무시하도록 한다. |
Recover | 현재 준비가 완료되었거나 수행이 끝난 트랜잭션 브랜치의 목록을 반환한다. |
isSameRM | 두 개의 XA 리소스 객체가 동일한 리소스 매니저에 속해 있는지의 여부를 반환한다. |
XID와 관련된 트랜잭션의 특정 브랜치를 시작하거나 이미 존재하는 트랜잭션을 재시작 또는 변경 사항을 조인시키기 위해 사용하는 메소드이다.
문법
void start(Xid xid, int flags)
파라미터
파라미터 | 설명 |
---|---|
xid | 현재 리소스 객체와 관련된 글로벌 트랜잭션의 ID이다. |
flags | flags 파라미터는 반드시 다음의 값 중의 하나여야 한다.
|
XID와 관련된 트랜잭션의 특정 브랜치에 대한 종료 상태(정상 또는 실패)를 알리거나 이미 존재하는 트랜잭션을 멈추기 위해 사용하는 메소드이다.
문법
void end(Xid xid, int flags)
파라미터
파라미터 | 설명 |
---|---|
xid | 현재 리소스 객체와 관련된 글로벌 트랜잭션의 ID이다. |
flags | flags 파라미터는 반드시 다음의 값 중의 하나여야 한다.
|
현재의 트랜잭션 브랜치에서 수행된 변경사항을 적용하기 위해 준비하는 과정으로 Two-phase commit 과정의 첫 번째 단계이다. 만약 분산 트랜잭션 내부에 오직 하나의 트랜잭션만 있는 경우라면 prepare()를 수행할 필요가 없다.
문법
int prepare(Xid xid)
파라미터
파라미터 | 설명 |
---|---|
xid | 현재 리소스 객체와 관련된 글로벌 트랜잭션의 ID이다. |
반환 값
이 메소드는 반드시 다음의 값 중의 하나를 반환한다.
반환 값 | 설명 |
---|---|
XAResource.XA_RDONLY | 현재의 트랜잭션 브랜치는 read-only 모드로 동작하므로 SELECT 문만 수행할 수 있다. |
XAResource.XA_OK | 현재의 트랜잭션 브랜치에서는 어떠한 수정 작업도 수행할 수 있다. |
현재의 트랜잭션 브랜치의 변화를 반영하는 과정으로 Two-phase commit 과정의 두 번째 단계이다. 단, 모든 트랜잭션 브랜치가 prepare를 완료한 후에 수행된다.
문법
void commit(Xid xid, boolean isOnePhase)
파라미터
파라미터 | 설명 |
---|---|
xid | 현재 리소스 객체와 관련된 글로벌 트랜잭션의 ID이다. |
isOnePhase | isOnePhase 파라미터에 설정된 값에 따라 다음과 같이 동작한다.
|
현재의 트랜잭션 브랜치의 변화를 롤백시킨다.
문법
void rollback(Xid xid)
파라미터
파라미터 | 설명 |
---|---|
xid | 현재 리소스 객체와 관련된 글로벌 트랜잭션의 ID이다. |
리소스 매니저가 지정한 트랜잭션 브랜치를 무시하도록 한다.
문법
void forget(Xid xid)
파라미터
파라미터 | 설명 |
---|---|
xid | 현재 리소스 객체와 관련된 글로벌 트랜잭션의 ID이다. |
현재 준비가 완료되었거나 수행이 끝난 트랜잭션 브랜치의 목록을 반환한다.
문법
Xid[] recover(int flag)
파라미터
파라미터 | 설명 |
---|---|
flag | flags 파라미터는 반드시 다음의 값 중의 하나여야 한다.
|
두 개의 XA 리소스 객체가 동일한 리소스 매니저에 속해 있는지의 여부를 반환한다.
문법
boolean isSameRM(XAResource aResource)
파라미터
파라미터 | 설명 |
---|---|
aResource | 현재 리소스 객체와 비교할 리소스 객체이다. |
트랜잭션 관리자는 XID 객체를 생성하고, 이를 이용하여 분산 트랜잭션의 브랜치를 관리한다. 각각의 트랜잭션 브랜치는 개별적으로 XID를 부여받는다.
XID는 다음의 정보를 포함한다.
Java 트랜잭션 관리자를 가리키며, NULL이 될 수 없다. 이 정보를 얻기 위한 메소드는 다음과 같다.
public int getFormatId()
동일한 분산 트랜잭션에 속하는 트랜잭션 브랜치의 경우 모두 같은 값을 가진다. 이 정보를 얻기 위한 메소드는 다음과 같다.
public byte[] getGlobalTransactionId()
이 정보를 얻기 위한 메소드는 다음과 같다.
public byte[] getBracheQualifier()
javax.transaction.xa.Xid에 정의된 인터페이스는 다음과 같다.
public TbXid(int formatId, byte[] globalId, byte[] branchId) throws XAException
tbJDBC에서는 com.tmax.tibero.jdbc.ext.TbXid 클래스로 제공된다.
다음은 두 개의 트랜잭션 브랜치로 이루어진 Two-phase 분산 트랜잭션 환경을 구현하는 순서이다.
트랜잭션 브랜치 #1을 시작한다.
트랜잭션 브랜치 #2를 시작한다.
트랜잭션 브랜치 #1에서 DML 문장을 수행한다.
트랜잭션 브랜치 #2에서 DML 문장을 수행한다.
트랜잭션 브랜치 #1의 트랜잭션을 종료한다.
트랜잭션 브랜치 #2의 트랜잭션을 종료한다.
트랜잭션 브랜치 #1을 준비한다.
트랜잭션 브랜치 #2를 준비한다.
트랜잭션 브랜치 #1을 커밋한다.
트랜잭션 브랜치 #2를 커밋한다.
다음의 예는 위 순서에 맞게 구현된 Java 프로그램 소스 코드이다.
import java.sql.*; import javax.sql.XAConnection; import javax.transaction.xa.XAResource; import com.tmax.tibero.jdbc.ext.*; public class TwoBranchXA { public static void main(String args[]) throws SQLException { createBaseTable(); try { // Create XADataSource instance TbXADataSource xads1 = new TbXADataSource(); xads1.setUrl("jdbc:tibero:thin:@localhost:7629:dbsvr"); xads1.setUser("tibero"); xads1.setPassword("tmax"); TbXADataSource xads2 = new TbXADataSource(); xads2.setUrl("jdbc:tibero:thin:@localhost:8629:dbsvr"); xads2.setUser("wrpark"); xads2.setPassword("tmax"); // Get the XA connection XAConnection xacon1 = xads1.getXAConnection(); XAConnection xacon2 = xads2.getXAConnection(); // Get the physical connection Connection conn1 = xacon1.getConnection(); Connection conn2 = xacon2.getConnection(); // Get the XA resource XAResource xars1 = xacon1.getXAResource(); XAResource xars2 = xacon2.getXAResource(); // Create the Xid Xid xid1 = createXid(1); Xid xid2 = createXid(2); // Start the resource xars1.start(xid1, XAResource.TMNOFLAGS); xars2.start(xid2, XAResource.TMNOFLAGS); // Execute SQL operations execute1(conn1); execute2(conn2); // End both the branches xars1.end(xid1, XAResource.TMSUCCESS); xars2.end(xid2, XAResource.TMSUCCESS); // Prepare the resource manager int pre1 = xars1.prepare(xid1); int pre2 = xars2.prepare(xid2); // Commit or rollback if (pre1 == XAResource.XA_RDONLY || pre1 == XAResource.XA_OK) xars1.commit(xid1, false); else xars1.rollback(xid1); if (pre2 == XAResource.XA_RDONLY || pre2 == XAResource.XA_OK) xars2.commit(xid2, false); else xars2.rollback(xid2); // Clear conn1.close(); conn1 = null; conn2.close(); conn2 = null; xacon1.close(); xacon1 = null; xacon2.close(); xacon2 = null; } catch (Exception se) { se.printStackTrace(); } } }