01.JAVA/Java2008. 11. 12. 17:08
반응형

트랜잭션은 반드시 원자적으로 수행되어야하는 작업들의 모음이다. 다시 말해, 트랜잭션 전체가 성공적이기 위해서는 각각의 작업이 반드시 성공적 이어야 한다는 뜻이며, 어느 한 작업이라도 성공적이지 못하면 전체 트랜잭션이 실패하게 된다. 실패했을 경우에는, 앞서 성공적으로 수행된 작업들은 반드시 원상태로 되돌아가, 작업을 시작하기 이전의 상태와 일치해야한다.

예를 들어, A 은행계좌(계좌번호12345-1)에서 B 은행계좌(계좌번호 12345-2)로 $50를 송금하길 원하는 사람이 있다고 하자. 이 트랜잭션의 절차는 다음과 같이 "pseudo code(역자 주: 프로그램이 실행되기 전 기계 코드로 번역될 필요가 있는 것)"로 나타낼 수 있다.

   BOUNDARY: START TRANSACTION A
   SUBTRACT 50 DOLLARS FROM SAVINGS ACCOUNT 12345-1
   ADD 50 DOLLARS TO THE CHECKING ACCOUNT 12345-2
   BOUNDARY: END TRANSACTION A

트랜잭션을 구동하기 위해서는 다음의 코드가 필요하다.

   PREPARE (RUN) TRANSACTION A
   IF TRANSACTION A SUCCEEDED
       COMMIT TRANSACTION A
   ELSE
       ROLLBACK TRANSACTION A

A은행계좌에 $50을 송금하기 충분한 자금이 있다고 가정하고, 트랜잭션의 첫번째 단계가 성공했다고 치자. 컴퓨터가 50 달러를 B계좌에 전송하려고 시도했으나 B 계좌가 사용정지 중이어서 두번째 단계에서 실패했다면, 이 두번째 단계가 실패했기 때문에 전체 트랜잭션은 실패한 것이 된다. 결과적으로 첫번째 단계의 작업은 초기화 되어야 한다. 즉, 50달러는 A계좌로 다시 돌아가야하는 것이다. 이는 매우 중요한 사항인데, 그렇지 않으면 컴퓨터가 전송을 시도할 때마다 A계좌에서는 돈이 빠져나가는 셈이기 때문이다!

전체 트랜잭션이 성공한 경우에는 모두 실행되고 그 결과가 지속된다.

전 과정이 완성되기 위해서는 두 과정이 실행된다. 첫번째로, 트랜잭션이 오류 없이 구동 되었는지를 확인하는 검사가 실행된다. 오류가 없다면 두번째로 넘어가며, 에러가 있다면 트랜잭션은 초기화된다. 이런 일반적인 트랜잭션 방법은 두 과정 실행 프로토콜(two-phase commit protocol)이라고 불린다.

J2EE 환경에서의 트랜잭션

트랜잭션에는 일반적으로 다음의 세가지가 관계되어 있다. 1. 애플리케이션: 트랜잭션 요청 착수, 2. data store(데이터베이스 등):트랜잭션 구동, 3. API(드라이버 등):애플리케이션과 데이터 창고 간의 통신. J2EE 플랫폼에서는 J2EE- compliant 애플리케이션 서버에 의해 API(또는 드라이버)가 제공된다. 이 애플리케이션 프로그램은 애플리케이션 서버를 호출하여 트랙잭션을 수행한다.

JTA(Java Transaction API)는 J2EE 플랫폼에 포함되어 있다. 이API는 분포된 트랙잭션을 수행할 수 있도록 한다. 즉, 애플리케이션은 이 API를 사용하여 한번에 네트워크 상에 있는 여러 개의 data store에서 트랙잭션을 수행할 수 있게 된다. 그러나 이를 효율적으로 수행하기 위해서는 애플리케이션 서버에서 기능하는 또다른 컴포넌트인 J2EE 트랜잭션 매니저가 도움이 될 것이다. 트랜잭션 매니저는 애플리케이션 서버에서 생성되는 많은 수의 트랜잭션을 효율적으로 스케쥴링하여 실행하도록 돕는다.

많은 수의 데이터베이스 벤더들은 각 회사 고유의 트랜잭션 매니저를 제공하고 있다. 그러나 특정 DBMS의 트랜잭션 매니저는 다른 벤더들의 데이터베이스에서 작동하지 않을 수 있다. 이런 여러 종류의 데이터베이스에서 작업하고 싶다면(ex. 여러 벤더사의 각종 데이터베이스를 업데이트), JTA 트랜잭션과 이에 수반되는 J2EE 트랜잭션 매니저를 사용할 것을 권장한다. JTA 설명서에는, "JTA 트랜잭션은 J2EE 트랜잭션 매니저로 관리됩니다"라고 나와있다. J2EE 트랜잭션 매니저에는 한가지 한계가 있는데, J2EE 트랜잭션은 수평적이라는 것이다. J2EE에서는 중첩된 트랜잭션이 지원되지 않는다. 즉, 먼저 시작한 트랜잭션이 끝나기 전에는 J2EE 트랜잭션 매니저가 다음 트랜잭션을 시작할 수 없다.

J2EE를 사용하는 애플리케이션 서버는 JTS(Java Transaction Service)를 이용하여 트랜잭션 매니저를 구현한다. JTA는 낮은 레벨의 JTS 절차에 호출할 API를 제공한다. JTA와 JTS를 헷갈리지 않도록 주의하기 바란다.

JTA 습득하기

트랜잭션을 수행하기 위해 JTA와 사용할 수 있는 인터페이스는 세가지가 있으며, 각 인터페이스는 트랜잭션을 핸들링하는 각각의 고유 방법이 있다. 다음은 그 인터페이스들이다.

  • javax.transaction.UserTransaction : 트랜잭션 매니저를 우회하는 트랜잭션 영역을 지정할 수 있다.
  • javax.transaction.TransactionManager : J2EE 트랜잭션 매니저가 각종 트랜잭션 작업과 함께 트랜잭션의 영역도 결정할 수 있도록 한다.
  • javax.transaction.xa.XAResource : 이 인터페이스는 트랜잭션 수행을 위해 써드파티 XA-compliant 트랜잭션 매니저를 이용하는 X/Open CAE Specification (Distributed Transaction Processing: The XA Specification) 표준과 매핑된다.

대부분의 J2EE 프로그래머들이 첫번째 인터페이스만을 사용하기 때문에 이 인터페이스에 특히 초점을 맞추어 설명하도록 하자.

EJB 트랜잭션: 컨테이너 & 빈 관리 트랜잭션

J2EE 환경에서 트랜잭션을 수행하는 가장 논리적인 장소는 EJB(Enterprise JavaBeans) 기술 컴포넌트(엔터프라이즈 빈이라고도 불림) 내부이다. 엔터프라이즈 빈을 이용하여 트랜잭션을 수행하는 데에는 두가지 방법이 있는데, 첫번째로 EJB 컨테이너로 트랜잭션 영역을 관리하는 것이다. 컨테이너 관리 트랜잭션(container-managed transactions)이라고 불리며, 이 방법으로 프로그래머에게 부담을 좀 줄여줄 수 있다. 두번째 방법을 이용하여 엔터프라이즈 빈 코드의 트랜잭션 영역을 명확히 지정해 줌으로써 프로그래머가 좀 더 자유로울 수 있다. 이는 빈 관리 트랜잭션(bean-managed transactions)이라고 불린다.

컨테이너 관리 트랜잭션은 세션 빈, 엔터티 빈, 메시지 드리븐 빈 중 어떤 엔터프라이즈 빈과도 함께 이용할 수 있다. 컨테이너 관리 트랜잭션에서는 EJB컨테이너가 트랜잭션 영역을 설정한다. 일반적으로 개별적인 트랜잭션으로 빈에 한 개 이상의 메소드를 선정하면서 작업이 완료된다. 컨테이너는 메소드 시간부분 바로 전부터 메소드가 끝나기 바로 전까지 영역을 설정한다. 그러나, 컨테이너 관리 트랜잭션에서는 각각의 메소드만이 하나의 트랜잭션이 될 수 있다(다중 트랜잭션은 허용되지 않는다.) 빈을 배치할 때 빈의 어떤 메소드가 트랜잭션과 관계될 것인지 지정해야한다. 트랜잭션 속성을 설정하여 지정할 수 있다.

트랜잭션 속성은 엔터프라이즈 빈 메소드가 다른 엔터프라이스 빈 메소드를 호출할 때 트랜잭션의 범위를 조절한다. JTA 설명서에서는 엔터프라이즈 빈 메소드가 EJB 배포 기술자의 6개의 트랜잭션 속성 중에서 선정할 수 있다고 명시하고 있다.

