스프링에서는 트랜잭션을 어떻게 처리하는가? (2)

정원식·2023년 12월 16일
0

스프링 트랜잭션

목록 보기
2/5

22년 12월 작성한 글입니다.

개요

  • 우리는 보통 @Transactional 어노테이션을 통해 트랜잭션을 처리합니다.
  • 해당 어노테이션은 어떻게 적용이 되는지
  • 그리고 스프링에서는 트랜잭션을 어떻게 처리하는지 알아봅니다. (5.3.18 기준)
  • 이번 챕터에서는 PlatformTransactionManager 가 어떻게 동작하는지 알아봅니다.

PlatformTransactionManager

  • 앞선 챕터에서 TransactionInterceptor 가 트랜잭션 advice 로 사용되는것을 확인했습니다.
  • 프록시가 적용될때, TransactionInterceptor#invoke 가 호출되고, 내부적으로 TransactionAspectSupport#invokeWithinTransaction 를 호출합니다.
  • 이후 과정에서 PlatformTranscationManager 를 통해 트랜잭션을 획득, 커밋 그리고 롤백합니다.

PlatformTranscationManager

  • TransactionInterceptor 가 트랜잭션을 획득, 커밋 그리고 롤백하는 과정은 아래와 같이 도식화할수 있습니다.

AbstractPlatformTranscationManager

  • PlatformTranscationManager 의 추상 구현체는 AbstractPlatformTranscationManager 로 대부분의 구현체가 해당 클래스를 상속하고 있습니다.
    • DataSourceTransactionManager
    • JdbcTransactionManager
    • HibernateTransactionManager
    • JtaTransactionManager
  • 3개의 메서드 구현을 살펴봅니다.
    • getTransaction
    • commit
    • rollback

getTransaction

  • TransactionStatus (트랜잭션) 을 획득합니다.
  • PropagationIsolation 에 따라서 동작이 달라집니다.
  • 이미 트랜잭션이 존재하는 경우에는 다르게 핸들링합니다.
  • 새로운 트랜잭션인 경우, TransactionSynchronizationManager 에 새로운 트랜잭션을 등록합니다.
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    throws TransactionException {

    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
    Object transaction = doGetTransaction();    // 트랜잭션 획득

    if (isExistingTransaction(transaction)) {
        // 이미 트랜잭션이 존재하는 경우 핸들링
        return handleExistingTransaction(def, transaction, debugEnabled);
    }

    ...
    try {
        // 트랜잭션 시작
        return startTransaction(def, transaction, debugEnabled, suspendedResources);
    }
    catch (RuntimeException | Error ex) {
        resume(null, suspendedResources);
        throw ex;
    }
    ...
}

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
    boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    doBegin(transaction, definition);
    prepareSynchronization(status, definition); // 트랜잭션 동기화
    return status;
}

commit

  • 트랜잭션을 커밋합니다.
  • 이 과정에서 예외 발생시 롤백 처리합니다.
  • 등록된 TransactionSynchorinization 를 호출합니다
    • #beforeCommit
    • #beforeCompletion
    • #afterCommit
    • #afterCompletion
  • 마지막에는 TransactionSynchronizationManager 에 등록된 트랜잭션을 해제합니다.
public final void commit(TransactionStatus status) throws TransactionException {
    ...
    processCommit(defStatus);
}

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {
        boolean beforeCompletionInvoked = false;

        try {
            boolean unexpectedRollback = false;

            prepareForCommit(status);
            // TransactionSynchorinization#beforeCommit 호출
            triggerBeforeCommit(status);
            // TransactionSynchorinization#beforeCompletion 호출
            triggerBeforeCompletion(status);
            beforeCompletionInvoked = true;

            ...
            doCommit(status);   // 커밋
            ...
        } ...
        catch (RuntimeException | Error ex) {
            if (!beforeCompletionInvoked) {
                triggerBeforeCompletion(status);
            }
            doRollbackOnCommitException(status, ex);    // 롤백
            throw ex;
        }

        try {
            // TransactionSynchorinization#afterCommit 호출
            triggerAfterCommit(status); 
        }
        finally {
            // TransactionSynchorinization#afterCompletion 호출
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
        }

    }
    finally {
        // TransactionSynchronizationManager 해제
        cleanupAfterCompletion(status);
    }
}

rollback

  • 트랜잭션을 롤백합니다.
  • 등록된 TransactionSynchorinization 를 호출합니다
    • #beforeCompletion
    • #afterCompletion
  • 마지막에는 TransactionSynchronizationManager 에 등록된 트랜잭션을 해제합니다.
public final void rollback(TransactionStatus status) throws TransactionException {
    ...

    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    processRollback(defStatus, false);
}

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        boolean unexpectedRollback = unexpected;

        try {
            // TransactionSynchorinization#beforeCompletion 호출
            triggerBeforeCompletion(status);

            ...
            doRollback(status); // 롤백
            ...
        }
        catch (RuntimeException | Error ex) {
            // TransactionSynchorinization#afterCompletion 호출
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            throw ex;
        }

        // TransactionSynchorinization#afterCompletion 호출
        triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
        ...
    }
    finally {
        // TransactionSynchronizationManager 해제
        cleanupAfterCompletion(status);
    }
}

결론

  • @Transactional 를 통해 트랜잭션이 적용될때, PlatformTransactionManager 를 통해 트랜잭션이 적용됩니다.
  • AbstractPlatformTranscationManager 에 트랜잭션을 획득, 커밋, 그리고 롤백하는 과정이 일부 작성되어 있습니다.

Reference

spring-tx

profile
매일매일 성장하고 싶은 백엔드 개발자입니다.

0개의 댓글

관련 채용 정보