본문 바로가기

Spring

스프링의 트랜잭션 관리

스프링 프레임워크의 트랜잭션 추상화

 

스프링 프레임워크의 트랜잭션 전략은 PlatformTransactionManager 인터페이스에 정의되어 있습니다.

 

명세는 아래와 같습니다.

 

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

 

여기서 getTransaction(TransactionDefinition definition) 메소드는 TransactionStatus 객체를 반환합니다.

 

TransactionDefinition 인터페이스는 아래와 같은 정보를 가지고 있습니다.

 

Isolation : 트랜잭션 Isolation level을 나타내는 정보입니다.

Propagation : 트랜잭션 propagation을 나타내는 정보입니다.

Timeout : 트랜잭션 실행 시간으로, 이 시간이 지나게 되면 롤백됩니다.

read-only 여부 : 읽기 전용인지에 대해 나타냅니다. 이 옵션은 하이버네이트 같은 일부 케이스의 최적화에 사용됩니다.

 

그리고, TransactionStatus 인터페이스는 트랜잭션 실행, 및 상태를 제어할 수 있는 간단한 방법을 제공하고 있습니다.

 

명세는 아래와 같습니다.

 

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

 

스프링 프레임워크의 트랜잭션 관리 방법

스프링 프레임워크에서는 두 가지 트랜잭션 관리 방식이 있습니다.

 

프로그래밍 방식 (TransactionTemplate을 사용하는 방법),

선언적 방식 (@Transactional 어노테이션을 사용하는 방법) 입니다.

 

선언적 트랜잭션 관리 방식은 어플리케이션 코드에 끼치는 영향이 거의 없기 때문에, 스프링 프레임워크 사용자 대부분이 선언적 트랜잭션 관리 방식을 사용합니다. ( @Transactional 을 사용하는 방식 )

 

스프링 프레임워크에서 선언적 트랜잭션을 사용하기 위한 설정

설정에서 @EnableTransactionManagement를 추가한 뒤, 트랜잭션을 사용하고 싶은 클래스 및 메소드에 @Transactional 어노테이션을 달면 됩니다.

 

만약 스프링부트를 사용하는 경우, TransactionAutoConfiguration 클래스를 통해서 @EnableTransactionManagement 어노테이션이 자동설정 되기 때문에 설정을 신경쓸 필요는 없습니다.

 

스프링 프레임워크의 선언적 트랜잭션 구현에 대한 이해

스프링의 선언적 트랜잭션 지원 기능은 AOP 프록시를 통해 활성화됩니다.

 

AOP 프록시는 TransactionInterceptorTransactionManager 구현체를 사용해 메소드 호출을 둘러싸고 트랜잭션을 실행합니다.

 

트랜잭션 프록시를 통한 메소드 호출 개념을 그림으로 나타내면 아래와 같습니다.

 

 

Caller -> AOP Proxy -> Transaction Advisor -> Custom Advisor -> Target Method 순서대로 호출되고, 다시 리턴되는 구조입니다.

 

TransactionInterceptor의 경우 TransactionAspectSupport 를 상속받고 있으며, TransactionAspectSupportinvokeWithinTransaction 메소드를 통해서 프록시 객체의 메소드를 호출합니다.

 

invokeWithinTransaction 메소드의 일부 내용을 보면 아래와 같습니다.

 

PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
   // Standard transaction demarcation with getTransaction and commit/rollback calls.
   TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

   Object retVal;
   try {
      // This is an around advice: Invoke the next interceptor in the chain.
      // This will normally result in a target object being invoked.
      retVal = invocation.proceedWithInvocation();
   }
   catch (Throwable ex) {
      // target invocation exception
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;
   }
   finally {
      cleanupTransactionInfo(txInfo);
   }

   if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
      // Set rollback-only in case of Vavr failure matching our rollback rules...
      TransactionStatus status = txInfo.getTransactionStatus();
      if (status != null && txAttr != null) {
         retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
      }
   }

   commitTransactionAfterReturning(txInfo);
   return retVal;
}

 

