글을 작성하게 된 계기
사람들과 스프링 서버를 만들어보는 프로젝트를 진행하며 @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. 트랜잭션 동작 과정
트랜잭션은 아래와 같은 과정을 거쳐 시작됩니다. 이는 중요 포인트/메서드를 추출한 것으로, 전체 트랜잭션의 단계가 아닙니다. 이 외에도 수많은 포인트/메서드를 거치는데, 이에 대해서는 직접 스프링 코드를 학습해 보실 것을 권장해 드립니다.
- Proxy 호출
- createTransactionIfNecessary
- getTransaction
- doGetTransaction
- isExistingTransaction, getTimeout
- Propagation 체크
- startTransaction
- doBegin
- begin
- 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 객체 내부에는 SavePoint 와 EntityManagerHolder, 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);
}
}
}