Home Transaction의 동작 과정은 어떻게 될까?
Post
Cancel

Transaction의 동작 과정은 어떻게 될까?

글을 작성하게 된 계기


사람들과 스프링 서버를 만들어보는 프로젝트를 진행하며 @Transactional의에 대해 질문받았습니다. 스프링 내부에서 어떤 과정을 거쳐 트랜잭션이 시작되는지, 이 과정에서 어떤 클래스와 인터페이스가 사용되는지를 정확하게 정리하고 싶어 해당 글을 작성하게 되었습니다.







1. 개요


트랜잭션은 크게 트랜잭션 시작, 비즈니스 로직 처리, 트랜잭션 종료 세 단계로 진행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Slf4j
@Component
public class UserServiceProxy implements UserSaveUseCase, UserSearchUseCase, UserDeleteUseCase {

    ......

    @Override
    public User save(User user) {
         // 1.트랜잭션 시작
        TransactionStatus txStatus = getTransactionStatus(false);
        log.info("txStatus:[{}]", txStatus.getTransaction());
        try {
            // 2.비즈니스 로직 처리
            User newUser = target.save(user);

            // 3.성공시 commit.
            txManager.commit(txStatus);
            return newUser;
        } catch (Exception exception) {
            // 3.실패: rollback
            txManager.rollback(txStatus);
            throw new RuntimeException();
        }
    }
    
    ......

}







실제 스프링 코드를 보더라도 비슷한 구조로 돼 있는걸 볼 수 있습니다. 이번 포스팅에서는 트랜잭션이 어떤 단계를 거쳐 동작하는지 스프링 코드를 따라가며 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

    ......

    @Nullable
    protected Object invokeWithinTransaction(
        Method method, @Nullable Class<?> targetClass,
        final InvocationCallback invocation
    ) throws Throwable {

        ......

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

        if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 1. 트랜잭션 시작
            TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                // 2. 비즈니스 로직 처리
                retVal = invocation.proceedWithInvocation();
            } catch (Throwable ex) {
                // 3. Rollback
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            } finally {
                // 3. 트랜잭션 자원 정리
                cleanupTransactionInfo(txInfo);
            }

            ......

            // 3. Commit
            commitTransactionAfterReturning(txInfo);
            return retVal;
        } else {

            ......

            return result;
        }
    }

    ......

}







2. 트랜잭션 동작 과정


트랜잭션은 아래와 같은 과정을 거쳐 시작됩니다. 이는 중요 포인트/메서드를 추출한 것으로, 전체 트랜잭션의 단계가 아닙니다. 이 외에도 수많은 포인트/메서드를 거치는데, 이에 대해서는 직접 스프링 코드를 학습해 보실 것을 권장해 드립니다.

  1. Proxy 호출
  2. createTransactionIfNecessary
  3. getTransaction
  4. doGetTransaction
  5. isExistingTransaction, getTimeout
  6. Propagation 체크
  7. startTransaction
  8. doBegin
  9. begin
  10. prepareSynchronization





2-1. Proxy 호출

선언적 트랜잭션을 사용하면 스프링을 AOP를 이용해 프록시 객체의 메서드를 호출합니다. 이는 TransactionInterceptor가 @Transactional 어노테이션을 읽어 동작하며, 내부적으로 targetClass를 호출합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {

    ......
    
    @Override
    @Nullable
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
            @Override
            @Nullable
            public Object proceedWithInvocation() throws Throwable {
                return invocation.proceed();
            }

            @Override
            public Object getTarget() {
                return invocation.getThis();
            }

            @Override
            public Object[] getArguments() {
                return invocation.getArguments();
            }
        });
    }

    ......

}







invoke 메서드 내부에서 TransactionAspectSupport의 invokeWithinTransaction 메서드를 한 번 더 호출하는데, 우리가 사용하는 트랜잭션(@Transactional)은 해당 메서드 내부에서 이루어집니다. 모든 과정을 다 볼 필요는 없으므로, 필요한 부분만 선별해서 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

    ......

    @Nullable
    protected Object invokeWithinTransaction(
        Method method, @Nullable Class<?> targetClass,
        final InvocationCallback invocation
    ) throws Throwable {

        ......

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

        if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 1. 트랜잭션 시작
            TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                // 2. 비즈니스 로직 처리
                retVal = invocation.proceedWithInvocation();
            } catch (Throwable ex) {
                // 3. Rollback
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            } finally {
                // 3. 트랜잭션 자원 정리
                cleanupTransactionInfo(txInfo);
            }

            ......

            // 3. Commit
            commitTransactionAfterReturning(txInfo);
            return retVal;
        } else {

            ......

            return result;
        }
    }

    ......

}







