SSISO Community

시소당

자바로 구현하는 트렌젝션 프로그래밍(3)

자바스터디 네트워크 [www.javastudy.co.kr]

조대협 [bcho_N_O_SPAM@j2eestudy.co.kr]

 

 

1.   EJB Transaction Model

 

지금까지 트렌젝션이 무엇인지, 그리고 Java에서 어떤식으로 트렌젝션이 구현되는지에 대해서 살펴보았다. 이번에는 J2EE 핵심 기술이라고도 있는 EJB에서의 트렌젝션 처리에 대해서 알아보기로 하자.

 

Declarative transaction demarcation (선언적 트렌젝션 정의)

 

EJB에서의 트렌젝션은 앞에서 우리가 봐왔던 복잡한 트렌젝션 처리들을 EJB내부에서 대부분 EJB Container 통해서 자동으로 처리해주도록 되어 있다. EJB에서는 트렌젝션 자동 처리를 위해서 몇가지 트렌젝션 모델을 미리 정해놓고 있는데, 이를 Declarative transaction이라고 하고, EJB Deployment Descriptor에만 내용을 정의해놓으면 간단하게 트렌젝션을 핸들링할 있다. 그럼 지금 부터, Declarative Transaction 종류와, 내용에 대해서 살펴보도록 하자

1) Required:

 

Required 정의된 메소드들은 모두 트렌젝션내에서 수행되게 된다. 해당 메소드가 CALL 되었을때, 이미 트렌젝션이 수행되고 있을경우에는 트렌젝션이 Propagation 되서, 트렌젝션 내에서 수행이 되는 것이고, 수행중인 트렌젝션이 없을 경우에는 새로운 트렌젝션을 만들어서, 새로운 트렌젝션내에서 메소드를 수행한다.

 

Required 속성을 사용하면 수행되는 액션이 트렌젝션의 보호아래서 수행되기 때문에, 데이타를 바꾸는 작업(update,delete)중에 유용하게 사용될 있다.

2) Required New:

 

Required New 정의된 메소드는 언제나 새로운 트렌젝션내에서 수행하게 된다. 해당 메소드가 호출 되었을때, 호출한 클라이언트가 트렌젝션을 시작하지 않은 상태이면 ( 트렌젝션 내가 아니라면) 새로운 트렌젝션을 시작하게 된다.

 

반대로, 만약에 클라이언트가 트렌젝션을 시작해서, 해당 메소드가 호출되는 시점이 트렌젝션중이라면, 이미 수행중인 트렌젝션을 잠시 중지하고, 호출된 메소드를 처리하기 위한 새로운 트렌젝션을 처리하여, 메소드가 수행된후에 이전에 중지된 트렌젝션을 다시 수행한다.

 

이렇게 되면, 만약 Required New 속성으로 지정된 트렌젝션은 외부 트렌젝션과 상관없이 작동이 된다. 예를 들어 설명하자.

TX_A_BEGIN

DoSomething1

 

RequiredNewMethod(); // Required New – TX_B

 

DoSomething2

TX_A_END

 

TX_A 안에서 RequiredNewMethod()라는 메소드가 Requried New라는 속성으로 정의되었을 경우, DoSomething1 까지 수행한후, TX_A SUSPEND한다음에, RequiredNewMethod()라는 메소드 안에 정의된 내용들을 새로운 트렌젝션 TX_B 내에서 수행하고, 수행된 내용을 commit한다. 그리고 TX_A 복귀하여, RESUME한후, DoSomething2 수행한후, 트렌젝션 A 끝낸다.

 

TX_B TX_A 안에 포함된 형태이기는 하지만, TX_B rollback되었다고, TX_A rollback되지는 않는다(failure localized).

반대로, DoSomething2에서 TX_A rollback되었다고, TX_B rollback되지 않는다. 이런 특성은 log 남기는 기능등에서 유용하게 사용될 있다.  트렌젝션내에 포함은 되어 있지만 전혀 별도의 트렌젝션으로 수행이 되는 것이다.

 

대신 무조건 새로운 트렌젝션을 생성하기 때문에, 그에 따른 시스템 오버해드가 있기 때문에 남용할만한 속성은 아니다.

 

 3) Supports

 

Support 속성으로 정의된 메소드는 호출하는 클라이언트의 트렌젝션 속성을 그대로 이어받는다. Support 속성으로 정의된 메소드를 클라이언트가 트렌젝션 범위 내에서 호출 했으면 메소드도 같은 트렌젝션 범위내에서 수행이되고, 만약 클라이언트가 트렌젝션 없이 메소드를 호출했으면 마찬가지로 해당 메소드 역시 트렌젝션이 없이 수행이 된다.

 

4) NotSupported:

 

NotSupported 속성으로 정의된 메소드는 트렌젝션범위 밖에서 수행이 된다. 호출하는 클라이언트가 트렌젝션을 가지고 있지 않고 메소드를 호출하였다면 마찬가지로 트렌젝션이 없이 호출되나, 만약에 클라이언트가 트렌젝션 범위 내에서 NotSupported 속성으로 정의된 메소드를 호출하면, 기존의 트렌젝션을 suspend한후, 메소드의 내용을 처리한후에 다시 기존의 트렌젝션을 resume하여 기존의 트렌젝션을 마저 수행하게 된다.

5) Mandatory:

 

Mandatory 속성으로 정의된 메소드는 트렌젝션 SCOPE내에서 실행되어야 한다. Mandatory 정의된 메소드는 메소드를 호출하는 클라이언트에서 트렌젝션을 시작해서, 메소드가 트렌젝션 범위내에서 수행되도록 해야한다.

 

만약 Mandatory 정의된 메소드를 트렌젝션을 시작하지 않은 상태로 호출할 경우에는 TransactionRequiredException이나 TranscationRequredLocalException이라는 에러를 내게 된다.

6) Never:

 

Never 속성으로 정의된 메소드는 기존 트렌젝션 범위내에서 실행될 없으며, 메소드 자체도 새로운 트렌젝션을 생성하지 않는다. Never 정의된 메소드는 트렌젝션 범위내에서 수행될 없다.

 

속성은 client managed transaction 수행하지 못하게 서버쪽에 명시적으로 정의해줄 있으며, 해당 메소드가 다른 트렌젝션에 포함되지 않도록 정의할때 유용하게 사용된다.

 

그러면 지금까지 살펴본Declarative Transaction 속성들을 간단하게 표로 정리해보도록 하자.

 

Transaction

Attribute

Client’s

Transaction

EJB Method’s

Transaction

Comment

Required

NO_TRAN

NEW TRAN

 

TRAN A

TRAN A

 

Required New

NO_TRAN

NEW TRAN

 

TRAN A

NEW TRAN

 

Support

NO_TRAN

NO_TRAN

 

TRAN A

TRAN_A

 

Not Supported

NO_TRAN

NO_TRAN

 

TRAN A

NO_TRAN

 

Mandatory

NO_TRAN

Error

 

TRAN A

TRAN A

 

Never

NO_TRAN

NO_TRAN

 

TRAN A

Error

 

 

( Client’s Transation 트렌젝션을 시작하는 쪽에서의 Transaction 상태를 나타낸다. NO_TRAN 트렌젝션이 없이 시작하는 경우를 이야기 한다. EJB Method’s Transaction 트렌젝션 속성과 Client’s Transaction 따라서, EJB Method 어떤 트렌젝션으로 실행되는지를 나타낸다. TRAN_A Client 트렌젝션을 그대로 이어받은 경우이며, NEW_TRAN Client 트렌젝션과 상관없이 새롭게 트렌젝션을 생성해서 수행하는 경우를 나타내낸다.)

Transaction 속성들의 사용

 

그러면, Declarative Transaction Model들은 어떤 상황에서 사용이 될까? 앞에서도 중간 중간  언급 했지만, 이해를 돕기 위해서, 각각의 트렌젝션 모델이 유용하게 사용될 있는 내용을  간단하게 정리해보자

o        DATA READ할때 – Supports

o        DATA UPDATE할때 – Required

o        트렌젝션을 지원하지 않는 Resource 사용하고자 할때 – NotSupported

참고로 MDB (Message Driven Bean)에서는 Required Not Supported 두가지 속성만이 사용 가능하다.

 

EJB Deployment Descriptor에서 Transaction 속성 정의

 

이렇게 정의된 트렌젝션 속성들을 실제로 EJB 적용하기 위해서는 EJB Deployment Descriptor ejb-jar.xml EJB 메소드별로 정의할 있으며, 형식은 다음과 같다.

 

 

ejb-jar.xml에서 <assembly-descriptor> 아래의 <container-transaction> 속성을 아래와 같이 정의 한다.

 

<container-transaction>

<method>

 <ejb-name>EJBNAME</ejb-name>

 <method-name>EJBMETHOD_NAME</method-name>

</method>

<trans-attribute>여기에 앞에서 설명한 트렌젝션 속성을 쓴다.</trans-attribute>

</container-transaction>

 

 

Bean Managed Transaction

 

지금 까지는 J2EE에서 미리 지정해놓은 Declarative Transaction Model 대해서 살펴보았다. 이외에도 EJB에서는 EJB Container 아니라, 직접 사용자가 프로그래밍을 통해서 Transaction 관리할 있는데, 이를 BMT (Bean Managed Transaction)이라고 한다.

 

Bean Managed Transaction, email이나, file등과 같이 실제적으로 트렌젝션 기능을 지원하지 않는 resource 대해서, 트렌젝션 처리를 하도록 임의적으로 구현하거나, 내지는 stateful session bean등에서, 하나의 메소드가 아니라, 여러개의 메소드에 걸쳐서, Transaction 진행하고자 할때, 매우 유용하게 사용할 있다.

 

Bean Managed Transaction 프로그래밍 하기 위해서는 직접 EJB Context 부터, UserTransaction Context 얻어서 Transaction 관리해야 하는데. 절차와 방법은 간단하다.

 

먼저, javax.transaction.UserTransaction EJB Context 부터 얻어온다.

