티스토리 뷰

728x90
반응형

 포스팅은 XADatasource를 활용한 2PC Commit과정을 살펴보겠습니다.


ejb모듈, TP에서 흔히 사용하는 XA를 통한 2PC의 구현 방법에 대해 간단한 source code를 통한 sample 입니다.

 

1. java code를 통한 DB 접근과 XA의 2pc구현 sample test

2. WAS를 통해 lookup을 통한 DB 접근과 XA의 2pc구현 sample test

를 통해서 XADatsource의 개념을 이해하고, 실제 어떻게 처리되고 구현되는지를 확인 할 수 있습니다.

 

1. XADatasource

XADatasource 는 쉽게 말해서 2 phase commit 을 통한 분산 트랜잭션 처리를 위한 표준입니다. 예를 들어, oracle tibero 간의 2단계 검정을 통해서 트랜잭션을 보장시켜줄 수 있습니다. XADatasource X-open에서 명시한 스펙이며, 트랜잭션이 보장된다는 것은 모든 트랜잭션이 commit되었을 때 전체가 commit되어야 하며, 하나라도 rollback되면 모두가 rollback되어야 한다는 원자성(atomicity)을 비롯한 일관성(consistency), 격리성(isolation), 지속성(durability)을 지켜야 합니다.

 

1.1 XADatasource Test 시나리오

- Oracle JDBC XA Driver를 이용해서 2PC를 구현하는 java code sample 테스트

- WAS(JEUS)에서 lookup 하여 XADatasource를 이용하여 2PC를 구현하는 jsp 샘플 테스트

 

1.2 TEST 환경

test eclipse + JEUS + oracle 을 연동하여 진행했으며, 소스 실행 시 JeusServer.log, 쿼리를 통한 table확인을 기반으로 확인하는 과정을 통해서 진행 하겠습니다.

eclipse버전: KEPLER  플러그인 : Eclipse(galileo)_Bridge_for_JEUS

링크 : http://technet.tmax.co.kr/kr/index.do 접속  -> 자료실 ->제품자료실 (링크주소는 변경될 수 있습니다.)

- Java : 1.6버전

- Jdbc 드라이버 : ojdb6.jar

- Oracle : 10g

- JEUS : 6.0.0.8

 

1.3 jdbc드라이버를 이용한 Java sample TEST 진행

jdbc드라이버를 이용해서 java source에의 DB로의 direct 접근을 통해 2pc를 구현하는 java sample code를 통해 TEST를 진행 합니다.  2pc를 구현 하는 java source 파일명은 OracleXARun.java이며, TEST를 위한 Table 명은 accountfrom , accountto 입니다.

1.3.1 table 생성 및 확인.

accountfrom table

SQL> create table accountfrom(accountno number, balance number, primary key(accountno));

Table created.

SQL> insert into accountfrom values(3000,30000);

1 row created.

SQL> select * from accountfrom;

 ACCOUNTNO    BALANCE

---------- ----------

      1000      10000

      3000      30000

 

accountto table

SQL> create table accountto (accountno number, balance number, primary key(accountno));

Table created.

 

1.3.2 OracleXARun.java 파일

- DB접속정보는 내부의 TEST DB를 이용 했으며, // DB Connection Info2 로 접속하여 확인.

- Code 중간에 주석으로 세부 설명을 해 놓았으며, accountfrom table update하고, accountto table insert 하는 sql문이 진행.

- 컴파일 하고 실행을 하면 accountrom table에서는 accountno 1000 column에서 balacne에서 10을 뺌.

- 컴파일 하고 실행을 하면 accountto table에는 accountno balance 100을 삽입.

import java.sql.*;

import javax.sql.*;

import java.util.*;

import oracle.jdbc.driver.*;

import oracle.jdbc.pool.*;

import oracle.jdbc.xa.OracleXid;

import oracle.jdbc.xa.OracleXAException;

import oracle.jdbc.xa.client.*;

import javax.transaction.xa.*;

import java.io.*;

class OracleXARun{

      // DB Connection Info1

      final static String DB_FROM_ADDR = "xxx.xxx.xxx.xxx";