트랜잭션 속성은 트랜잭션이 수반될 때 EJB 컨테이너가 클라이언트 엔터프라이즈 빈에 의해 호출된 메소드를 어떻게 처리해야하는지를 보여준다.

   <ejb-jar>
     ...
    <enterprise-beans>
    ... 
     </enterprise-beans>
     <assembly-descriptor>
       <container-transaction>
         <method>
           <ejb-name>BankBean</ejb-name>
           <method-intf>Remote</method-intf>
           <method-name>transferMoney</method-name>
           <method-params>
             <method-param>java.lang.String</method-param>
             <method-param>java.lang.String</method-param>
             <method-param>java.lang.double</method-param>
           </method-params>
         </method>
         <trans-attribute>Required</trans-attribute>
       </container-transaction>
     </assembly-descriptor>
   </ejb-jar>

다음은 6개의 트랜잭션 속성에 대해 스펙에 적혀있는 내용이다.

  • Required - 클라이언트가 트랙잭션 하에 구동되고 있을 때 엔터프라이즈 빈의 메소드를 호출하면, 그 메소드는 클라이언트의 트랜잭션 내에서 실행된다. 만약 이 클라이언트가 트랜잭션에 관계 없다면, 컨테이너는 메소드를 구동하기 전에 새로운 트랙잭션을 시작한다. 대부분의 컨테이너 관리 트랜잭션이 Required를 사용한다.
  • RequiresNew - 클라이언트가 트랙잭션 하에 구동되고 있을 때 엔터프라이즈 빈의 메소드를 호출하면 컨테이너는 그 클라이언트의 트랜잭션을 잠시 보류하고 새로운 트랜잭션을 시작하며 메소드에 호출 권한을 위임한다. 메소드가 완료되면 클라이언트의 트랜잭션을 다시 시작한다. 클라이언트가 트랜잭션에 관계 없다면, 컨테이너는 메소드를 구동하기 전에 새로운 트랜잭션을 시작한다.
  • Mandatory - 클라이언트가 트랜잭션하에 구동될 때 엔터프라이즈 빈의 메소드를 호출하면 그 메소드는 그 클라이언트 트랜잭션 하에서 실행된다. 클라이언트가 트랜잭션과 관계 없다면 컨테이너는 TransactionRequiredException을 던지게 된다. 엔터프라이즈 빈의 메소드가 반드시 클라이언트의 트랜잭션을 이용해야할 때 Mandatory 속성을 이용하자.
  • NotSupported - 클라이언트가 트랜잭션하에 구동될 때 엔터프라이즈 빈의 메소드를 호출하면 컨테이너는 메소드를 활성화하기 전데 클라이언트의 트랜잭션을 일시 중지하며, 메소드가 완료되면 컨테이너는 클라이언트 트랜잭션을 재시작한다. 클라이언트가 트랜잭션과 관련이 없다면 컨테이너는 메소드를 구동하기 전에는 새로운 트랜잭션을 시작하지 않는다. 트랜잭션을 필요로하지 않는 메소드에 대해 code>NotSupported 속성을 사용하자. 트랜잭션이 전체를 포함하기 때문에 이 속성은 성능 향상을 가져다줄 것이다.
  • Supports - 클라이언트가 트랜잭션 하에서 구동될 때 엔터프아리즈 빈의 메소드를 호출하면 메소드는 그 클라이언트 트랜잭션 하에서 실행된다. 클라리언트가 트랜잭셕과 관계 없다면 컨테이너는 메소드를 시작하기 전에 새로운 트랜잭션을 시작하지 않는다. 메소드의 트랜잭션 비헤이비어가 변할 수 있기 때문에, Supports 속성을 사용할 때는 주의가 필요하다.
  • Never - 클라이언트가 트랜잭션 하에서 구동될 때 엔터프라이즈 빈의 메소드를 호출하면 컨테이너는 RemoteException를 던진다. 클라이언트가 트랜잭션과 관계 없다면 컨테이너는 메소드를 구동하기 전에 새로운 트랜잭션을 시작하지 않는다.