2-2. createTransactionIfNecessary

트랜잭션의 시작은 createTransactionIfNecessary 메서드로 부터 시작 됩니다. TransactionAspectSupport는 내부적으로 PlatformTransactionManager를 참조하는데, 이를 통해 getTransaction 메서드를 호출합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

    ......
    
    protected TransactionInfo createTransactionIfNecessary(
        @Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr,
        final String joinpointIdentification
    ) {

        ......

        TransactionStatus status = null;
        if (txAttr != null) {
            if (tm != null) {
                // 트랜잭션 획득
                status = tm.getTransaction(txAttr);
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured");
                }
            }
        }
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }
}







2-3. getTransaction

getTransaction 메서드는 다음과 같으며, 여기서 트랜잭션에 필요한 설정 이 이루어 집니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    ......
    
    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

        // 1. TransactionDefinition 생성/획득
        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

        // 2. Transaction 객체 생성
        Object transaction = doGetTransaction();
        boolean debugEnabled = logger.isDebugEnabled();

        // 3. 존재하는 트랜잭션인지, 타임아웃인지 확인
        if (isExistingTransaction(transaction)) {
            return handleExistingTransaction(def, transaction, debugEnabled);
        }
        if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
        }

        // 4. Propagation 확인 및 트랜잭션 시작
        if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
        } else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
            }
            try {
                // 5. 트랜잭션 시작
                return startTransaction(def, transaction, debugEnabled, suspendedResources);
            } catch (RuntimeException | Error ex) {
                resume(null, suspendedResources);
                throw ex;
            }
        } else {
            if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
                logger.warn("Custom isolation level specified but no actual transaction initiated; " + "isolation level will effectively be ignored: " + def);
            }
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
        }
    }
}







메서드 내부에서는 먼저 TransactionDefinition 객체를 생성/획득합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

        // 1. TransactionDefinition 생성/획득
        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

        ......

    }
}







이는 트랜잭션이 처음 시작되는지, 이전 트랜잭션에 이어서 진행되는지에 따라 나뉩니다. 트랜잭션이 처음 시작된다면 StaticTransactionDefinition 객체를 가져와 사용하며, 이미 진행 중인 트랜잭션이 있다면 이를 재사용 합니다.

1
2
3
4
5
6
7
8
final class StaticTransactionDefinition implements TransactionDefinition {

	static final StaticTransactionDefinition INSTANCE = new StaticTransactionDefinition();

	private StaticTransactionDefinition() {
	}

}







2-4. doGetTransaction

TransactionDefinition 생성/획득했다면 다음으로 doGetTransaction 메서드를 통해 transaction 객체를 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

        // 1. TransactionDefinition 생성/획득
        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

        // 2. Transaction 객체 생성
        Object transaction = doGetTransaction();

        ......

    }
}

이는 어떤 구현체를 사용하는지에 따라 구현이 달라지는데, JPA를 사용한다는 전제 하에 설명을 진행하겠습니다.







transaction 객체 내부에는 SavePointEntityManagerHolder, ConnectionHolder 가 저장됩니다. EntityManagerHolder와 ConnectionHolder는 각각 EntityManager, Connection을 랩핑한 객체로, 객체를 직접적으로 사용하지 않도록 래핑 해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class JpaTransactionManager extends AbstractPlatformTransactionManager
    implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {

    ......

    @Override
    protected Object doGetTransaction() {
        // SavePoint 저장
        JpaTransactionManager.JpaTransactionObject txObject = new JpaTransactionManager.JpaTransactionObject();
        txObject.setSavepointAllowed(isNestedTransactionAllowed());

        EntityManagerHolder emHolder = (EntityManagerHolder)
            TransactionSynchronizationManager.getResource(obtainEntityManagerFactory());
        if (emHolder != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found thread-bound EntityManager [" + emHolder.getEntityManager() +
                    "] for JPA transaction");
            }
            // EntityManagerHolder 저장
            txObject.setEntityManagerHolder(emHolder, false);
        }

        if (getDataSource() != null) {
            ConnectionHolder conHolder = (ConnectionHolder)
                TransactionSynchronizationManager.getResource(getDataSource());
            // ConnectionHolder 저장
            txObject.setConnectionHolder(conHolder);
        }

        return txObject;
    }
}

EntityManagerHolder 처음 시작하는 트랜잭션이라면 값이 null 인데, 이는 추후 다룰 예정이기에 넘어가도록 하겠습니다.







SavePoint는 트랜잭션이 실패할 경우, 롤백할 지점 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JpaTransactionManager extends AbstractPlatformTransactionManager
    implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {

    ......

    @Override
    protected Object doGetTransaction() {
        JpaTransactionManager.JpaTransactionObject txObject = new JpaTransactionManager.JpaTransactionObject();

        // SavePoint 지정
        txObject.setSavepointAllowed(isNestedTransactionAllowed());

        ......

        return txObject;
    }
}







2-5. isExistingTransaction, getTimeout

TransactionDefinition와 transaction 객체를 생성/획득했다면 isExistingTransaction와 getTimeout 메서드로 트랜잭션의 상태를 체크합니다. isExistingTransaction 메서드는 현재 트랜잭션이 새롭게 진행하는 트랜잭션인지, 이전 트랜잭션에 이어지는 트랜잭션인지를 체크하며, getTimeout은 현재 트랜잭션이 타임아웃 됐는지, 아닌지를 체크합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    ......

    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

        // 1. TransactionDefinition 생성/획득
        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

        // 2. Transaction 객체 생성
        Object transaction = doGetTransaction();
        boolean debugEnabled = logger.isDebugEnabled();

        // 3. 존재하는 트랜잭션인지, 타임아웃인지 확인
        if (isExistingTransaction(transaction)) {
            return handleExistingTransaction(def, transaction, debugEnabled);
        }
        if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
        }
        ......

    }
}







새로 시작하는 트랜잭션이 아니라면 handleExistingTransaction 메서드로 이를 처리합니다. 즉, TransactionDefinition에 따라 다양한 트랜잭션 전파 행위를 구현합니다. 트랜잭션 전파 행위는 트랜잭션이 이미 존재할 때 새로운 트랜잭션 또는 비즈니스 로직이 어떻게 동작해야 하는지를 정의합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
            throw new IllegalTransactionStateException(
                "Existing transaction found for transaction marked with propagation 'never'");
        }

        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
            if (debugEnabled) {
                logger.debug("Suspending current transaction");
            }
            Object suspendedResources = suspend(transaction);
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(
                definition, null, false, newSynchronization, debugEnabled, suspendedResources);
        }

        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
            if (debugEnabled) {
                logger.debug("Suspending current transaction, creating new transaction with name [" + definition.getName() + "]");
            }
            SuspendedResourcesHolder suspendedResources = suspend(transaction);
            try {
                return startTransaction(definition, transaction, debugEnabled, suspendedResources);
            } catch (RuntimeException | Error beginEx) {
                resumeAfterBeginException(transaction, suspendedResources, beginEx);
                throw beginEx;
            }
        }

        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            if (!isNestedTransactionAllowed()) {
                throw new NestedTransactionNotSupportedException("Transaction manager does not allow nested transactions by default - " + "specify 'nestedTransactionAllowed' property with value 'true'");
            }
            if (debugEnabled) {
                logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
            }
            if (useSavepointForNestedTransaction()) {
                DefaultTransactionStatus status =
                    prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
                status.createAndHoldSavepoint();
                return status;
            } else {
                return startTransaction(definition, transaction, debugEnabled, null);
            }
        }

        if (debugEnabled) {
            logger.debug("Participating in existing transaction");
        }
        if (isValidateExistingTransaction()) {
            if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
                Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
                if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
                    Constants isoConstants = DefaultTransactionDefinition.constants;
                    throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] specifies isolation level which is incompatible with existing transaction: " +
                        (currentIsolationLevel != null ?
                            isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
                            "(unknown)"));
                }
            }
            if (!definition.isReadOnly()) {
                if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                    throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] is not marked as read-only but existing transaction is");
                }
            }
        }
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
    }
}







2-6. Propagation 체크