      final static int DB_FROM_PORT = 1521;

      final static String DB_FROM_SID = "xxxx";

      final static String DB_FROM_USER = "scott";

      final static String DB_FROM_PASSWORD= "tiger";

     

      // DB Connection Info2

      final static String DB_TO_ADDR = "xxx.xxx.xxx.xxx";

      final static int DB_TO_PORT = 1521;

      final static String DB_TO_SID = "xxxx";

      final static String DB_TO_USER = "scott";

      final static String DB_TO_PASSWORD = "tiger";

      // main

      public static void main(String args[]){

             OracleXARun oxa = new OracleXARun();

             try{

                     oxa.XARunning();

             }catch(Exception e){

                     e.printStackTrace();

             }

      }

      void XARunning()

                     throws XAException,SQLException

                     {

             // step 1. open connection

             // step 1-1. create XA Data source (ResourceFactory)

             XADataSource xds1 =

                            getXADataSource(DB_FROM_ADDR,DB_FROM_PORT

                                            ,DB_FROM_SID,DB_FROM_USER,DB_FROM_PASSWORD);

             XADataSource xds2 =

                             getXADataSource(DB_TO_ADDR,DB_TO_PORT

                                            ,DB_TO_SID,DB_TO_USER,DB_TO_PASSWORD);

             // step 1-2. make XA connection (Transactiona Resource)

             XAConnection xaconn1 = xds1.getXAConnection();

             XAConnection xaconn2 = xds2.getXAConnection();

             // step 1-3. make connection (DB Connecction)

             Connection conn1 = xaconn1.getConnection();

             Connection conn2 = xaconn2.getConnection();

             // step 2. get XA Resource (XAResource)

             XAResource xar1 = xaconn1.getXAResource();

             XAResource xar2 = xaconn2.getXAResource();

             // step 3. generate XID (Transaction ID)

             // 같은 Global Transaction ID 가지고, 다른 Branch Transaction ID

             // 갖는 Transaction ID 생성한다.

             Xid xid1 = createXid(1);

             Xid xid2 = createXid(2);

             // step 4. xa start (send XA_START message to XAResource)

             xar1.start(xid1,XAResource.TMNOFLAGS);

             xar2.start(xid2,XAResource.TMNOFLAGS);

             // step 5. execute query(execute Transaction)

             // FROM DB ACCOUNT계좌에서 1000원빼고, TODB ACCOUNTNO 1000이고 BALANCE 1000 레코드를

             // insert한다.

             String sql ;

             Statement stmt1 = conn1.createStatement();

             sql = "update accountfrom set balance=balance-10 where accountno=1000";

             stmt1.executeUpdate(sql);

             sql = " insert into accountto values(100,100)";

             Statement stmt2 = conn2.createStatement();

             stmt2.executeUpdate(sql);

             // step 6. xa end (Transaction 종료되었음을 알린다.)

             xar1.end(xid1,XAResource.TMSUCCESS);

             xar2.end(xid2,XAResource.TMSUCCESS);

             // step 7. xa prepare (xa prepare 한다.)

             int prep1 = xar1.prepare(xid1);

             int prep2 = xar2.prepare(xid2);

             //

             // step 8-1. check prepare stat

             // 양쪽 prepare 성공 하였으면 commit 준비를 한다.

             // XA_RDONLY update 없이 select등의 Read Only 있는 Transaction

             // 성공하였을때,

             boolean docommit=false;

             if( (prep1 == XAResource.XA_OK || prep1 ==

                            XAResource.XA_RDONLY)

                            && (prep2 == XAResource.XA_OK || prep2 ==

                            XAResource.XA_RDONLY) )

             {

                     docommit = true;

             }

             if(docommit){

                     // XA_RDONLY 이미 commit 되어 있기 때문에 따로 commit하지 않는다.

                     if(prep1 == XAResource.XA_OK)

                            xar1.commit(xid1,false);

                     if(prep2 == XAResource.XA_OK)

                            xar2.commit(xid2,false);

             }else{

                     // roll back 하는 부분

                     if(prep1 != XAResource.XA_RDONLY)

                            xar1.rollback(xid1);

                     if(prep2 != XAResource.XA_RDONLY)

                            xar2.rollback(xid2);

             }

             // step 9. close connection

             conn1.close();

             conn2.close();

             xaconn1.close();

             xaconn2.close();

             conn1 = null;

             conn2 = null;

             xaconn1 = null;

             xaconn2 = null;

                     }// XARun

