Transaction Service

Transaction 관리에 대하여 일관성 있는 추상화된 방법을 제공하는 서비스로 다음과 같은 장점을 제공한다.
  • JTA, JDBC와 같은 서로 다른 Transaction API에 대해 일관성 있는 프로그래밍 모델을 제공한다.
  • 프로그램적인 Transaction 관리에 대한 사용하기 쉬운 API를 제공한다.
  • 선언적인 Transaction 관리를 지원한다.
  • Query Service와 통합이 용이하다.
  • Hibernate와 통합이 용이하다.
사용방법은 다음과 같다. 원하는 서비스를 선택하여 Transaction을 관리하도록 한다.

Common Service Configuration

Transaction 서비스를 사용하기 위해서 공통적으로 설정해야 하는 설정 파일 내용은 다음과 같다. Transaction을 관리하고자 하는 서비스 bean과 TransactionProxyFactoryBean bean을 함께 정의해놓아야 한다. TransactionProxyFactoryBean에 정의할 transactionManager은 DataSource, Hibernate, JTA 중 어떤 TransactionManager를 사용할 것인지 결정하여 추가 정의해주면 된다.
Property Name
Description
Required
Default Value
transactionManager 사용할 Transaction manager 속성을 정의한 Transaction Manager의 id를 참조한다. Transaction Manager 속성 정의 내용은 하단의 내용을 참고하도록 한다.
Y
N/A
transactionAttributes target의 메소드 별로 transaction attributes를 정의할 수 있다. 여기에 Propagation Behavior, Isolation Level, "readOnly" flag, Rollback 규칙를 설정할 수 있다.(하단 참고 그림을 참고한다) e.g. key ="myMethod", value ="PROPAGATION_REQUIRED,readOnly"
  • Propagation Behavior(전달 행위): [Required] 상세 내용은 아래 표를 참고하여 그 중 하나를 선택하도록 한다.
  • Isolation Level : [Optional] 상세 내용은 아래 표를 참고하여 그 중 하나를 선택하도록 한다.
  • "readOnly" flag : [Optional] readOnly falg는 상응하는 Transaction이 읽기 전용 Transaction으로 최적화 되어야 한다는 것을 나타내는 일종의 최적화 힌트이다. 어떤 Transaction 전략은 중요한 최적화를 수행할 수 있는데, 예를 들어 Hibernate나 TopLink 같은 객체/관계 매핑 도구를 사용할 때 dirty checking("flush" 시도)을 회피하는 경우를 들 수 있다. 일반적으로 Transaction 내에서 SELECT 쿼리만을 실행하는 경우에 이 속성을 사용한다.
  • Rollback 규칙 : [Optional] Transaction의 Default 설정은 RuntimeException이 발생할 경우에는 Rollback, CheckedException이 발생하는 경우에는 Commit되도록 하고 있다. 그러나 Transaction의 속성을 지정할 때 Rollback 규칙을 이용하여 디폴트 설정을 변경하는 것이 가능하다. 하단 그림의 Rollback 규칙에서 마이너스(-)로 시작하는 Exception에 대해서는 무조건 Rollback, 플러스(+)로 시작하는 Exception은 무조건 Commit되도록 규칙을 변경할 수 있다. 이때 플러스(+)로 시작하는 Exception의 경우 Runtime Exception에 대해서도 무조건 Commit 처리되므로 신중히 사용하도록 한다. (ex. -java.lang.Exception,-anyframe.common.exception.BaseException)
Y
N/A
target TransactionProxyFactoryBean bean의 abstract attribute 값을 true로 놓고, 이 TransactionProxyFactoryBean bean을 parent로 정의한 bean 내에 target을 정의한다.
Y
N/A
[참고] transactionAttributes 속성

다음은 TransactionProxyFactoryBean bean의 transactionAttributes property에 정의할 내용 중 Propagation Behavior에 대한 추가 설명이다.
Attribute Name
Description
PROPAGATION_MADATORY 반드시 Transaction 내에서 메소드가 실행되야 하고,Transaction이 없는 경우에는 예외를 발생시킨다.
PROPAGATION_NESTED Transaction에 있는 경우, 기존 Transaction 내의 nested transaction 형태로 메소드를 실행하고, nested transaction 자체적으로 commit, rollback이 가능하다. Transaction이 없는 경우, PROPAGATION_REQUIRED 속성으로 행동한다.
PROPAGATION_NEVER Transaction 컨텍스트 없이 실행되어야 하며 Transaction이 있으면 예외를 발생시킨다.
PROPAGATION_NOT_SUPPORTED Transaction 없이 메소드를 실행하며,기존의 Transaction이 있는 경우에는 이 Transaction을 호출된 메소드가 끝날 때까지 잠시 보류한다.
PROPAGATION_REQUIRED Transaction 컨텍스트 내에서 메소드가 실행되어야 한다. 기존 Transaction이 있는 경우에는 기존 Transaction 내에서 실행하고, 기존 Transaction이 없는 경우에는 새로운 Transaction을 생성한다.
PROPAGATION_REQUIRED_NEW 호출되는 메소드는 자신 만의 Transaction을 가지고 실행하고, 기존의 Transaction들은 보류된다.
PROPAGATION_SUPPORTS 새로운 Transaction을 필요로 하지는 않지만, 기존의 Transaction이 있는 경우에는 Transaction 내에서 메소드를 실행한다.

다음은 TransactionProxyFactoryBean bean의 transactionAttributes property에 정의할 내용 중 Isolation Level에 대한 추가 설명이다. Isolation Level(격리수준)은 Transaction에서 일관성이 없는 데이터를 허용하도록 하는 수준이며, 여러 Transaction들이 다른 Transaction의 방해로부터 보호되는 정도를 나타낸다.
예를 들어, 한 사용자가 어떠한 데이터를 수정하고 있는 경우 다른 사용자들이 그 데이터에 접근하는 것을 차단함으로써 완전한 데이터만을 사용자들에게 제공하게 된다. 또한, 많은 사용자들의 수정 작업으로 인하여 통계 자료를 작성할 수 없는 사용자를 위하여 읽기 작업을 수행할 수 있도록 Isolation Level을 변경할 수 있다.
Attribute Name
Description
ISOLATION_DEFAULT 개별적인 PlatformTransactionManager를 위한 디폴트 격리 레벨
ISOLATION_READ_COMMITTED 이 격리수준을 사용하는 메소드는 commit 되지 않은 데이터를 읽을 수 없다. 쓰기 락은 다른 Transaction에 의해 이미 변경된 데이터는 얻을수 없다. 따라서 조회 중인 commit 되지 않은 데이터는 불가능하다. 대개의 데이터베이스에서의 디폴트로 지원하는 격리 수준이다.
ISOLATION_READ_UNCOMMITTED 가장 낮은 Transaction 수준이다. 이 격리수준을 사용하는 메소드는 commit 되지 않은 데이터를 읽을 수 있다. 그러나 이 격리수준은 새로운 레코드가 추가되었는지 알수 없다.
ISOLATION_REPEATABLE_READ ISOLATION_READ_COMMITED 보다는 다소 조금 더 엄격한 격리 수준이다. 이 격리 수준은 다른 Transaction이 새로운 데이터를 입력했다면, 새롭게 입력된 데이터를 조회할 수 있다는 것을 의미한다
ISOLATION_SERIALIZABLE 가장 높은 격리수준이다. 모든 Transaction(조회를 포함하여)은 각 라인이 실행될 때마다 기다려야 하기 때문에 매우 느리다. 이 격리수준을 사용하는 메소드는 데이터 상에 배타적 쓰기를 락을 얻음으로써 Transaction이 종료될 때까지 조회, 수정, 입력 데이터로부터 다른 Transaction의 처리를 막는다 가장 많은 비용이 들지만 신뢰할만한 격리 수준을 제공하는 것이 가능하다.

다음은 Transaction 서비스가 가지는 설정 정보에 대한 설명이다.

Samples

