2010년 7월 27일 화요일

Spring에서 Transaction 처리.

트랜잭션 처리는 보통 클래스 및 메서드 레벨의 애너테이션으로 가능하다.
하지만 Exception 발생 여부와 상관 없이 로그를 남겨야 하는 경우라든지 메서드 내부에서 트랜잭션 안에서 실행되어야 하는 부분과 트랜잭션과 무관하게 실행되어야 하는 부분이 함께 존재한다면 애너테이션을 사용하여 처리할 수 없다. (사실은 Propagation 설정으로 가능할 것 같은데 잘 안 된다. 천천히 알아보기로 하구 일단..)


[PROPAGATION]
REQUIRED : 이미 tx가 존재할 경우, 해당 tx에 참여 / tx가 없을 경우, 신규 tx를 생성하고 실행
SUPPORTS : 이미 tx가 존재할 경우, 해당 tx에 참여 / tx가 없을 경우, 그냥 실행
MANDATORY : 이미 tx가 존재할 경우, 해당 tx에 참여 / tx가 없을 경우, Exception 발생
REQUIRES_NEW : 이미 tx가 존재할 경우, 해당 tx를 suspend 시키고 신규 tx를 생성 / tx가 없을 경우, 신규 tx를 생성
NOT_SUPPORTED : 이미 tx가 존재할 경우, 해당 tx를 suspend 시키고 tx 없이 실행 / tx가 없을 경우, 그냥 실행
NEVER : 이미 tx가 존재할 경우, Exception 발생 / tx가 없을 경우, tx 없이 실행
NESTED : 이미 tx가 존재할 경우, 해당 tx에 참여 / tx가 없을 경우, nested tx 실행

[ISOLATION]
DEFAULT : datastore에 의존
READ_UNCOMMITED : Dirty Reads, Non-Repeatable Reads, Phantom Reads 발생
READ_COMMITED : Dirty Reads 방지, Non-Repeatable Reads, Phantom Reads 발생
REPEATABLE_READ : Non-Repeatable Read 방지, Phantom Reads 발생
SERIALIZABLE : Phantom Read 방지



Transaction 처리 방식
  1. TransactionTemplate 사용
  2. TransactionManager 사용

dataSource 및 transactionManager 설정


spring 설정파일에 아래와 같이 설정한다.

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://host:3306/databaseName?useUnicode=true&characterEncoding=UTF-8"/>
    <property name="username" value="dbuser"/>
    <property name="password" value="dbpw"/>
    <property name="maxWait" value="28000"/>
    <property name="maxIdle" value="100" />
    <property name="minIdle" value="1" />
    <property name="initialSize" value="10" />
    <property name="timeBetweenEvictionRunsMillis" value="28000" />
    <property name="minEvictableIdleTimeMillis" value="28000000" />
    <property name="validationQuery" value="select 1 from dual"/>
    <property name="testWhileIdle" value="true" />
</bean>


<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>


TransactionTemplate 사용하기


서비스 클래스에 아래와 같이 프로퍼티를 설정한다.

@Resource(name="txManager")
  protected DataSourceTransactionManager txManager;

  protected TransactionTemplate transactionTemplate;
  
  @PostConstruct
  public void init() {
    transactionTemplate = new TransactionTemplate(txManager);
  }

  @Override
  public void myTrxMethod(final PutAccountInfoRequest request)
      throws Exception {
    // write log
    MyTrxResult result = (MyTrxResult)
      transactionTemplate.execute(new TransactionCallback() {
      
      @Override
      public Object doInTransaction(TransactionStatus status) {
        boolean success = false;
        Exception exception = null;
        try {
          // trx1
          // trx2
          
        } catch(Exception ex) {
          exception = ex;
          status.setRollbackOnly();
        } 
        return new MyTrxResult();
      }
    });
    
    // write transaction result

  }


리턴되는 결과가 없는 경우에는 TransactionCallback 대신 TransactionCallbackWithoutResult 을 사용할 수도 있으나, TransactionCallback에서 null을 리턴해도 무방하다.
Transaction은 doInTransaction 메서드 단위로 처리가 되며, exception이 발생하였을 때 setRollbackOnly을 호출하면 메서드 내의 Transaction은 롤백처리된다.

TransactionManager 사용하기


protected TransactionStatus getTransaction() {
    DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
    definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    return txManager.getTransaction(definition);
  }


  @Override
  public void myTrxMethod(PutAccountInfoRequest request)
      throws Exception {
    // write log

    boolean success = false;
    Exception exception = null;
    
    TransactionStatus txStatus = getTransaction();
    
    try {
      // trx1
      // trx2
      
      txManager.commit(txStatus);
    } catch(Exception ex) {
      exception = ex;
      txManager.rollback(txStatus);
    }

getTransaction하는 시점부터 commit 또는 rollback 할 때까지의 일련의 처리가 단일 트랜잭션으로 처리된다.

댓글 없음:

댓글 쓰기