다음 얻어온 UserTransaction 객체의 begin 메소드를 통해서 트렌젝션을 시작한다.

다음 트렌젝션 관련된 명령을 수행한다음, 트렌젝션이 끝났으면, commit 오류가 발생한경우에는 rollback 하면된다.

 

그러면 간단한 샘플 코드를 통해서 어떻게 Bean Managed Transaction 구현되는지 살펴보기로 하자.

 

// Bean Managed Transaction Session Bean

import javax.transaction.*;

  :

 

public class BeanManagedTransaction implements SessionBean{

 SessionContext ctx = null;

 

 public void setSessionContext(SessionContext ctx){

  this.ctx = ctx;

 }// setSessionContext

 

 public void startTransaction(){

  UserTransaction utx = ctx.getTransaction();

  utx.begin(); // 여기서 트렌젝션을 시작한다.

  }

 

 public void doSomethingA() throws Exception{

      : 

  try{

    // do something about db transactions...

   

  }catch(Exception e){

       :

    UserTransaction utx = ctx.getTransaction();

    utx.rollback();

    throw new SomeException();

  }finally{

    conn.close();

  }// try-catch

 }// doSomethingA

 

 public void doSomethingB() throws Exception{

  // simliar to doSomethingA

  // do something about db transactions..

 }// doSomethingB

 

 

 public void endTransaction(){

  UserTransaction utx = ctx.getTransaction();

try{

     :

    utx.commit(); // 트렌젝션을 끝내고, 내용을 commit한다.

  }catch(Exception e){

    utx.rollback();

  }

 }

 

}// class BeanManagedTransaction

 

// in client code

 

BeanMangedTransaction bmt = …..;

 :

try{

bmt.startTransaction();

bmt.doSomethingA();

bmt.doSomethingB();

bmt.endTransaction();

}catch(Exception e){

  // transaction has failed

}

 

예제는 Session Bean에서, 트렌젝션을 시작하고, 두개의 메소드를 통해서 DB작업을 한후에, 트렌젝션을 종료하는 로직을 간단하게 표현한 내용이다. (위에는 Session Bean 클래스, 아래는 EJB 호출하는 Client클래스 내용이다,)

 

Client 에서 BeanManagedTransaction SessionBean 객체 bmt 생성해서이 내용들을 모두 호출 하는데, 먼저 startTransaction에서 User Transaction 생성하고, doSomethingA doSomethingB에서, DB작업을 수행한다. 수행중에 만약 에러가 발생하면, UserTransactionContext 얻어서, rollback 하고, Exception Throw해서, 진행중인 Transaction stop하도록 한다.트렌젝션이 끝나면, commit 실행하여, 내용을 반영한다. 잘보면 하나의 Method 아니라, 4개의 Method 걸쳐서 트렌젝션이 수행되고 있다. 

 

 여기서 주의할점중의 하나가 Session Beans Stateful Session Bean이라는 것이다. Stateless 경우에는 상태가 유지가 되지 않기 때문에, 여러 메소드를 통해서 트렌젝션을 수행할 경우에는 제대로 수행이 되지 않는다.

 

Bean managed transactions 속성은 ejb-jar.xml 정의해줘야하는데, 예제에서사용한 BeanManagedTransaction Bean 경우에는 다음과 같이 transaction-type Bean으로 지정하면 된다.

 

<ejb-jar>

 :

  <session>

    <display-name>BeanManagedTransaction</display-name>

    <ejb-name>BeanManagedTransaction</ejb-name>

       :

    <session-type>Stateful</session-type>

    <transaction-type>Bean</transaction-type>

     :

  </session>

 

 

Bean Managed Transaction 프로그래밍을 할때는 몇가지 주의할점이 있는데, 첫번째는 앞에서도 설명했듯이, 여러 Method 걸쳐서 트렌젝션이 수행될때는 Stateful Session Bean 만을 사용해야 한다. Stateless Session Bean 경우에는 시작된 User Transaction 반드시 시작된, 메소드 안에서만 종료되어야 한다.

 

 또한 Bean Managed Transaction 모든 종류의 Bean에서 가능한것이 아니라Session Bean Message Driven Bean에서만 사용이 가능하다. Entity Bean에서는 불가능하다.

 

Transaction in Entity Beans

 

그렇다면, 여기서 자연스럽게 하나의 질문이 생길 있는데, Entity Bean에서는 어떻게 Transaction 처리하는지 살펴볼 필요가 있다.

 

Entity Beans에서는 ejbLoad, ejbStore 대한 처리를 Declarative Transaction Model 중에서, Support 속성으로 처리하게 되어 있다. Entity Bean 호출하는 Client Transaction 그대로 사용하여 EJB 호출하게 된다.

 

 Client Transaction A 내에서 사용되고 있으면, 그에 의해 호출되는 Entity Bean ejbLoad ejbStore Transaction A내에서 실행된다. 그럼 만약에 Client Transaction없이 수행될때는 ,역시 Transaction 없이 실행이 된다.