다음은 Transaction 서비스를 사용하기 위해 공통적으로 설정해야 하는 속성 설정에 대한 예제이다.
  • Configuration
  • 다음은 DataSource Transaction 서비스를 적용하여 TransactionTestSampleService 서비스 속성을 정의한 applicationContext-tx-sample-datasource.xml의 일부이다. 유사한 형태로 Hibernate Transaction 서비스를 적용한 TransactionTestSampleService 서비스 속성 파일과 JTA Trasaction 서비스를 적용한 TransactionTestSampleService 서비스 속성 파일이 존재한다. 각기 서로 다른 설정 파일을 테스트 수행을 위해서 다른 파일들로 구성해놓았다.
    <bean id="transactionSampleDataSource" parent="baseTransactionProxyDataSource">
       <property name="target">
          <bean class
             ="integration.anyframe.services.transaction.sample.TransactionTestSampleServiceImpl">
             <property name="queryService" ref="queryService"></property>
          </bean>
       </property>
       <property name="proxyInterfaces">
          <list>
             <value>
                integration.anyframe.services.transaction.sample.TransactionTestSampleService
             </value>
          </list>
       </property>
    </bean>
    
    <bean id="baseTransactionProxyDataSource"
       class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
       abstract="true">
       <property name="transactionManager" ref="transactionManagerDataSource" />
       <property name="transactionAttributes">
          <props>
             <prop key="insert*">PROPAGATION_REQUIRED</prop>
             <prop key="update*">PROPAGATION_REQUIRED</prop>
             <prop key="delete*">PROPAGATION_REQUIRED</prop>
             <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>
          </props>
       </property>
    </bean>
  • TestCase
  • 다음은 앞서 정의한 속성 설정을 기반으로 하여 DatSource Transaction 서비스를 이용하여 Transaction 처리 기능을 테스트 하는 실제 테스트 메소드는 AbstractTransactionServiceTest.java에 공통으로 작성되어 있다. DataSource, Hibernate, JTA Transaction 서비스 별로 테스트를 수행하기 위해서는 이 AbstractTransactionServiceTest를 상속받은 테스트 클래스를 사용한다. Transaction 서비스가 올바르게 동작하는 지 확인하기 위해서 아래와 같이 Programmatic한 방식으로 TransactionManager와 TransactionStatus를 활용하여 commit와 rollback 처리를 하고 있다.
    public abstract class AbstractTransactionServiceTest extends AbstractTest {
       public void testInsertRollback() throws Exception {
          Thread.sleep(500);
          // 1. get previous commit count and rollback count
          int prevCommitCount = service.getCommitCount();
          int prevRollbackCount = service.getRollbackCount();
          DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
          txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
          TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);
          try {
             // 2. execute commit twice with a same data
             TransactionVo vo = new TransactionVo();
             vo.setCol1(Thread.currentThread().getName() + "-col1");
             vo.setCol2(Thread.currentThread().getName() + "-col2");
             vo.setCol3(new BigDecimal(System.currentTimeMillis()));
             service.insertData(vo);
             service.insertData(vo);
             transactionManager.commit(txStatus);
             } catch (QueryServiceException e) {
                // 3. execute rollback
                transactionManager.rollback(txStatus);
             } finally {
                // 4. assert
                boolean checkedCommit = prevCommitCount == service.getCommitCount();
                if (!checkedCommit)
                   throw new Exception("fail to get commit count.");
                boolean checkedRollback = prevRollbackCount + 2 == service.getRollbackCount();
                if (!checkedRollback)
                   throw new Exception("fail to get rollback count.");
             }
          }
    중략...

DataSource Transaction Service

DataSource Transaction 서비스는 DataSource를 사용하여 Local Transaction을 관리한다.

Samples

