티스토리 뷰
본 포스팅은 XADatasource를 활용한 2PC Commit과정을 살펴보겠습니다.
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)을 지켜야 합니다. - Oracle JDBC XA Driver를 이용해서 2PC를 구현하는 java code sample 테스트 - WAS(JEUS)에서 lookup 하여 XADatasource를 이용하여 2PC를 구현하는 jsp 샘플 테스트 본 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 입니다. 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. - 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 해당 접속 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을 대상으로 하였습니다. 이번 chapter는 JEUS(WAS)를 통한 DB접속으로 구현하며, UserTransaction 인터페이스를 통해서 lookup을 합니다. 이 외의 sql문은 java code와 동일하며 실행결과는 WAS를 이용했기 때문에 JEUS서버 로그를 통해서 좀더 세부적으로 파악합니다. - WAS벤더마다 ctx.lookup()의 파라미터 값이 다르며, JEUS는 java: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(); %> - 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> 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 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 동일한 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 일반적으로 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 보장을 위해 주로 사용됩니다. 이와 연관된 부수적인 개념 부분은 본 문서에 많이 생략되었으나, 소스코드를 한번 실행해서 실제 어떻게 처리되는지를 확인 하는데 큰 도움이 될 것이라고 생각합니다. 고맙습니다.
'④ 미들웨어 > ⓙ JEUS' 카테고리의 다른 글
[JEUS6] JMX Guide (0) | 2018.07.23 |
---|---|
[JEUS7] 운영자 모니터링 (0) | 2018.07.20 |
[JEUS] DBPool 동작 방식 (0) | 2018.07.18 |
[JEUS] JSP Compile 시 code too large 에러 발생하는 경우 (0) | 2018.07.17 |
[Web Application Server] JEUS7 중복로그인 방지기능 (2) | 2018.07.10 |
- Total
- Today
- Yesterday
- MSA
- SA
- k8s
- 마이크로서비스
- apache
- 오픈스택
- node.js
- openstack tenant
- API Gateway
- Docker
- kubernetes
- wildfly
- nodejs
- 마이크로서비스 아키텍처
- Architecture
- aws
- openstack token issue
- git
- OpenStack
- JBoss
- 아키텍처
- TA
- JEUS7
- Da
- webtob
- SWA
- aa
- JEUS6
- 쿠버네티스
- jeus
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |