Java/Spring2014. 11. 5. 23:24
반응형

 스프링(Spring Framework)의 중첩 트랜잭션(정확히는 중첩 트랜잭션 전파레벨)에서 예외 로그 출력에 관한 포스팅을 하면서 이왕이면 스프링이 제공하는 트랜잭션 관리 기능에 대해서 구체적으로 알아보는 것이 좋겠다고 생각이 들었습니다.


 일단 트랜잭션이란 무엇인지에 대해서 간단하게 언급하고 지나가도록 하겠습니다. 잘알고 계시듯 일반적으로 트랜잭션이란 더이상 쪼갤수 없는 어떤 논리적인 작업단위라고 할 수 있습니다. DBMS(데이터베이스 관리 시스템)에서는 의미를 명확히 하기 위해서 작업단위 자체를 나타내는 용어를 데이터베이스 트랜잭션(Database Transaction)이라고 하고 흔히 트랜잭션이라고 말하면 떠올리는 데이터베이스 트랜잭션에 관련된 조작을 트랜잭션 처리(Transaction Processing)라고 합니다. 이러한 트랜잭션(특별히 언급하지 않는한 트랜잭션은 트랜잭션 처리를 의미함)을 사용하는 주된 목적은 데이터의 완전무결성(integrity)을 보장하기 위해서 입니다.



 스프링의 트랜잭션 관리 기능에 대해서 알아보기 전에 순수하게 JDBC 인터페이스만을 이용하여 트랜잭션을 처리하는 단순한 소스코드를 살펴보도록 하자.



소스코드1

public void methodNameA() {
    ...
    conn = ds.getConnection();
    conn.setAutoCommit(false);

    stmt = conn.createStatement();

    stmt.executeUpdate("INSERT INTO TABLE_NAME (COLUMN_NAME) VALUES ('VALUE')");

    conn.commit();
    ...
}

public void methodNameB() {
    ...
    conn = ds.getConnection();
    conn.setAutoCommit(false);

    stmt = conn.createStatement();

    stmt.executeUpdate("UPDATE TABLE_NAME SET COLUMN_NAME = 'NEW_VALUE'");

    conn.commit();
    ...
}


 JDBC를 이용한 단순한 트랜잭션 처리는 위와같이 구현될 수 있는데 위와 같이 순수한 JDBC를 이용한 소스코드의 문제점은 무엇일지 아래의 코드를 보면서 생각해보자.



소스코드2

public void methodNameC() {

    methodNameA();
    methodNameB();

}



  소스코드1처럼 단순히 메소드 단위로 호출이 될 경우에는 트랜잭션이 경계가 확실히 구분되면서 잘 작동하겠지만 소스코드2처럼 별도의 트랜잭션 처리를 하는 메소드 2개를 제3의 메소드에서 호출한다고 할 경우 methodNameA의 처리가 완료된 시점에 commit이 실행되어 하나의 트랜잭션이 완결되므로 A처리후 B처리중에 실패한다고 하더라도 A의 트랜잭션을 롤백할 수 없게 되어 버립니다. 결과적으로 위의 소스코드는 재사용성이 극히 떨어지는 상태가 되며 동일한 처리를 반복해서 작성해야 합니다. 또한 위와 같은 명시적인 커밋, 롤백 등등의 처리를 단순히 Copy & Paste 로 입력하다보니 빠지는 경우도 발생하게 됩니다.



 위와 같은 트랜잭션 처리상의 어려움 해결하기 위한 방법은 크게 2가지가 있을듯 합니다.

  1. 트랜잭션의 상태를 각 모듈에 직접 전파한다.
  2. 트랜잭션의 상태를 공용 저장공간을 이용하여 간접적으로 전파한다.



 언뜻 이해하기 힘들 수 있겠지만 간단하게 말해서 첫번째는 트랜잭션의 상태를 호출하는 메소드에 인자로 직접적으로 전달하는 것을 말하며 두번째는 메소드 내부에서 공용공간을 참조하여 간접적으로 상태를 확인하는 방법입니다.(일반적으로 ThreadLocal이 이용된다.)



 첫번째에 해당하는 해결책은 사실 메소드 시그니쳐에 영향을 끼치기 때문에 특정 벤더의 솔루션 내부적으로 템플릿화 해서 구현하는 경우가 일반적이며 범용적인 트랜잭션 관리 구현체는 두번째 방법을 이용해서 구현되어 있습니다. 스프링에서도 마찬가지로 트랜잭션을 포괄적으로 관리할 수 있는 기능을 제공합니다. 스프링에서 제공하는 트랜잭션 관련 기능을 간단하게 살펴보면 아래와 같습니다.



  • 트랜잭션 관리를 위한 추상화 기능

 추상화된 트랜잭션 관리 기능을 제공하기 때문이 이종의(JTA, JDBC, Hibernate, JPA, JDO) 데이터베이스 접근API간에도 일관적인 프로그래밍이 가능합니다.

  • 선언적(declarative) 트랜잭션 관리

어토테이션 혹은 AOP를 통해서 프로그램 코드가 아니라 설정 혹은 선언만으로 트랜잭션 관리가 가능합니다.

  • 프로그램(직접적인 코드)에 의한 트랜잭션 관리

심플한 프로그램 코드에 의한 직접적인 트랜잭션 관리기능도 제공합니다.




 그러면 먼저 스프링의 트랜잭션 추상화 인터페이스인 org.springframework.transaction.PlatformTransactionManager의 소스코드를 살펴보자.


public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}


 코드상의 getTransaction메소드의 경우 트랜잭션의 상태를 반환하고 있다. 위에서 트랜잭션 관리를 위해서 트랜잭션의 상태를 전파할 필요가 있다고 언급했었는데 소스코드상에서 트랜잭션을 취득하면 트랜잭션 상태를 반환받으므로 이것을 전파하는 것으로 트랜잭션 관리할 수 있을 것으로 파악할 수 있다.



public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}


 트랜잭션 매니저에서 트랜잭션 취득시에 반환받았던 트랜잭션 상태 클래스의 구조를 살펴보면 새 트랜잭션인가 세이브포인트를 가지고 있는가 롤백을 할것인가 등등의 정보를 저장하고 있다. 이를 이용해서 최종적으로 트랜잭션 종료 시점에 커밋, 롤백 등등의 처리를 진행 할 것임을 예측 할 수 있습니다.



설정예


  


 위의 설정예는 일반적인 프로젝트에서 가장 많이 사용하는 데이터 소스 기반의 트랜잭션 매니져 설정이다. 위에서 소개한 트랜잭션관련 인터페이스의 구현체를 스프링 빈으로 등록하여 사용하게 됩니다. 개념을 알고 보니 설정에 대해서 이해하기 한결 수월해진듯 합니다.




 스프링 트랜잭션 관리의 개념에 대해서 간단하게 알아보았습니다. 사실 스프링 트랜잭션 관리의 개념 자체는 스프링만의 독창것이라고 보기는 어려울지도 모릅니다. 하지만 예전부터 각자 구현해 왔던 트랜잭션 관리를 추상화 하여 다양한 플랫폼 아래서도 일관된 소스코드를 적용할 수 있게 해주는 점이 매력적이라고 할 수 있습니다.



참조 : http://docs.spring.io/spring/docs/3.2.12.RELEASE/spring-framework-reference/html/transaction.html


Posted by Reiphiel