DispatcherServlet.doDispatch() 살펴보기 이전 글에서 가독성이 떨어져 이번 글에는 코드를 최대한 첨부해 작성했습니다.
@Transactional을 메서드에 매핑하면 마법처럼(?) 트랜잭션이 생성됩니다. 이를 위해선 시작점부터 AOP를 통해 트랜잭션과 관련된 핸들링을 먼저 진행하고, 그 뒤에 실제 메서드가 호출하는 작업이 필요합니다.
트랜잭션 흐름의 첫 시작인 TransactionInterceptor를 호출하기 전, CglibAopProxy(Spring에서 제공하는 CGLIB 기반 AopProxy 구현체)을 통해 CglibMethodInvocation 인스턴스를 생성합니다.
// CglibAopProxy.DynamicAdvisedInterceptor.java
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
...
else {
// We need to create a method invocation...
retVal = **new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()**;
}
}
...
// TransactionInterceptor.java
@Override
@Nullable
public Object **invoke(MethodInvocation invocation)** throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
**...
}**
}
흐름이 TransactionInterceptor로 넘어가면 그 뒤는 TransactionAspectSupport.invokeWithinTransaction(..)가 호출되며 본격적으로 트랜잭션과 관련된 일들이 일어납니다.
본격적으로 트랜잭션 관련 행위가 일어납니다.
먼저 invokeWithinTransaction(..) 내부입니다.
// TransactionAspectSupport.java
@Nullable
protected Object **invokeWithinTransaction**(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
...
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
**// [1] 트랜잭션 객체 생성 및 시작, DB ConnectionPool 접근 및 Connection 얻어오기 등**
// Standard transaction demarcation with getTransaction and commit/rollback calls.
**TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);**
...
}
createTransactionIfNecessary(..)는 트랜잭션을 필요한 경우에만 생성하는 동작을 수행합니다. 호출 전 동작은 아래와 같습니다.
<aside> 💡 Map, ConcurrentMap을 활용해 접근이 잦은 객체를 캐싱하는 패턴이 많이 보이네요 ❗
</aside>
createTransactionIfNecessary(..)를 호출하고, TransacitonInfo 인스턴스를 반환합니다. (2번부터 6번까진 모두 createTransactionIfNecessary(..) 메서드 내부에서 일어납니다)
*Create a transaction if necessary based on the given TransactionAttribute. Allows callers to perform custom TransactionAttribute lookups through the TransactionAttributeSource.
필요한 경우 주어진 TransactionAttribute를 기반으로 트랜잭션을 생성합니다. TransactionAttributeSource를 통해 호출자가 사용자 지정 TransactionAttribute 조회를 수행할 수 있습니다.
TransactionAttribute 인스턴스와 TransactionManager가 not null인 경우 getTransaction(..)를 호출하고 TransactionStatus를 가져옵니다.
// TransactionAspectSupport.java
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 {
...
}
}
return **prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);**
}
// AbstractPlatformTransactionManager.java
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
...
**Object transaction = doGetTransaction();
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
...**
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
**...**
try {
**return startTransaction(def, transaction, debugEnabled, suspendedResources);**
}
**** catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
**** } else {
...
return **prepareTransactionStatus**(def, null, true, newSynchronization, debugEnabled, null);
}
}
JpaTransactionManager에서는 EnityManagerHolder의 null 여부, transactionActive 필드(boolean)를 확인합니다.
// JpaTransactionManager.java
public boolean hasTransaction() {
return (this.entityManagerHolder != null && this.entityManagerHolder.isTransactionActive());
}
여기서 만약 트랜잭션이 이미 존재하는 경우 아래 행위 중 일부는 진행되지 않습니다. 이는 PROPAGATION 레벨에 따라 달라집니다.
startTransaction()를 호출해 앞서 생성했던 새로운 트랜잭션을 시작합니다.
// AbstractPlatformTransactionManager.java
/**
* Start a new transaction.
*/
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;
}
getJpaDialect()를 통해 HibernateJpaDialect 인스턴스를 가져온 뒤 beginTransaction(..)를 호출합니다. 이를 통해 HibernateJpaDialect.SessionTransactionData 인스턴스가 반환됩니다.
// JpaTransactionManager.java
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
...
try {
EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
// Delegate to JpaDialect for actual transaction begin.
**** int timeoutToUse = determineTimeout(definition);
**Object transactionData = getJpaDialect().beginTransaction(em,**
**new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));**
...
}
...
}
entityManager.getTransaction().begin()를 호출해 JPA 전체 컨텍스트를 준비합니다.
내부적으로 TransactionImpl.begin() → JdbcTresourceLocalTransactionCoordinatorImpl.TransactionDriverControlImpl.begin() → LogicalConnectionManagedImpl.begin()으로 이어집니다.
여기서 두번째 단계에 해당하는 TransactionDriverControlImpl.begin()을 통해 JDBC Conneciton의 Physical connection과 애플리케이션 내의 로컬 트랜잭션이 브릿징됩니다.
*The delegate bridging between the local (application facing) transaction and the "physical" notion of a transaction via the JDBC Connection.
// HibernateJpaDialect.java
@Override
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
throws PersistenceException, SQLException, TransactionException {
...
// Standard JPA transaction begin call for full JPA context setup...
**entityManager.getTransaction().begin();
...**
}
...
// LogicalConnectionManagedImpl.java
@Override
public void begin() {
initiallyAutoCommit = !doConnectionsFromProviderHaveAutoCommitDisabled() && determineInitialAutoCommitMode(
**getConnectionForTransactionManagement()** );
super.begin();
}