      Xid createXid(int bids) throws XAException{

             byte[] gid = new byte[1]; gid[0] = (byte)9;

             byte[] bid = new byte[1]; bid[0] = (byte)bids;

             byte[] gtrid = new byte[64];

             byte[] bqual = new byte[64];

             System.arraycopy(gid,0,gtrid,0,1);

             System.arraycopy(bid,0,bqual,0,1);

             Xid xid = new OracleXid(0x1234,gtrid,bqual);

             return xid;

      }// createXid

      XADataSource getXADataSource(String dbAddr,int

                     port,String sid

                     ,String userId,String password)

                                    throws SQLException,XAException

                                    {

             OracleDataSource oxds = new OracleXADataSource();

             String url = "jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1521:xxxx";

             oxds.setURL(url);

             oxds.setUser(userId);

             oxds.setPassword(password);

             return (XADataSource)oxds;

                                    }// getXADataSource

}// class OracleXARun

 

1.3.3 결과 화면

해당 접속 DB에서 sql 문을 통해서 확인 함.

SQL> select * from accountfrom;

 

 ACCOUNTNO    BALANCE

---------- ----------

      1000       9990

      3000      30000

 

SQL> select * from accountto;

 

 ACCOUNTNO    BALANCE

---------- ----------

       100        100

 

 

1.4 WAS에서 lookup을 통해서 2cp구현 TEST

Test를 위해 필요한 table은 동일한 DB 인스턴스의 동일한 table을 대상으로 하였습니다. 이번 chapterJEUS(WAS)를 통한 DB접속으로 구현하며, UserTransaction 인터페이스를 통해서 lookup을 합니다. 이 외의 sql문은 java code와 동일하며 실행결과는 WAS를 이용했기 때문에 JEUS서버 로그를 통해서 좀더 세부적으로 파악합니다.

1.4.1 XAtest.jsp

- WAS벤더마다 ctx.lookup()의 파라미터 값이 다르며, JEUSjava:comp/UserTransaction을 사용 함.

- update insert 쿼리문은 동일하나 java와 식별을 위해 value 만 다르게 설정 함.

- accountfrom table에서 account 1000 column에서 balacne 30을 빼고, 300, 400 accountto table에 삽입.

- JNDI : OracleTXDS

<%@ page contentType="text/html;charset=euc-kr"%>

<%@ page import= "javax.naming.*, javax.transaction.* ,java.sql.* ,javax.management.ObjectName ,javax.management.MBeanServerConnection ,javax.sql.* ,java.util.*" %>