다음은 DataSource Transaction 서비스의 속성 설정 및 테스트 코드에 대한 예제이다.
  • Configuration
  • 다음은 DataSourceTransactionManager bean의 속성 파일인 applicationContext-transaction-datasource.xml의 일부이다. 여기에서는 dataSource property를 정의해 준다.
    <bean id="transactionManagerDataSource" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	<property name="dataSource"><ref bean="common_datasource"/></property>
    </bean>
    위에서 common_datasource bean은 DataSource 서비스 속성을 정의한 applicationContext-datasource-common.xml 파일에 아래와 같이 정의되어 있다.
    <bean id="common_datasource
       " class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:file:/./db/sampledb"/>
        <property name="username" value="sa"/>
    </bean>
  • TestCase
  • 다음은 앞서 정의한 속성 설정을 기반으로 하여 DatSource Transaction 서비스를 이용하여 Transaction 처리 기능을 테스트 하는 TransactionServiceTestDataSource.java 코드의 일부이다. 실제 테스트 메소드는 AbstractTransactionServiceTest.java에 공통으로 작성되어 있다. DatSource Transaction 서비스를 테스트하기 위해서는 setUp() 메소드를 아래와 같이 작성해준다.
    public class TransactionServiceTestDataSource extends AbstractTransactionServiceTest {
       중략...
       protected void setup() {
          super.setup();
          this.service = (TransactionTestSampleService)context.getBean("transactionSampleDataSource");
          this.transactionManager = (PlatformTransactionManager)context
                                                            .getBean("transactionManagerDataSource");
       }
    }

Hibernate Transaction Service

Hibernate Transaction 서비스는 DataSource를 사용하여 Local Transaction과 Hibernate Session을 관리한다. HibernateTransactionManager는 LocalSessionFactoryBean 에 의존성을 가지고 있으므로 반드시 LocalSessionFactoryBean 설정과 함께 사용되어야 한다.

Samples

다음은 Hibernate Transaction 서비스의 속성 설정 및 테스트 코드에 대한 예제이다.
  • Configuration
  • 다음은 HibernateTransactionManager bean의 속성 파일인 applicationContext-transaction-hibernate.xml의 일부이다. 여기에서 sessionFactory property를 정의해 주는데, sessionFactory bean은 LocalSessionFactoryBean으로 정의해 준다. Hibernate의 sessionFactory bean 설정 방법은 Hibernate 서비스 매뉴얼 내용을 참고한다.
    <bean id="transactionManagerHibernate"
    	class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    	<property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    위에서 sessionFactory bean은 Hibernate 서비스 속성을 정의한 applicationContext-hibernate.xml 파일에 아래와 같이 정의되어 있다.
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
      <property name="dataSource" ref="common_datasource" />
      <property name="mappingDirectoryLocations">
        <value>classpath:/integration/anyframe/services/hibernate/sample</value>
      </property>
      <property name="hibernateProperties">
        <props>
          <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
          <prop key="hibernate.show_sql">true</prop>
          <prop key="hibernate.cache.provider_class">org.hibernate.cache.OSCacheProvider</prop> 
          <prop key="hibernate.cache.use_second_level_cache">true</prop>         
        </props>
      </property>
    </bean>
  • TestCase
  • 다음은 앞서 정의한 속성 설정을 기반으로 하여 Hibernate Transaction 서비스를 이용하여 Transaction 처리 기능을 테스트 하는 TransactionServiceTestDataSource.java 코드의 일부이다. 실제 테스트 메소드는 TransactionServiceTestHibernate.java에 공통으로 작성되어 있다. Hibernate Transaction 서비스를 테스트하기 위해서는 setUp() 메소드를 아래와 같이 작성해준다.
    public class TransactionServiceTestHibernate extends AbstractTransactionServiceTest {
       중략...
       protected void setup() {
          super.setup();
          this.service = (TransactionTestSampleService)context.getBean("transactionSampleHibernate");
          this.transactionManager = (PlatformTransactionManager)context
                                                            .getBean("transactionManagerHibernate");
       }
    }

JTA Transaction Service

JTA Transaction 서비스는 JTA를 사용한 Global Transaction 관리 부분을 추상화하여 해당 서비스가 JTA,JNDI 등에 종속적이지 않게 구현 가능 하도록 도와준다. JndiTemplate bean 설정과 함께 사용되어야 한다. 또한 이때 DataSource 서비스는 JNDI DataSource 서비스로 설정해줘야 한다.

Samples