이런 경우 몇가지 문제를 일으킬 있는데, Bean 데이타를 기록하는 과정이 Transaction 없이 수행되기 때문에, Entity Bean ejbStore등을 이용해서 Data write하는 과정에서, 다른 Entity Bean 동시에 Data Writing할때 데이타가 잘못 들어가거나, 또는 다른 Client에서 같은 Data write할때고 같은 문제를 발생시킬 있다. 일반적으로 트렌젝션없이 수행될때 나올 있는 문제들이 그대로 발생한다. ( 트렌젝션이 보장이 안되는 상황이 발생한다.)

 

그렇다면 Entity Bean 실행되는 Transaction 속성이 Required 되지 않을까 하는 생각을 있다. Required 속성이면 호출하는 client transaction 없을때 새로운 transaction 만들어서 내에서 데이트 작업을 수행한다.

알고 있듯이 Entity Bean Activation, Passivation이라는 작업이 있다, ( 내용에 대해서는 알고 싶다면, EJB관련 서적을 미리 참고하기 바란다.) 그리고 WAS 따라서 내용을 별도로 Caching하기도 한다. Entity Bean Passivation 될때, Entity Bean Client 다른 Transaction으로 수행되고 있다면, EJB 변경된 내용을 아직 client 작업이 아직 끝나지 않았음에도 DB writing하고 commit 해버린다.

 

예를 들어 설명해보자, 은행 계좌 이체를 수행하는 client CA 있다. ClientCA EntityBean EA 호출하여 값을 설정한후, doSomething이라는 함수를 호출하여 어떤 작업을 수행한후, 다신 EntityBean EA 호출하여 DB값을 업데이트하는 시나리오를 가지고 있다고 하자.

 

그런데, EntityBeanEA update하던중에, Entity Bean EA Passivation 일어났다고 하자, 그러면 EJB 내용은 DB update 될테고, Entity Bean EA 새로운 트렌젝션을 만들어서 수행이 되기 때문에, 내용을 DB commit해버린다.

 

그후, client CA에서 doSomething이라는 함수를 이용하여 무엇인가를 실행하려고 할때,  doSomething으로의 call 실패했을 경우 어떻게 될까? Entity Bean EA에서 이미 commit 해버렸기 때문에, 의도하지 않은 데이타가 들어갈 있다. 실제로 프로그램을 작성한 사람은 완전하게 client CA call 끝나고, 내용을 DB 반영하려고 했는데도, passivation 의해서 DB 업데이트가 발생해 버린다.

 

Client CA

Entity Bean EA

doSomething

DB

When failed faield?

When passivated passivated

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


이런 이유 때문에 EJB Entity Bean Container Management Persistance(CMP) 사용할 경우에는 가급적이면 해당 EJB Entity Bean 호출 하는 client transaction 가지고 호출하도록 해야 한다.

 

EJB에서 Transaction 관련 API

 

EJB에서는 Transaction 관련하여 몇가지  Method 사용한다, 알아놓으면 매우 편리하기 때문에, 하나씩 집어 보도록 하자.

 

1) setRollbackOnly()/getRollbackOnly()

setRollbackOnly()라는 메소드를 호출하면, 해당 Transaction 경우에는 commit 되지 않는다. 트렌젝션에서 MA,MB,MC라는 세개의 메소드를 호출하는 도중에, MB에서 setRollbackOnly메소드가 호출 되면, 순간 트렌젝션이 rollback 되는게 아니라, MC까지 수행하고, Transaction 마지막 순간에, rollback 한다.

 setRollbackOnly 이와 같이, rollback 해야하지만 뒤에 있는 Logic까지 수행해야하는 경우에, Transaction 마지막 단계에 rollback 하도록 mark해주는 역할을 한다.

 

메소드는 Container Management Transaction에서만 사용할 있으며,  그중에서도 Required,RequiresNew ,Mandatory 속성에서만 사용할 있다.

 

그리고, rollback 마크된 여부를 파악하기 위해서, getRollbackOnly()라는 메소드를 지원한다.

 

2) afterBegin,afterCompletion,beforeCompletion

 

EJB에서는 Session Bean 이용해서 Container Managed Transaction 구현할때, javax.ejb.SessionSynchronization interface 이용해서, 몇가지 call back event 메소드를 제공한다. 트렌젝션 수행 상태, 시작/Commit 전후에 어떤 특별한 액션을 취하고 싶다면 interface implement하면 된다.

 

public interface javax.ejb.SessionSynchronizaton{

    public void afterBegin() throws EJBException,RemoteException;

    public void afterCompletion(boolean commited) throws EJBException,RemoteException;

    public void beforeCompletion() throws EJBException,RemoteException;

}// SessionSynchronization

.

afterBegin() 새로운 트렌젝션이 생기고, 트렌젝션을 시작하자 마자, 맨처음 불려지는 메소드이다. beforeCompletion commit 되기 전에 최정적으로 실행되는 메소드이다.

afterCompletioncommit/rollback 되지 마자 수행되는데, commit 된경우에는 commited value true, 반대의 경우에는 false 체크된다. (afterCompletion 트렌젝션이 끝난후에 실행되기 때문에 트렌젝션 Context 밖에서 수행된다.)

 