<%!

      // DB Connection Info

      final static String DB_FROM_DATASOURCE = "OracleTXDS";

      final static String DB_TO_DATASOURCE = "OracleTXDS";

 

      //final static String DB_TO_DATASOURCE= “OracleTXDS_Chaeju”;

      void JTARun() throws Exception {

 

             // step 1. JNDI Lookup and get UserTransaction Object

             Context ctx = null;

             Hashtable env = new Hashtable();

             // Parameter for JEUS

             env.put(Context.INITIAL_CONTEXT_FACTORY, "jeus.jndi.JNSContextFactory");

             //env.put(Context.URL_PKG_PREFIXES, "jeus.jndi.jns.url");

             env.put(Context.PROVIDER_URL, "nrson-PC");

             ctx = new InitialContext(env);

             System.out.println("Context Created :" + ctx);

             // step 2. get User Transaction Object

             UserTransaction tx = (UserTransaction) ctx.lookup("java:comp/UserTransaction");

             // step 3 start Transaction

             System.out.println("Start Transaction : " + tx);

             tx.begin();

             try {

                     // step 4. doing query

                     // step 4-1. get Datasource

                     DataSource xads1 = (DataSource) ctx.lookup(DB_FROM_DATASOURCE);

                     DataSource xads2 = (DataSource) ctx.lookup(DB_TO_DATASOURCE);

                     System.out.println("Datasource from :" + xads1);

                     System.out.println("Datasource to :" + xads2);

                     // step 4-2. get Connection

                     Connection conn1 = xads1.getConnection();

                     Connection conn2 = xads2.getConnection();

                     conn1.setAutoCommit(false);

                     conn2.setAutoCommit(false);

                     System.out.println("Connection from :" + conn1);

                     System.out.println("Connection to :" + conn2);

                     // step 4-3 execute query

                     Statement stmt1 = conn1.createStatement();

                     String sql = "update accountfrom set balance=balance-30 where accountno=1000";

                     stmt1.executeUpdate(sql);

                     System.out.println("Statement 1 passed");

                     Statement stmt2 = conn2.createStatement();

                     sql = "insert into accountto values(200,300)";

                     stmt2.executeUpdate(sql);

                     System.out.println("Statement 2 passed");

                     // step 4-4. close connection

                     stmt1.close();

                     conn1.close();

                     stmt2.close();

                     conn2.close();

                     System.out.println("connection closed");

                     System.out.println("tx :" + tx);

                     tx.commit();

                     System.out.println("Transaction Commited");

             } catch (Exception e) {

                     System.out.println("Transaction rolled back due to :" + e);

                     tx.rollback();

             }// try-catch

             System.out.println("End Transaction");

             ctx.close();

      }// JTARun

      %>

<%

      JTARun();

%>

 

1.4.2 JEUSMain.xml

- JEUS에서 DB와 연동되는 부분

- XADatasource를 구현 가능하게 하는 class를 선언.

- 소스에서 명시한 JNDI를 설정.

<resource>

       <data-source>         

          <database>

            <vendor>oracle</vendor>

            <export-name>OracleTXDS</export-name>

            <data-source-class-name>oracle.jdbc.xa.client.OracleXADataSource</data-source-class-name>

           <data-source-name>oracle.jdbc.xa.client.OracleXADataSource</data-source-name>

            <data-source-type>XADataSource</data-source-type>

            <database-name>xxxx</database-name>

            <port-number>1521</port-number>

            <server-name>xxx.xxx.xxx.xxx</server-name>

            <user>scott</user>

            <password>tiger</password>

            <property>

               <name>driverType</name>

               <type>java.lang.String</type>

               <value>thin</value>

            </property>

            <connection-pool>

               <check-query>SELECT 1 FROM DUAL</check-query>

               <check-query-period>10000</check-query-period>

              <pooling>

                  <min>2</min>

                  <max>30</max>

                  <step>1</step>

                  <period>3600000</period>

               </pooling>

               <wait-free-connection>

                  <wait-time>5000</wait-time>

                  <enable-wait>true</enable-wait>

               </wait-free-connection>

            </connection-pool>

         </database>

        </data-source>

    </resource>

 

1.4.3 결과 화면

Eclipse+JEUS를 통해서 위의 jsp를 호출 하는 화면.



Table java sample때와 동일한 table이고 accountfrom table에서 BALANCE 30이 마이너스되고, accountto에서 400, 700이 삽입된 것을 확인.

SQL> select * from accountfrom;

 

 ACCOUNTNO    BALANCE

---------- ----------

      1000       9960

      3000      30000

 

SQL> select * from accountto;

 

 ACCOUNTNO    BALANCE

---------- ----------

       200        300

       100        100

       400        700

 

JeusServer.log

- 2pc가 정상적으로 구현 되었을 때 commit 이후에 System.out.println()으로 인한 message 확인 .

- 정상적으로 transaction End 되었다는 message 확인 함.

Datasource from :OracleTXDS;shareable=true

Datasource to :OracleTXDS;shareable=true

[2014.02.05 15:50:39][2][b288] [container1-66] [TM-6216] xa session started : xid