컨테이너 관리 트랜잭션을 처음상태로 돌리는 데에는 두가지 방법이 있다. 시스템 예외상황이 던져지면 컨테이너는 자동적으로 트랜잭션을 원상태로 돌리게 된다. 또한 EJBContext 인터페이스의 setRollbackOnly() 메소드를 호출하여 원상태로 만들 수도 있다. 이 메소드는 컨테이너에게 트랜잭션을 초기화하도록 지시한다. 엔터프라이즈 빈이 애플리케이션 예외상황을 던지면, 자동적으로 초기화 되지는 않지만 setRollbackOnly()를 호출함으로써 초기화에 착수할 수는 있다. 컨테이너 관리 트랜잭션을 사용할 때에는 JTA 메소드들을 호출할 수 없다는 것을 명심하기 바란다. 세 메소드가 빈 관리 트랜잭션을 이용하기 위해 예약되어 있기 때문이다. 이 세 메소드는 다음과 같다.

  • 트랜잭션적 의미와 충돌되는 리소스 지정 기능; java.sql.Connectioncommit(), setAutoCommit(), rollback() 메소드 등
  • javax.ejb.EJBContextgetUserTransaction() 메소드
  • javax.transaction.UserTransaction의 모든 메소드

빈 관리 트랙잭션에서 세션 빈 또는 메세지 드리븐 빈에서의 코드는 트랜잭션 영역을 명확하게 지정한다. 엔터티 빈은 빈 관리 트랜잭션을 가질 수 없으며, 반드시 컨테이너 관리 트랜잭션을 이용해야한다. 세션 빈 또는 메시지 드리븐 빈을 위해 빈 관리 트랜잭션을 코딩할 때는 일반적으로 JDBC 또는 JTA 트랜잭션을 사용할 수 있다. JDBC 트랜잭션은 J2EE 트랜잭션 매니저가 아닌 데이터베이스 관리 시스템의 트랜잭션 매니저에 의해 관리된다. JDBC 트랜잭션을 수행하기 위해서는 java.sql.Connection 인터페이스의 commit() 메소드와 rollback() 메소드를 이용한다. 트랜잭션의 시작에는 가장 최근의 commit(), rollback(), connect() 문을 따르는 첫번째 SQL 명령문으로 시작된다. JTA 트랜잭션을 위해서는 javax.transaction.UserTransaction 인터페이스의 begin(), commit(), rollback() 메소드를 호출할 수 있다. begin() 메소드와 commit() 메소드는 트랜잭션 영역을 지정한다. 트랜잭션 작업이 실패했을 경우, 일반적으로 예외상황 핸들러가 rollback() 메소드를 호출하여 EJBException을 던진다. 다음의 코드에서는 javax.transaction.UserTransaction 인터페이스를 이용하여 빈 관리 트랜잭션을 수행하는 법을 보여준다.

      UserTransaction ut = context.getUserTransaction();

      try {
         ut.begin();
         // Do whatever transaction functionality is necessary
         ut.commit();
      } catch (Exception ex) {
          try {
             ut.rollback();
          } catch (SystemException syex) {
              throw new EJBException
                 ("Rollback failed: " + syex.getMessage());
          }
          throw new EJBException 
             ("Transaction failed: " + ex.getMessage());
       }   

JDBC 빈 관리 트랜잭션을 수행하는 코드도 이와 비슷하다. 그러나 그 코드는 데이터베이스 자동 연결 기능이 되지 않음을 주의해야한다. 이 방법으로 데이터베이스는 수반되는 모든 작업을 하나의 트랜잭션으로 취급한다.( commit() 메소드가 호출될 때까지)

   try {            
        Connection con = makeDatabaseConnection();
        con.setAutoCommit(false);           
        //  Do whatever database transaction functionality
        //  is necessary           
        con.commit();          
    } catch (Exception ex) {
        try {
            con.rollback();
        } catch (SQLException sqx) {
            throw new EJBException("Rollback failed: " +
                sqx.getMessage());
        }
    } finally {
        releaseDatabaseConnection();
    }

JTA 스펙에 나와있는 몇가지 조항을 소개한다.