다음은 JTA Transaction 서비스의 속성 설정 및 테스트 코드에 대한 예제이다.
  • Configuration
  • 다음은 JTATransactionManager bean의 속성 파일인 applicationContext-transaction-jta.xml의 일부이다. 여기에서 transactionManagerName property와 jnditemplate property를 정의해 주는데, transactionManagerName property는 Weblogic server의 JNDI 위치를 지정하고 있으며 이는 WAS 벤더 별로 다를 수 있다. jnditemplate bean에는 naming provider url과 naming initial context factory 클래스명을 작성한다. 다음은 WebLogic Server를 통해 Transaction을 관리하는 경우 TransactionManager 설정에 대한 예제이다.
    <bean id="transactionManagerJTA"
       class="org.springframework.transaction.jta.WebLogicJtaTransactionManager">
       <property name="transactionManagerName"	value="javax.transaction.TransactionManager" />
       <property name="jndiTemplate" ref="jnditemplate"></property>
    </bean>
    
    <bean id="jnditemplate" class="org.springframework.jndi.JndiTemplate" >
       <property name="environment">
          <props>
             <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
             <prop key="java.naming.provider.url">t3://107.108.150.108:7001</prop>
          </props>
       </property>
    </bean>

    또한, JEUS Server를 통해 Transaction을 관리하는 경우에는 다음과 같이 TransactionManager를 설정할 수 있다.
    <bean id="transactionManagerJTA"
       class="org.springframework.transaction.jta.JtaTransactionManager">
       <property name="transactionManagerName"	value="java:/TransactionManager" />
       <property name="jndiTemplate" ref="jnditemplate"></property>
    </bean>
    
    <bean id="jnditemplate" class="org.springframework.jndi.JndiTemplate" >
       <property name="environment">
          <props>
             <prop key="java.naming.factory.initial">jeus.jndi.JNSContextFactory</prop>
             <prop key="java.naming.provider.url">107.108.150.108:9736</prop>
          </props>
       </property>
    </bean>
    JEUS Server는 별도 설정없이도 기본적인 TransactionManager를 가지고 있으며 해당 TransactionManager의 JNDI명은 java:/TransactionManager 이다. JNDI 명이 java:/TransactionManager인 TransactionManager를 찾지 못해 javax.naming.NameNotFoundException이 발생하였다면 해당 프로젝트의 클래스패스 내에 j2ee-x.x.jar 파일이 추가되어 있지 않은지 확인하여 삭제하도록 한다. 이는 JEUS Server를 위한 기본 라이브러리 내에 존재하는 Transaction 처리 관련 클래스와 충돌로 인해 발생하는 문제이기 때문이다.
    JNDI DataSource 서비스는 아래와 같이 설정하도록 한다.
    <bean class="org.springframework.jndi.JndiObjectFactoryBean" id="common_datasource">
       <property name="jndiName" value="STMRDS"/>
       <property name="jndiTemplate" ref="jnditemplate"/>
    </bean>
  • TestCase
  • 다음은 앞서 정의한 속성 설정을 기반으로 하여 JTA Transaction 서비스를 이용하여 Transaction 처리 기능을 테스트 하는 TransactionServiceTestDataSource.java 코드의 일부이다. 실제 테스트 메소드는 TransactionServiceTestJTA.java에 공통으로 작성되어 있다. JTA Transaction 서비스를 테스트하기 위해서는 setUp() 메소드를 아래와 같이 작성해준다.
    public class TransactionServiceTestHibernate extends AbstractTransactionServiceTest {
       중략...
       protected void setup() {
          super.setup();
          this.service = (TransactionTestSampleService)context.getBean("transactionSampleJTA");
          this.transactionManager = (PlatformTransactionManager)context
                                                 .getBean("transactionManagerJTA");
       }
    }
또한, 예제 테스트 코드가 런타임시 WebLogic 라이브러리를 참조하므로 [WebLogic Home]/server/lib/ 폴더의 weblogic-8.1.jar, xbean-8.1.jar 파일을 [Anyframe Core 설치 폴더] 내에 복사해 두어야 한다. 위 작업이 완료된 후, WebLogic Server가 성공적으로 시작된 상태에서 예제 테스트 코드를 실행하도록 한다. * [Anyframe Core 설치 폴더]라함은 [anyframe-X.X.X-bin.zip 압축 해제 위치]로 간주한다.

Resources