동작 과정을 아주 간략하게 보면 트랜잭션을 가져오거나 생성하고,

 

중간에 invocation.proceedWithInvocation() 메소드를 통해 비지니스 메소드를 호출시킨 뒤

 

예외가 발생한다면 롤백 처리를 해주고 트랜잭션을 완료해주거나,

 

이상이 없을 경우 마지막에 커밋을 해주는것을 볼 수 있습니다.

 

@Transactional 의 사용

 

@Transactional의 경우 클래스에도 사용할 수 있고, 메소드에도 사용할 수 있습니다.

 

클래스에 사용하는 경우 선언하는 클래스의 모든 메소드에 적용할 기본값을 나타냅니다.

물론 개별 메소드마다 어노테이션을 설정할 수 있습니다. 

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName) {
        // ...
    }

    Foo getFoo(String fooName, String barName) {
        // ...
    }

    void insertFoo(Foo foo) {
        // ...
    }

    void updateFoo(Foo foo) {
        // ...
    }
}

 

+) javax.transaction.Transactionalorg.springframework.transaction.annotation.Transactional 두 개의 어노테이션중 어떤걸 사용해야 할지 헷갈릴 수 있는데, 둘 다 사용해도 무방합니다.

 

어차피 두 개의 어노테이션을 파싱하는 파서가 둘 다 등록되어 있어서 상관없습니다.

 

메소드 가시성과 @Transactional

 

프록시 버전 ( default )을 사용할 때는 public 메소드에만 @Transactional 어노테이션을 적용해야 합니다.

 

protected, private 메소드나 패키지에서만 접근할 수 있는 메소드에 @Transactional 어노테이션을 선언한다고 해서 에러가 발생하지는 않지만 커스텀하게 지정한 트랜잭션 설정을 사용할 수 없게 됩니다. 

 

public 이 아닌 메소드에 어노테이션을 달고 싶다면 AspectJ를 사용하는게 좋습니다.

 

+) @Transactional 어노테이션을 인터페이스 대신 구체적인 클래스에 선언하는 것이 좋습니다. 인터페이스에 사용할 경우 인터페이스 기반 프록시를 사용할 때만 사용가능하고, 클래스 기반 프록시를 사용할 경우 프록시로 감싸지지 않습니다.

 

@Transactional Default Setting

 

디폴트 @Transactional 설정을 다음과 같습니다.

 

  • propagation : Require
  • isolation : default -> JDBC isolation level을 따라갑니다.
  • timeout : default -> 트랜잭션 시스템을 따라가고, 타임아웃을 지원하지 않으면 없는것으로 설정됩니다.
  • RuntimeException과 Error를 롤백시키고, checked Exception은 롤백하지 않습니다.

 

이 옵션들은 커스텀하게 지정할 수 있습니다.

 

프로그래밍 방식의 트랜잭션 관리

 

TransactionTemplate를 사용하는 것을 권장합니다.

 

사용방식은 트랜잭션 내부에서 실행시키고 싶은 내용들을 TransactionCallback 인스턴스로 감싸 생성한 뒤 TransactionTemplateexecute() 메소드에 넣어주면 됩니다.

 

코드로 보면 아래와 같습니다.

 

public class SimpleService implements Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private final TransactionTemplate transactionTemplate;

    // use constructor-injection to supply the PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // the code in this method runs in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

 

위 예제를 보면, 트랜잭션 API와 결합도가 올라가는걸 확인할 수 있을 것입니다. 상황을 보고 잘 판단한 후에 사용하시는 것이 좋습니다.

 

출처

 

https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html 스프링 프레임워크 트랜잭션 관리 레퍼런스

https://godekdls.github.io/Spring%20Data%20Access/transactionmanagement/ 토리맘의 스프링 트랜잭션 관리 한글 레퍼런스