[2608.00C0A8465A29415100CFFB660000000000000000.00AB9736CD00C0A8465A29415100CFFB6600C0A84

65A29415100CFFB660000000000000000@jeus.transaction.XidImpl@ffffd9d0]

Connection from :JeusConnection@746d1683[PHY_ID=OracleTXDS-1,actual=oracle.jdbc.driver.LogicalConnection@25e12e2c]

Connection to :JeusConnection@629e5e21[PHY_ID=OracleTXDS-1,actual=oracle.jdbc.driver.LogicalConnection@25e12e2c]

Statement 1 passed

Statement 2 passed

connection closed

tx :jeus.transaction.UserTransactionImpl@4a744a4d

[2014.02.05 15:50:40][2][b288] [container1-66] [TM-6205] xa session ended

Transaction Commited

End Transaction

[2014.02.05 15:52:17][2][b288] [container1-58] [WEB-2207] [Tmax] (jeus_jspwork._600_XATest_5fjsp) destroyed

Context Created :javax.naming.InitialContext@16315e08

Start Transaction : jeus.transaction.UserTransactionImpl@4a744a4d

Datasource from :OracleTXDS;shareable=true

 

 

1.5 주의 사항 및 결론

1.5.1 동일한 primary key 값을 접근 할 경우

1passed 후에 Sql Exception으로 인해 rollback 이 된 것을 확인 할 수 있습니다. unique constraint 으로 알 수 있듯이 이미 accountno 라는 primary key에 동일한 값이 삽입되어 있기 때문에 다음과 같은 에러가 발생 합니다.

Context Created :javax.naming.InitialContext@521e7f21

Start Transaction : jeus.transaction.UserTransactionImpl@4a744a4d

Datasource from :OracleTXDS;shareable=true

Datasource to :OracleTXDS;shareable=true

[2014.02.05 16:02:32][2][b288] [container1-60] [TM-6216] xa session started : xid[2608.00C0A8465A29415100CFFB660000000000000003.00AB9736CD00C0A8465A29415100CFFB6600C0A846

5A29415100CFFB660000000000000003@jeus.transaction.XidImpl@ffffd9d0]

Connection from :JeusConnection@68f55ff2[PHY_ID=OracleTXDS-2,actual=oracle.jdbc.driver.LogicalConnection@456e3eb3]

Connection to :JeusConnection@48d67d61[PHY_ID=OracleTXDS-2,actual=oracle.jdbc.driver.LogicalConnection@456e3eb3]

Statement 1 passed

Transaction rolled back due to :java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (SCOTT.SYS_C0094516) violated

[2014.02.05 16:02:32][2][b288] [container1-60] [TM-6205] xa session ended

End Transaction

[2014.02.05 16:02:32][1][b288] [container1-60] [MGR-0396] resources not closed :

JeusConnection@68f55ff2[PHY_ID=OracleTXDS-2,actual=oracle.jdbc.driver.LogicalConnection@456e3eb3]

Thread in use : http1-w03 [container1-60]

JeusConnection@48d67d61[PHY_ID=OracleTXDS-2,actual=oracle.jdbc.driver.LogicalConnection@456e3eb3]

Thread in use : http1-w03 [container1-60]

RequestURI : /Tmax/XATest.jsp

 

1.5.2 결론 및 정리

일반적으로 transaction은 아래 와 같은 logic으로 처리 됩니다.

Begin Transaction

If(error) then rollback

Prepare transaction

If(prepare transaction failed) then rollback

 

Else commit transaction

End Transaction

 

이런 transaction 의 기본 logic을 이해한 상태에서, sample test 문서를 활용하면 XADatasource transaction의의 기본적인 관계와 개념을 이해하기 쉽습니다.  XA ejb모듈이나 tp에서 Global Transaction 을 통한 transaction 보장을 위해 주로 사용됩니다. 이와 연관된 부수적인 개념 부분은 본 문서에 많이 생략되었으나, 소스코드를 한번 실행해서 실제 어떻게 처리되는지를 확인 하는데 큰 도움이 될 것이라고 생각합니다.

고맙습니다.

728x90
반응형