Understanding Spring's @Transactional Annotation: Implementation and AOP Mechanics

This article explains the purpose and inner workings of Spring's @Transactional annotation, detailing how AOP and dynamic proxies create transactional proxies, the role of BeanPostProcessor, advisors, TransactionInterceptor, and the transaction lifecycle, accompanied by code examples and diagrams for deeper insight.

Top Architect
Top Architect
Top Architect
Understanding Spring's @Transactional Annotation: Implementation and AOP Mechanics
@Transactional

is a Spring annotation for declarative transaction management, allowing automatic handling of transaction begin, commit, and rollback via AOP.

When a method is annotated with @Transactional, Spring defines a pointcut to intercept the method, creates a proxy during bean initialization, and injects the transaction advice.

The proxy creation involves the BeanPostProcessor implementation AnnotationAwareAspectJAutoProxyCreator and advisors such as BeanFactoryTransactionAttributeSourceAdvisor, which determine whether a bean requires a transactional proxy.

During method invocation, the proxy's intercept method ( DynamicAdvisedInterceptor#intercept) builds an interceptor chain that includes TransactionInterceptor, which ultimately calls TransactionInterceptor#invoke.

@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
    // Analyze whether the method is marked with @Transactional
    AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, Transactional.class);
    if (attributes != null) {
        return parseTransactionAnnotation(attributes);
    } else {
        return null;
    }
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Class<?> targetClass = null;
    Object target = null;
    try {
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        target = getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        } else {
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    } finally {
        if (target != null) {
            releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
    // Determine target class
    Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
    // Delegate to TransactionAspectSupport
    return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
        @Override
        public Object proceedWithInvocation() throws Throwable {
            return invocation.proceed();
        }
    });
}
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass);
    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            retVal = invocation.proceedWithInvocation();
        } catch (Throwable ex) {
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            cleanupTransactionInfo(txInfo);
        }
        commitTransactionAfterReturning(txInfo);
        return retVal;
    } else {
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, new TransactionCallback<Object>() {
                @Override
                public Object doInTransaction(TransactionStatus status) {
                    TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                    try {
                        return invocation.proceedWithInvocation();
                    } catch (Throwable ex) {
                        if (txAttr.rollbackOn(ex)) {
                            if (ex instanceof RuntimeException) {
                                throw (RuntimeException) ex;
                            } else {
                                throw new ThrowableHolderException(ex);
                            }
                        } else {
                            return new ThrowableHolder(ex);
                        }
                    } finally {
                        cleanupTransactionInfo(txInfo);
                    }
                }
            });
            if (result instanceof ThrowableHolder) {
                throw ((ThrowableHolder) result).getThrowable();
            } else {
                return result;
            }
        } catch (ThrowableHolderException ex) {
            throw ex.getCause();
        }
    }
}

The TransactionInterceptor manages transaction boundaries: it obtains the TransactionAttribute, starts a transaction via the PlatformTransactionManager, proceeds with the target method, and commits or rolls back based on whether an exception occurs.

Overall, the article walks through the source code to illustrate how Spring implements @Transactional using AOP, dynamic proxies, and transaction interceptors, providing a clear mapping from high‑level annotation to low‑level execution flow.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaaopBackend Developmentspringtransactionaltransaction-management
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.