이후 트랜잭션의 Propagation을 체크해 트랜잭션의 전파 옵션에 따라 알맞은 트랜잭션을 시작합니다. 새로운 트랜잭션이 필요한 경우 트랜잭션을 새로 열며, 기존 트랜잭션에 포함될 때 기존 트랜잭션에 참여시킵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    ......
    
    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

        // 1. TransactionDefinition 생성/획득
        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

        // 2. Transaction 객체 생성
        Object transaction = doGetTransaction();
        boolean debugEnabled = logger.isDebugEnabled();

        // 3. 존재하는 트랜잭션인지, 타임아웃인지 확인
        if (isExistingTransaction(transaction)) {
            return handleExistingTransaction(def, transaction, debugEnabled);
        }
        if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
        }

        // 4. Propagation 확인 및 트랜잭션 시작
        if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
        }
        else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
            }
            try {
                // 트랜잭션 시작
                return startTransaction(def, transaction, debugEnabled, suspendedResources);
            }
            catch (RuntimeException | Error ex) {
                resume(null, suspendedResources);
                throw ex;
            }
        }
        else {
            if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
                logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                    "isolation level will effectively be ignored: " + def);
            }
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
        }
    }
}







2-7. startTransaction

트랜잭션이 새롭게 시작하는 경우를 살펴보겠습니다. 이 경우 startTransaction 메서드가 호출됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

        // 1. TransactionDefinition 생성/획득
        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

        // 2. Transaction 객체 생성
        Object transaction = doGetTransaction();
        boolean debugEnabled = logger.isDebugEnabled();

        // 3. 존재하는 트랜잭션인지, 타임아웃인지 확인
        if (isExistingTransaction(transaction)) {
            return handleExistingTransaction(def, transaction, debugEnabled);
        }
        if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
        }

        // 4. Propagation 확인 및 트랜잭션 시작
        if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
        }
        else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
            }
            try {
                // 5. 트랜잭션 시작
                return startTransaction(def, transaction, debugEnabled, suspendedResources);
            }
            catch (RuntimeException | Error ex) {
                resume(null, suspendedResources);
                throw ex;
            }
        }
        
        ......

    }
}







내부에서는 DefaultTransactionStatus를 생성, doBegin 메서드를 호출해 데이터베이스와 커넥션을 맺기 위한 준비를 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    ......

    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;
    }

    ......

}







2-8. doBegin

doBegin 메서드 내부에서는 데이터베이스와 커넥션을 맺기 위해 현재 트랜잭션과 EntityManager를 ThreadLocal 에 바인딩 하고, 트랜잭션의 활성 여부를 지정 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class JpaTransactionManager extends AbstractPlatformTransactionManager
    implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {

    ......
    
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        JpaTransactionObject txObject = (JpaTransactionObject) transaction;

        if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            throw new IllegalTransactionStateException("......");
        }

        try {
            if (!txObject.hasEntityManagerHolder() ||
                txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
                EntityManager newEm = createEntityManagerForTransaction();
                if (logger.isDebugEnabled()) {
                    logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
                }

                // 최초 트랜잭션이면 EntityManagerHolder 등록
                txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
            }

            EntityManager em = txObject.getEntityManagerHolder().getEntityManager();

            int timeoutToUse = determineTimeout(definition);

            // Connection 및 트랜잭션 정보 등록
            Object transactionData = getJpaDialect().beginTransaction(em,
                new JpaTransactionManager.JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder())
            );
            txObject.setTransactionData(transactionData);
            txObject.setReadOnly(definition.isReadOnly());

            if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getEntityManagerHolder().setTimeoutInSeconds(timeoutToUse);
            }

            if (getDataSource() != null) {
                ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
                if (conHandle != null) {
                    ConnectionHolder conHolder = new ConnectionHolder(conHandle);
                    if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
                        conHolder.setTimeoutInSeconds(timeoutToUse);
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Exposing JPA transaction as JDBC [" + conHandle + "]");
                    }

                    // ThreadLocal에 트랜잭션 바인딩
                    TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);

                    // ConnectionHolder 바인딩
                    txObject.setConnectionHolder(conHolder);
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because " +
                            "JpaDialect [" + getJpaDialect() + "] does not support JDBC Connection retrieval");
                    }
                }
            }

            if (txObject.isNewEntityManagerHolder()) {
                // ThreadLocal에 EntityManager 바인딩
                TransactionSynchronizationManager.bindResource(
                    obtainEntityManagerFactory(), txObject.getEntityManagerHolder());
            }
            txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
        } catch (TransactionException ex) {
            closeEntityManagerAfterFailedBegin(txObject);
            throw ex;
        } catch (Throwable ex) {
            closeEntityManagerAfterFailedBegin(txObject);
            throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
        }
    }
}