빈 관리 트랜잭션을 이용하는 비상태 유지 세션 빈(stateless session bean)에서는 비즈니스 메소드가 반드시 실행되거나 트랜잭션을 초기화한 후 리턴되야한다 그러나 상태 유지 세션 빈(stateful session bean)에서는 이러한 제한이 없다. JTA 트랜잭션을 이용하는 상태 유지 세션 빈에서는 빈 인스턴스와 트랜잭션 사이의 연관성이 다중 클라이언트 호출을 망라하여 유지된다. 심지어는 클라이언트에 의해 호출된 각각의 비즈니스 메소드가 데이터베이스 연결을 통제할 때는 인스턴스가 트랜잭션을 완료할 때까지 관계가 유지된다. JDBC 트랜잭션을 이용하는 상태 유지 세션 빈에서는 다중 호출 시 JDBC 연결이 빈 인스턴스와 트랜잭션 사이의 관계를 유지한다. 연결이 끊기면, 관계는 유지되지 않는다.

JTA 빈 관리 트랜잭션을 사용할 때 한가지 메소드 제한이 있다. EJBContext 인터페이스의 getRollbackOnly() 메소드와 setRollbackOnly()메소드를 호출하면 안된다.(이 메소드들은 컨테이너 관리 트랜잭션에만 사용되어야한다.) 빈 관리 트랜잭션을 위해서는 UserTransaction 인터페이스의 getStatus() 메소드와 rollback() 메소드를 사용하기 바라며 또한, 트랜잭션적 의미와 충돌되는 resource-specific 기능을 사용하지 말기 바란다.

JTA와JTS에 대한 좀 더 자세한 정보는 J2EE Transaction page를 참고하기 바란다.

Java Transaction API에 대한 예제 코드 구동하기

  1. Java 트랜잭션 API 팁을 위한 예제 압축파일을 다운로드 받으세요.
  2. 예제 압축파일을 다운로드할 디렉토리를 변경하고,예제 압축파일을 위한 JAR file의 압축을 푸세요.
          jar xvf  ttJan2005jta.jar
    
    jta라는 이름의 디렉토리와 소스 코드, 컴파일된 클래스들, 다른 지원 파일들이 나타납니다.
  3. 애플리케이션 서버를 시작합니다. J2EE 1.4 SDK는 Sun Java System Application Server Platform Edition 8을 포함합니다. 정확하게 작동하기 위해서는 Sun Java Application Server 8.1 4Q2004또는 그 이후 버전이 필요합니다.
  4. PointBase 데이터베이스 서버를 시작합니다.
  5. jta 디렉토리로 변경합니다. Ant 스크립트(build.xml)를 사용자의 J2EE 홈 디렉토리에 지정하여 편집합니다.
  6. 명령창에 명령어를 입력합니다.
          ant create-db
    
    데이터베이스가 생성되고 "account" 테이블로 채워집니다. 테이블을 삭제하려고 할 때 데이터베이스의 시간이 지연되면, 컴퓨터를 재부팅하고 PointBase를 다시 시작하기 바랍니다.
  7. 명령어를 입력합니다.
          ant build
    
    ejb라는 디렉토리가 생성되고 두개의 JAR 파일(bank-client.jar, bank-ejb.jar)로 채워집니다.
  8. Deploy bank-ejb.jar를 배치하세요. Sun Java System Application Server Platform Edition 8 Admin Console을 이용하거나(Application->EJB Modules), 또는 Autodeploy 디렉토리에 파일을 복사하여(이 옵션 없이 서버를 인스톨 했을 때) 실행할 수 있습니다.
  9. ejb 디렉토리를 변경하고 다음의 명령어를 입력하면,
          appclient -client bank-client.jar
    

다음과 같은 결과가 나타납니다.

   --OUTPUT FROM APP CLIENT BEGIN--
   Using Bean Transaction Management (Database Transaction 
   Manager)...
   
   Balance of 12345-01 is: 100.0
   Balance of 12345-02 is: 0.0
   
   Now transferring 23.43 from 12345-01 to 12345-02
   
   Balance of 12345-01 is: 76.57
   Balance of 12345-02 is: 23.43
   
   Now transferring 23.43 from 12345-01 to fictional account 
   number 12345-10
   Balance should be the same as before...
   
   Balance of 12345-01 is: 76.57
   Balance of 12345-02 is: 23.43
   
   
   Now Using JTA Container Transaction Management...
   
   
   Now transferring 23.43 from 12345-01 to fictional account 
   number 12345-10
   Exception was caught.
   Balance should again be the same as before...
   
   Balance of 12345-01 is: 76.57
   Balance of 12345-02 is: 23.43
   
   --OUTPUT FROM APP CLIENT END-- 

"Java EE" 카테고리의 다른 글

Posted by 1010