Posts 스프링 @Transactional 살펴보기 (AOP, ThreadLocal)
Post
Cancel

스프링 @Transactional 살펴보기 (AOP, ThreadLocal)

HelloService\(EnhancerBySpringCGLIB\)ba960935 (hello.springboot.controller) -> org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept -> org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation#proceed -> org.springframework.aop.framework.ReflectiveMethodInvocation#proceed -> org.springframework.transaction.interceptor.TransactionInterceptor#invoke -> org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

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
    // TransactionAspectSupport#invokeWithinTransaction

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

            } else {
                TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

                Object retVal;
                try {
                    retVal = invocation.proceedWithInvocation();
                } catch (Throwable var20) {
                    this.completeTransactionAfterThrowing(txInfo, var20);
                    throw var20;
                } finally {
                    this.cleanupTransactionInfo(txInfo);
                }

                if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
                    TransactionStatus status = txInfo.getTransactionStatus();
                    if (status != null && txAttr != null) {
                        retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                    }
                }

                this.commitTransactionAfterReturning(txInfo);
                return retVal;
            }
        }
    }

TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

리턴값 : org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo

트랜잭션 처리에 필요한 정보를 관리하는 객체

  • 필요한 정보 ?
    • PlatformTransactionManager
    • TransactionAttribute
    • TransactionStatus
      • 새로운 트랜잭션인지
      • readOnly인지
      • savepoint가 있는지
      • 등등..
    • joinpointIdentification (@Transactional 선언된 메서드명)
  • TransactionStatus는 PlatformTransactionManager로부터 얻어온다. ```java // org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary

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

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
- TransactionStatus는 `TransactionDefinition`과 `트랜잭션 객체`를 기반으로 만들어진다.
  - TransactionDefinition : 트랜잭션 전파 레벨, 격리 수준을 관리
  - 트랜잭션 객체
    - `AbstractPlatformTransactionManager`의 구현체가 `DataSourceTransactionManager`를 기준으로 `DataSourceTransactionObject`를 의미

```java
// org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
    TransactionDefinition def = definition != null ? definition : TransactionDefinition.withDefaults();
    Object transaction = this.doGetTransaction();
    boolean debugEnabled = this.logger.isDebugEnabled();
    if (this.isExistingTransaction(transaction)) {
        return this.handleExistingTransaction(def, transaction, debugEnabled);
    }

    ...

    } else {
        SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
        if (debugEnabled) {
            this.logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }

        try {
            return this.startTransaction(def, transaction, debugEnabled, suspendedResources);
        } catch (Error | RuntimeException var7) {
            this.resume((Object)null, suspendedResources);
            throw var7;
        }
    }
}
  • 트랜잭션 객체
    • TransactionSynchronizationManager로부터 ConnectionHolder를 가져온다. ```java // org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction

protected Object doGetTransaction() { DataSourceTransactionObject txObject = new DataSourceTransactionObject(); txObject.setSavepointAllowed(this.isNestedTransactionAllowed()); ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.obtainDataSource()); txObject.setConnectionHolder(conHolder, false); return txObject; }

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
- key값을 `DataSource`로해서 `ThreadLocal`에 `org.springframework.jdbc.datasource.ConnectionHolder`가 매핑된다.
```java
public abstract class TransactionSynchronizationManager {
   private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");

   ...

  @Nullable
  public static Object getResource(Object key) {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    return doGetResource(actualKey);
  }

  @Nullable
  private static Object doGetResource(Object actualKey) {
    Map<Object, Object> map = (Map)resources.get();
    if (map == null) {
      return null;
    } else {
      Object value = map.get(actualKey);
      if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
        map.remove(actualKey);
        if (map.isEmpty()) {
          resources.remove();
        }

        value = null;
      }

      return value;
    }
  }

}

중간 정리

  • @Transactional이 붙은 메서드(또는 클래스 단위)를 가진 클래스는 AOP Proxy 객체 생성된다. (런타임에 ?)
  • 해당 메서드가 호출되면 TransactionInterceptor에 의해 intercept되어 TransactionAspectSupport#invokeWithinTransaction가 실행된다.
  • TransactionAspectSupport#invokeWithinTransaction의 실행 흐름은 다음과 같다.
    • 트랜잭션 처리에 필요한 정보 가져오기 (기존에 트랜잭션이 없으면 트랜잭션 생성)
    • 타겟 메서드 실행
    • 커밋
  • 트랜잭션 처리에 필요한 정보 중 TransactionStatus를 만드는 과정
    • TransactionDefinition과 트랜잭션 객체이 필요
    • 특히, 트랜잭션 객체에는 TransactionSynchronizationManager에서 DataSource를 key값으로 매핑하여 관리하는 ConnectionHolder 정보가 담겨있음
      • ConnectionHolder는 스레드 단위로 바인딩됨
  • 트랜잭션 시작
    • this.doBegin(transaction, definition); –> 트랜잭션 시작이라는건 결국 ConnectionHolder를 현재 스레드에 바인딩 시키는게 가장 키포인트인 것 같다.
    • this.prepareSynchronization(status, definition); –> 트랜잭션 동기화 ? –> 트랜잭션 전파 관련해서 관리하기 위해 ?
1
2
3
4
5
6
7
8
9
// org.springframework.transaction.support.AbstractPlatformTransactionManager#startTransaction

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
  boolean newSynchronization = this.getTransactionSynchronization() != 2;
  DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
  this.doBegin(transaction, definition);
  this.prepareSynchronization(status, definition);
  return status;
}
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
// org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

protected void doBegin(Object transaction, TransactionDefinition definition) {
  DataSourceTransactionObject txObject = (DataSourceTransactionObject)transaction;
  Connection con = null;

  try {
    if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
      Connection newCon = this.obtainDataSource().getConnection();
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
      }

      txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
    }

    txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
    con = txObject.getConnectionHolder().getConnection();
    Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
    txObject.setPreviousIsolationLevel(previousIsolationLevel);
    txObject.setReadOnly(definition.isReadOnly());
    if (con.getAutoCommit()) {
      txObject.setMustRestoreAutoCommit(true);
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
      }

      con.setAutoCommit(false);
    }

    this.prepareTransactionalConnection(con, definition);
    txObject.getConnectionHolder().setTransactionActive(true);
    int timeout = this.determineTimeout(definition);
    if (timeout != -1) {
      txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
    }

    if (txObject.isNewConnectionHolder()) {
      TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
    }

  } catch (Throwable var7) {
    if (txObject.isNewConnectionHolder()) {
      DataSourceUtils.releaseConnection(con, this.obtainDataSource());
      txObject.setConnectionHolder((ConnectionHolder)null, false);
    }

    throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
  }
}
1
2
3
4
5
6
7
8
9
10
11
// org.springframework.transaction.support.AbstractPlatformTransactionManager#prepareSynchronization

protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(definition.getIsolationLevel() != -1 ? definition.getIsolationLevel() : null);
        TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
        TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
        TransactionSynchronizationManager.initSynchronization();
    }
}

this.commitTransactionAfterReturning(txInfo);

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

MySQL - 인덱스 살펴보기

API 호출시 타임아웃이 발생하면 ? (caller 관점에서)