2-9. begin

doBegin 메서드 내부에서는 entityManager를 참조해 begin 메서드가 호출되는데, 이를 통해 트랜잭션의 활성화 상태를 변경해 줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class HibernateJpaDialect extends DefaultJpaDialect {

    ......

    @Override
    public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
        throws PersistenceException, SQLException, TransactionException {

        SessionImplementor session = getSession(entityManager);

        if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
            session.getTransaction().setTimeout(definition.getTimeout());
        }

        boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
        Integer previousIsolationLevel = null;
        Connection preparedCon = null;

        if (isolationLevelNeeded || definition.isReadOnly()) {
            if (this.prepareConnection && ConnectionReleaseMode.ON_CLOSE.equals(
                session.getJdbcCoordinator().getLogicalConnection().getConnectionHandlingMode().getReleaseMode())) {
                preparedCon = session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection();
                previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(preparedCon, definition);
            } else if (isolationLevelNeeded) {
                throw new InvalidIsolationLevelException(
                    "HibernateJpaDialect is not allowed to support custom isolation levels: " +
                        "make sure that its 'prepareConnection' flag is on (the default) and that the " +
                        "Hibernate connection release mode is set to ON_CLOSE.");
            }
        }

        // 트랜잭션 Active 설정
        entityManager.getTransaction().begin();

        FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
        if (definition instanceof ResourceTransactionDefinition &&
            ((ResourceTransactionDefinition) definition).isLocalResource()) {
            previousFlushMode = null;
            if (definition.isReadOnly()) {
                session.setDefaultReadOnly(true);
            }
        }
        return new org.springframework.orm.jpa.vendor.HibernateJpaDialect.SessionTransactionData(
            session, previousFlushMode, (preparedCon != null), previousIsolationLevel, definition.isReadOnly());
    }
}







내부 메서드를 타고 가다 보면 AbstractLogicalConnectionImplementor에서 트랜잭션을 활성화하는 것을 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TransactionImpl implements TransactionImplementor {
    
    ......
    
    @Override
    public void begin() {
        if (!session.isOpen()) {
            throw new IllegalStateException("Cannot begin Transaction on closed Session/EntityManager");
        }

        if (transactionDriverControl == null) {
            transactionDriverControl = transactionCoordinator.getTransactionDriverControl();
        }

        // per-JPA
        if (isActive()) {
            if (jpaCompliance.isJpaTransactionComplianceEnabled()
                || !transactionCoordinator.getTransactionCoordinatorBuilder().isJta()) {
                throw new IllegalStateException("Transaction already active");
            } else {
                return;
            }
        }

        LOG.debug("begin");

        this.transactionDriverControl.begin();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class AbstractLogicalConnectionImplementor implements LogicalConnectionImplementor, PhysicalJdbcTransaction {
    
    ......

    @Override
    public void begin() {
        try {
            if (!doConnectionsFromProviderHaveAutoCommitDisabled()) {
                log.trace("Preparing to begin transaction via JDBC Connection.setAutoCommit(false)");
                getConnectionForTransactionManagement().setAutoCommit(false);
                log.trace("Transaction begun via JDBC Connection.setAutoCommit(false)");
            }

            // 트랜잭션 활성화
            status = TransactionStatus.ACTIVE;
        } catch (SQLException e) {
            throw new TransactionException("JDBC begin transaction failed: ", e);
        }
    }
}







2-10. prepareSynchronization

마지막으로 prepareSynchronization 메서드에서 현재 트랜잭션에 대한 정보TransactionSynchronizationManager 에 동기화한 후 트랜잭션을 시작하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    ......
    
    protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
        if (status.isNewSynchronization()) {
            TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
                definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
                    definition.getIsolationLevel() : null);
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
            TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
            TransactionSynchronizationManager.initSynchronization();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class TransactionSynchronizationManager {

    private static final ThreadLocal<Map<Object, Object>> resources =
        new NamedThreadLocal<>("Transactional resources");

    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
        new NamedThreadLocal<>("Transaction synchronizations");

    private static final ThreadLocal<String> currentTransactionName =
        new NamedThreadLocal<>("Current transaction name");

    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
        new NamedThreadLocal<>("Current transaction read-only status");

    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
        new NamedThreadLocal<>("Current transaction isolation level");

    private static final ThreadLocal<Boolean> actualTransactionActive =
        new NamedThreadLocal<>("Actual transaction active");

    ......

}







모든 메서드가 호출되었으므로, 다시 프록시를 호출했던 처음 단계로 돌아가보겠습니다. 트랜잭션이 시작될 때, createTransactionIfNecessary 메서드를 통해 TransactionInfo 객체를 생성했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

    ......

    @Nullable
    protected Object invokeWithinTransaction(
        Method method, @Nullable Class<?> targetClass,
        final InvocationCallback invocation
    ) throws Throwable {

        ......

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

        if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 1. 트랜잭션 시작
            TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

            Object retVal;
            try {
                // 2. 비즈니스 로직 처리
                retVal = invocation.proceedWithInvocation();
            } catch (Throwable ex) {
                // 3. Rollback
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            } finally {
                // 3. 트랜잭션 자원 정리
                cleanupTransactionInfo(txInfo);
            }

            ......

            // 3. Commit
            commitTransactionAfterReturning(txInfo);
            return retVal;
        } else {

            ......

            return result;
        }
    }

    ......

}