메소들은 주로, Data sync,caching,format 체크등에 유용하게 사용할 있다.

Programming Two-Phase Commit using EJB

 

지금부터 웹로직과 EJB 이용해서 간단하게 두개의 Oracle DB Two Phase commit 구현해보도록 하자. 우리가 구현할 내용은 지난주에 구현했던과 같은 내용을 이번에는 EJB 구현하는 내용이다.

 

A계좌에서 계좌이체를 해서 B 계좌로 금액을 옮기는 내용을 Entity Bean Session Bean 이용해서 구현할것이다.

 

1) ORACLE 해당 개정에 XA 권한 주기

step 1. sql plus 시스템 사용자로 로그인한다.

% sqlplus  sys/CHANGE_ON_INSTALL@<DATABASE ALIAS NAME>

 

step 2. 사용자 개정에 XA 있는 권한을 부여한다.

% grant select on DBA_PENDING_TRANSACTIONS to public

 

2) ORACLE 테이블 만들기

2PC 구현하기 때문에,  두개의 다른 ORACLE DB 테이블을 만들어야 한다.

 

DB A :

SQL> create table TABLE_A(

  2  ID NUMBER UNIQUE,

  3  ACCOUNT NUMBER,

  4  PRIMARY KEY(ID)

5  );

6 insert into TABLE_A VALUES(1,1000000);

 

DB B:

SQL> create table TABLE_B(

  2  ID NUMBER UNIQUE,

  3  ACCOUNT NUMBER,

  4  PRIMARY KEY(ID)

5  );

 

 


3) EJB 코딩

 

그러면 EJB 직접 만들어보자, AccountManager, AccountBankA, AccountBankB 3개의 EJB 만들것이다. AccountManager EJB Session Bean으로 trasnsfer라는 메소드를 가지고 있다. transfer메소드는 AccountBankA EJB 이용하여 TABLE A에서 balance 만큼의 금액을 srcAccount 개정에서 빼서, AccountBankB EJB 이용하여, 다른 DB TABLE_B update하도록 되어 있다.

 

 

+transfer(srcAccount,balance,desAccount)

AccountManager

AccountBankA

AccountBankB

+getAccountID() : Long

+getBalance() : long

+setBalance(balance:long)

TABLE_B

+getAccountID() : Long

+getBalance() : long

+setBalance(balance:long)

TABLE_A

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


그럼 AccountBankA 빈즈를 보자. AccountBankA 빈즈는 간단한  EntityBean이다. 값을 set/get하는 역할만을 수행한다. AccountBankB 내용도 AccountBankA 빈즈와 같기 때문에 AccountBankB 소스 코드는 생략하기로 한다.

 

// AccountBankA.java

package com.javastudy.twopc;

 

import java.rmi.RemoteException;

import javax.ejb.EJBObject;

 

public interface AccountBankA extends EJBObject {

             public  Long getAccountId() throws RemoteException;

             public  long getBalance() throws RemoteException;

             public  void setBalance(long balance) throws RemoteException;

 

}

 

// AccountBankA_Home.java

package com.javastudy.twopc;

 

import java.rmi.RemoteException;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

import javax.ejb.FinderException;

 

public interface AccountBankA_Home extends EJBHome{

             public AccountBankA create(Long accountID,long balance)

                              throws RemoteException,CreateException;

            

             public AccountBankA findByPrimaryKey(Long accountID)

                              throws RemoteException,FinderException;

}

 

// AccountBankA_EJB.java

package com.javastudy.twopc;

 

import javax.ejb.CreateException;

import javax.ejb.EntityBean;

import javax.ejb.EntityContext;

 

public abstract class AccountBankA_EJB implements EntityBean {

             public abstract Long getAccountId();

             public abstract void setAccountId(Long accountId);

            

             public abstract long getBalance();

             public abstract void setBalance(long balance);

            

             public Long ejbCreate(Long accountId,long balance)

                              throws CreateException {

                                                setAccountId(accountId);

                                                setBalance(balance);

                                               

                                                return null;

                              }// ejbCreate

            

             public void ejbPostCreate(Long accountId,long balance)

                              throws CreateException{}

                             

             public void ejbActivate() {}

             public void ejbLoad() {}

             public void ejbPassivate() {}

             public void ejbRemove(){}

             public void ejbStore(){}

             public void setEntityContext(EntityContext ctx){}

             public void unsetEntityContext(){}

            

}

 

다음은 AccountBankA AccountBankB 빈즈를 컨트롤하는 AccountManager Session Bean이다.

 

//AccountManager.java

package com.javastudy.twopc;

 

import java.rmi.RemoteException;

import javax.ejb.EJBObject;

 

public interface AccountManager extends EJBObject{

             public String transfer(long srcAccount,long balance,long desAccount)

                              throws RemoteException;

}

 

 

//AccountManager_EJB.java

package com.javastudy.twopc;

 

import javax.ejb.CreateException;

import javax.ejb.SessionBean;

import javax.ejb.SessionContext;

import javax.naming.InitialContext;

import javax.naming.Context;

