ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ChainedTransactionManager
    Spring 2021. 3. 9. 22:18

    ChainedTransactionManager

    TransactionManager를 연결, 분리된 Transaction들의 Commit이나 Rollback을 보장함

    두 개 이상의 데이터베이스 트랜잭션을 연결해야하는 경우 사용할 수 있습니다.
    (사실 정말 트랜잭션을 묶어야하는건지 생각을 해봐야합니다. 분리할 수 있다면 어플리케이션 레벨에서 트랜잭션을 보장해주는 방법도 고려해봐야 할 것 같습니다.)


    ChainedTransactionManager 생성자

    ChainedTransactionManager는 2개 이상의 TransactionManager를 연결할때 사용할 수 있습니다.
    내부 동작에 대해서 파악해보겠습니다.

    public class ChainedTransactionManager implements PlatformTransactionManager {
    ...
        public ChainedTransactionManager(PlatformTransactionManager... transactionManagers) {
                this(SpringTransactionSynchronizationManager.INSTANCE, transactionManagers);
            }
    
        ChainedTransactionManager(SynchronizationManager synchronizationManager,
                    PlatformTransactionManager... transactionManagers) {
    
                Assert.notNull(synchronizationManager, "SynchronizationManager must not be null!");
                Assert.notNull(transactionManagers, "Transaction managers must not be null!");
                Assert.isTrue(transactionManagers.length > 0, "At least one PlatformTransactionManager must be given!");
    
                this.synchronizationManager = synchronizationManager;
                this.transactionManagers = asList(transactionManagers);
            }
    ...
    }

    생성자를 보면 PlatformTransactionManager 타입의 인자들을 받고 있는데, 만약 Jpa를 사용한다면 PlatformTransactionManager 를 상속받은 JpaTransactionManager 를 인자로 전달하면 됩니다.

    validation 후 synchronizationManagertransactionManagers 를 등록하는데 transactionManager를 List로 만들어서 등록하는 것을 볼 수 있습니다.


    Commit / Rollbak

    commit과 rollback은 transactionManagers 의 맨 마지막 요소부터 처리하게 됩니다.

    public void commit(TransactionStatus status) throws TransactionException {
    
            MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;
    
            boolean commit = true;
            Exception commitException = null;
            PlatformTransactionManager commitExceptionTransactionManager = null;
    
            for (PlatformTransactionManager transactionManager : reverse(transactionManagers)) {
    
                if (commit) {
    
                    try {
                        multiTransactionStatus.commit(transactionManager);
                    } catch (Exception ex) {
                        commit = false;
                        commitException = ex;
                        commitExceptionTransactionManager = transactionManager;
                    }
    
                } else {
    
                    // after unsucessfull commit we must try to rollback remaining transaction managers
    
                    try {
                        multiTransactionStatus.rollback(transactionManager);
                    } catch (Exception ex) {
                        LOGGER.warn("Rollback exception (after commit) (" + transactionManager + ") " + ex.getMessage(), ex);
                    }
                }
            }
    
            if (multiTransactionStatus.isNewSynchonization()) {
                synchronizationManager.clearSynchronization();
            }
    
            if (commitException != null) {
                boolean firstTransactionManagerFailed = commitExceptionTransactionManager == getLastTransactionManager();
                int transactionState = firstTransactionManagerFailed ? HeuristicCompletionException.STATE_ROLLED_BACK
                        : HeuristicCompletionException.STATE_MIXED;
                throw new HeuristicCompletionException(transactionState, commitException);
            }
        }

    핵심은 for (PlatformTransactionManager transactionManager : reverse(transactionManagers)) { 이 부분입니다. reverse(transactionManagers). 즉, 등록한 트랜잭션 매니저를 역순으로 commit 하고 있습니다. 그렇기 때문에 실패할 경우 모두 rollback 할 수 있도록 트랜잭션 매니저 순서를 잘 정해서 등록해야합니다.


    완벽한 Rollback을 보장하지 않는다.

    commit 코드 중간의 주석을 보면 이렇게 적혀있습니다.

    // after unsucessfull commit we must try to rollback remaining transaction managers

    주목해야할 부분은 rollback remaining ... 입니다. 역순으로 반복문을 돌면서 커밋을 하는데, 중간에 실패할 경우 남아있는 (아직 커멋처리하지 않은) Transaction 들에 대해서만 rollback 처리를 한다는 얘기 입니다.

    예를 들어 T1, T2, T3 순서로 등록했다고 했을때

    • T3 fail = T3, T2, T1 모두 rollback
    • T3 commit, T2 fail = T2, T1 만 rollback. T3는 commit

    위와 같은 상황이 벌어집니다. 그렇기 때문에 희망과는 달리 일부 데이터가 등록되는 상황이 벌어집니다.


    생각할거리

    • 정말 분리할 수 없는 업무 단위 (트랜잭션) 인가
    • 해당로직을 처리하는데 걸리는 시간. 이로인해 DB 테이블 접근의 문제가 생기는지

    ChainedTransaction 을 적용해보고, 면접에서 받은 질문들을 되돌아 보니 좋은 방법은 아닌 것 같습니다. DB Transaction 들을 연결하지 않고, 어플리케이션 레벨에서 트랜잭션을 보장해 줄 수 있는 방법을을 찾아봐야겠습니다.

    'Spring' 카테고리의 다른 글

    REST, REST API  (0) 2019.11.04
    spring-data-redis 테스트 (local, remote 테스트)  (0) 2019.10.28

    댓글

Paycis's