이는 PlatformTransactionManager의 getTransaction 메서드를 호출해 트랜잭션 객체를 형성했고요. 즉, 이 과정을 통해 트랜잭션을 시작하기 위한 준비가 끝났습니다. 이후 비즈니스 로직을 수행하고, 커넥션을 닫아준 후 트랜잭션을 종료하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
    
    ......
    
    protected TransactionInfo createTransactionIfNecessary(
        @Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr,
        final String joinpointIdentification
    ) {

        if (txAttr != null && txAttr.getName() == null) {
            txAttr = new DelegatingTransactionAttribute(txAttr) {
                @Override
                public String getName() {
                    return joinpointIdentification;
                }
            };
        }

        TransactionStatus status = null;
        if (txAttr != null) {
            if (tm != null) {
                status = tm.getTransaction(txAttr);
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured");
                }
            }
        }
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }

    protected TransactionInfo prepareTransactionInfo(
        @Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, String joinpointIdentification,
        @Nullable TransactionStatus status
    ) {
        TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
        if (txAttr != null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
            }
            txInfo.newTransactionStatus(status);
        } else {
            if (logger.isTraceEnabled()) {
                logger.trace("No need to create transaction for [" + joinpointIdentification +
                    "]: This method is not transactional.");
            }
        }
        txInfo.bindToThread();
        return txInfo;
    }
}







3. 정리


트랜잭션의 동작 과정 중, 트랜잭션이 어떻게 시작되는지 살펴보았습니다. 내용이 길고, 알아야 할 오브젝트들이 많지만, 이를 확실히 정리해 두면 디버깅할 때 많은 도움이 될 것입니다. 무엇보다 재미있고요. 여튼 긴 글 읽어주셔서 감사합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    ......
    
    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

        // 1. TransactionDefinition 생성/획득
        TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

        // 2. Transaction 객체 생성
        Object transaction = doGetTransaction();
        boolean debugEnabled = logger.isDebugEnabled();

        // 3. 존재하는 트랜잭션인지, 타임아웃인지 확인
        if (isExistingTransaction(transaction)) {
            return handleExistingTransaction(def, transaction, debugEnabled);
        }
        if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
        }

        // 4. Propagation 확인 및 트랜잭션 시작
        if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
        } else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
            }
            try {
                // 5. 트랜잭션 시작
                return startTransaction(def, transaction, debugEnabled, suspendedResources);
            } catch (RuntimeException | Error ex) {
                resume(null, suspendedResources);
                throw ex;
            }
        } else {
            if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
                logger.warn("Custom isolation level specified but no actual transaction initiated; " + "isolation level will effectively be ignored: " + def);
            }
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
        }
    }
}

This post is licensed under CC BY 4.0 by the author.

TransactionManager의 종류는 어떤 것들이 있을까?

Proxy패턴과 Decorator패턴의 차이는 무엇일까?