import javax.naming.NamingException;

import javax.rmi.PortableRemoteObject;

 

public class AccountManager_EJB implements SessionBean

{

             public String transfer(long srcAccount,long amount,long desAccount){

                              AccountBankA accountA = null;

                              AccountBankB accountB = null;

                              String result_msg = null;

                           // AccountA AccountB Entity Bean 생성한다.

                              accountA = getAccountBankA(srcAccount);

                              if(accountA == null) return "cannot cat account A:"+srcAccount;

 

                              accountB = getAccountBankB(desAccount);

                              if(accountB == null) return "cannot cat account B:"+desAccount;

 

                              /* start transaction */

                              try{

// AccountA AccountB Entity Bean에서 각각의 BALANCE값을 읽어오고

// A에서 balnce만큼을 빼서 B 더한다.

 

                              long balanceA = accountA.getBalance();

                              long balanceB = accountB.getBalance();

 

                           // 현재 내용을 출력한다.

System.out.println("current balance A :"+balanceA);

System.out.println("current balance B :"+balanceB);

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

                              accountA.setBalance(balanceA-amount);

                              accountB.setBalance(balanceB+amount);

                              }catch(Exception e){

                                                System.out.println("Error occur during AccountManager_EJB.transfer:"+e.toString());

                                                return e.toString();

                              }

                              /* end transaction */

 

                              return null;

             }// transfer

 

             private AccountBankA getAccountBankA(long accountID){

                              AccountBankA accountbank = null;

 

                              try{

                                                Context ctx = getContext();

                                                Object obj = ctx.lookup("com.bea.twopc.AccountBankA_Home");

 

                                                System.out.println("AccountBank_A lookup is :"+obj);

 

                                                AccountBankA_Home home =

                                                                  (AccountBankA_Home)PortableRemoteObject.narrow(obj,

                                                                                                                                        AccountBankA_Home.class);

 

                                                accountbank = home.findByPrimaryKey(new Long(accountID));

                              }catch(Exception e){

                                                System.out.println("error during AccountManager_EJB:getAccountBankA:"

                                                                                   +e.toString());

                                                return null;

                              }

 

                              return accountbank;

             }// getAccountBankA

 

             AccountBankB getAccountBankB(long accountID){

                              AccountBankB accountbank = null;

 

                              try{

                                                Context ctx = getContext();

                                                Object obj = ctx.lookup("com.bea.twopc.AccountBankB_Home");

 

                                                System.out.println("AccountBank_B lookup is :"+obj);

 

                                                AccountBankB_Home home =

                                                                  (AccountBankB_Home)PortableRemoteObject.narrow(obj,                                                                                                                                                                  AccountBankB_Home.class);

 

                                                accountbank = home.findByPrimaryKey(new Long(accountID));

                              }catch(Exception e){

                                                System.out.println("error during AccountManager_EJB:getAccountBankB:"

                                                                                   +e.toString());

                                                return null;

                              }// try-catch

 

                              return accountbank;

             }// getAccountBankB

 

             private Context getContext()

                              throws NamingException

             {

                              Context ctx = new InitialContext();

                              return ctx;

             }// getContext

            

             // callback methods

             public void ejbRemove(){}

             public void ejbActivate(){}

             public void ejbPassivate(){}

             public void setSessionContext(SessionContext sc){}

             public void ejbCreate() throws CreateException {}

 

}// AccountManager_EJB

 

 

//AccountManagerHome.java

package com.bea.twopc;

 

import java.rmi.RemoteException;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

 

public interface AccountManagerHome extends EJBHome{

             AccountManager create() throws CreateException,RemoteException;

}

 

여기까지 했으면 9개의 파일을 만들었을 것이다.

AccountBankA.java         AccountBankA_EJB.java     AccountBankA_Home.java

AccountBankB.java         AccountBankB_EJB.java     AccountBankB_Home.java

AccountManager.java       AccountManagerHome.java   AccountManager_EJB.java

9개의 파일을 컴파일 하자.

 

% javac –d . *.java

 

컴파일이 끝나면, ~/com/javastudy/twopc라는 디렉토리 아래 9개의 class파일이 생성되었을 것이다.

 

4) EJB 패키징


EJB
들을 패키징하기 위해서 Deployment Descriptor 작성하자.

 

<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

 

<!-- Generated XML! -->

 

 

<ejb-jar>

  <enterprise-beans>

    <session>

      <ejb-name>AccountManager_EJB</ejb-name>

      <home>com.bea.twopc.AccountManagerHome</home>

      <remote>com.bea.twopc.AccountManager</remote>

      <ejb-class>com.bea.twopc.AccountManager_EJB</ejb-class>

      <session-type>Stateless</session-type>

      <transaction-type>Container</transaction-type>

    </session>

 

    <entity>

      <ejb-name>AccountBankA_EJB</ejb-name>

      <home>com.bea.twopc.AccountBankA_Home</home>

      <remote>com.bea.twopc.AccountBankA</remote>

      <ejb-class>com.bea.twopc.AccountBankA_EJB</ejb-class>

      <persistence-type>Container</persistence-type>

      <prim-key-class>java.lang.Long</prim-key-class>

      <reentrant>False</reentrant>

      <cmp-field>

        <field-name>accountId</field-name>

      </cmp-field>

      <cmp-field>

        <field-name>balance</field-name>

      </cmp-field>

      <primkey-field>accountId</primkey-field>

    </entity>

 

    <entity>

      <ejb-name>AccountBankB_EJB</ejb-name>

      <home>com.bea.twopc.AccountBankB_Home</home>

      <remote>com.bea.twopc.AccountBankB</remote>

      <ejb-class>com.bea.twopc.AccountBankB_EJB</ejb-class>

      <persistence-type>Container</persistence-type>

      <prim-key-class>java.lang.Long</prim-key-class>

      <reentrant>False</reentrant>

      <cmp-field>

        <field-name>accountId</field-name>

      </cmp-field>

      <cmp-field>

        <field-name>balance</field-name>

      </cmp-field>

      <primkey-field>accountId</primkey-field>

    </entity>

  </enterprise-beans>

 

  <assembly-descriptor>

    <container-transaction>

      <method>

        <ejb-name>AccountBankA_EJB</ejb-name>

        <method-name>*</method-name>

      </method>

      <trans-attribute>Required</trans-attribute>

    </container-transaction>

    <container-transaction>

      <method>

        <ejb-name>AccountBankB_EJB</ejb-name>

        <method-name>*</method-name>

      </method>

      <trans-attribute>Required</trans-attribute>

    </container-transaction>

    <container-transaction>

      <method>

        <ejb-name>AccountManager_EJB</ejb-name>

        <method-name>*</method-name>

      </method>

      <trans-attribute>Required</trans-attribute>

    </container-transaction>

  </assembly-descriptor>

 

</ejb-jar>

 

ejb-jar.xml에서 있듯이. AccountManager EJB Stateless Session Bean이고, 모든 메소드의 트렌젝션 속성을 Required 설정했다. (, AccountManager 의해서 호출되는 AccountBankA AccountBankB EJB ejbStore ejbLoad 메소드는 AccountManager 같은 트렌젝션으로 수행된다.)

 

Weblogic이나 상용 WAS EJB deploy하기 위해서는 별도의 descriptor 필요하다. Weblogic 경우에는 weblogic-ejb-jar.xml weblogic-cmp-rdbms-jar.xml 필요한데, 지면 사정상 내용은 생략하기로 한다. 소스를 다운받아서 보면 있으리라 생각된다.

descriptor 만드는 방법은http://e-docs.bea.com/wls/docs61/ejb/index.html 참고하면 도움이 될것이다.

 

여기서 WebLogic에서 EJB Deployment descriptor 쉽게 만들 있는 방법을 잠깐 소개하고자 한다.

EJB 있는 디렉토리 (여기서는 EJB ~/com/javastudy/twopc 구조를 가지고 있으므로 ~/ 디렉토리가 된다.) “ java weblogic.ant.taskdefs.ejb20.DDInit . “ ( WebLogic 6.1 기준이며, WebLogic 관련 클래스 패스를 설정해놓은 뒤에 실행해야 한다. ) 해주면 자동으로 EJB Deploy 필요한 3가지 descriptor 파일을 생성해준다. 물론 DataSource등의 세세한 내용은 개발자가 직접 수정해야 하지만,  그래도 상당 부분의 코딩을 덜어줄 있기 때문에, 매우 유용하게 사용할 있다.

 이렇게 만들어진 deployment descriptor META-INF 디렉토리에 저장한다.

~/com/javastudy/twopc에는 9개의 컴파일된 EJB 클래스가,

~/META-INF/ 에는 3개의 Deployment Descriptor 파일이 위치해야 한다.

 

그러면 ~/ 디렉토리에서 하위 디렉토리를 jar 유틸을 이용해서 twopc.jar라는 이름으로 패키징 하자.

 

 % jar –cvf twopc.jar ~/*

 

5) 웹로직 설정

 

EJB 만들었으니까는, 이제는 EJB Deploy EJB Container 설정하자,Entity Bean 있기 때문에, DataSource 설정해야한다.

 

WebLogic Console 연후, Services  > JDBC > Connection Pools 에서 Create New JDBC Connection 선택한다.

<그림 3-1> 웹로직 JDBC Connection Pool 설정

각각의 필드에, 다음과 같이 JDBC 연결 정보를 넣는다.

NAME

JDBC Connection Pool이름으로, OracleXAConnection_A OracleXAConnection_B 사용하자

URL

jdbc:oracle:thin:@IP Address:포트:오라클 SID

Driver

ClassName

oracle.jdbc.xa.client.OracleXADataSource

Properties

user=오라클개정

password=오라클비밀번호

 

Driver Class XA 사용하기 때문에, 일반 JDBC Driver명을 넣는것이 아니다. 위의 내용을 주목하자.

 

생성된 JDBC Connection Pool Target Tab 선택하여, 해당 Server 지정한다.

 

같은 방법으로 두개의 Connection Pool 만든다.

 

만들어진 두개의 Connection Pool 이용해서 DataSource 만들어보자.

Services  > JDBC > TX DataSource 선택해서 Configure a new JDBC TxDataSource 선택한다.

 

 

 

 

<그림 3-1> 웹로직 JDBC TXDataSource설정

각각의 필드를 아래와 같은 내용으로 입력한다.

 

NAME

OracleXADS_A

JNDI Name

OracleXADS_A

PoolName

위에서 설정한 Connection Pool 이름을 적는다.

OracleXAConnection_A

 

같은 방법으로 OracleXADS_B 설정한다.

 

6) EJB DEPLOY

 

이제 EJB Deploy하기 위한 Oracle RDBMS설정과 EJB Container WebLogic 설정도 완료되었다. 실제로 EJB Deploy해보자.

 

웹로직 콘솔에서 Deployments > EJB 선택하고 Install a  new EJB 선택한다.

인스톨 화면이 나오면,[찾아보기] 버튼을 이용해서, 앞에서 만든 twopc.jar 선택하고 deploy 한다. Deploy 끝난후에, target 탭을 이용하여, 해당 server targetting 되어 있는지 확인한다.

 

7) CLIENT 작성

이제 EJB 성공적으로 Deploy 했으니, TEST 해볼차례다

TEST 위해서는 하나의 CLIENT 만들어야 한다.

 

// TwoPC.java

 

import com.bea.twopc.*;

import javax.naming.*;

import javax.ejb.*;

import javax.rmi.*;

import java.util.*;

 

public class TwoPC{

                              public static void main(String args[]){

                                                                  try{

                                                                  // step 1. get AccountManager

                                                                  Hashtable env = new Hashtable();

                                                                  env.put(Context.INITIAL_CONTEXT_FACTORY,                                                                                                                                    "weblogic.jndi.WLInitialContextFactory");

                                                         // 밑줄 친부분은 자기 환경에 맞추도록 하자.

                                                                  env.put(Context.PROVIDER_URL,"t3://웹로직 IP:웹로직 포트");

                                                                  env.put(Context.SECURITY_PRINCIPAL,"system");

                                                                  env.put(Context.SECURITY_CREDENTIALS,"weblogic");

 

                                                                  Context ctx = new InitialContext(env);

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

                                                                  Object obj = ctx.lookup("com.bea.twopc.AccountManagerHome");

                                                                  System.out.println("com.bea.twopc.AccountManagerHome Jndi :"+obj);

 

                                                                  AccountManagerHome home = (AccountManagerHome)                                                                                                                PortableRemoteObject.narrow(obj,AccountManagerHome.class);

 

                                                                  AccountManager manager = home.create();

 

                                                                 

                                                                  // step 2. run transfer method

                                                                  String result = manager.transfer((long)1,(long)10,(long)100);

                                                                 

                                                                  // step 3. display result

                                                                  System.out.println("result is :"+result);

                                                                  }catch(Exception e){

                                                                                                     e.printStackTrace();

                                                                  }

                              }// main

}

 

작성된 파일은 WebLogic Class Path 적용하여, 컴파일 한다.

% javac  -d . TwoPC.java

 

8) CLIENT 실행 결과 확인

 

완성된 Client 실행해보자,

% java TwoPC.java

 

처음 실행을 하고, DB 내용을 보면 TABLE_A에서 VALUE 100 빠져서,  TABLE_B 더해진다. TABLE_B 들어있는 데이타베이스를 끊거나, 내리고 실행을 하면, 에러가 나면서, TABLE_A에서 100 내용을 다시 ROLL BACK하는 것을 확인할 있을 것이다..

 

아주 간단하게, EJB 이용한 분산 트렌젝션 처리에 대한 프로그래밍을 해봤다. 일일이 집어가면서 하나씩 자세히 설명하면 좋겠지만, 지면 사정상, 내용을 함축해서, EJB 대한 개념이 없는 개발자에게는 다소 어려울 있다는 생각은 든다.

 

정리해보자면 EJB 우리가 3회에 걸쳐서 살펴본 복잡한 Transaction 처리를 매우 Component 모델이라는 이름아래서, 매우 쉽고, 안정적으로 처리해준다. EJB 자체의 개념이 어려우리라 생각하겠지만, 실제로, 이렇게 복잡한 트렌젝션 처리를 간단하고 안정적으로 처리해준다는건 많은 메리트가 있는 것이다. 거기다가 대부분의 EJB Container들은, 이런 EJB 클러스터링 기능까지 지원하고 있기 때문에, 개발자가 개발해야하는 많은 복잡한 부분을 대체해주고 있다..

 

EJB에서 제시하는 6까지 트렌젝션 모델은 대부분의 트렌젝션이 사용되는 개발을 가능하게 해준다. 일반적인 개발에서는 트렌젝션 관리를 Container 맞기고, 개발자는 Logic에만 신경을 쓰는것이 바람직하며, 복잡한 트렌젝션이나, 트렌젝션의 전이 과정, Bean Managed Transaction 사용하는 경우에는 좀더 주의를 기울이도록 하자.

1272 view

4.0 stars