How Spring Uses AOP to Implement Transaction Management

This article explains why transaction control is essential for database‑backed applications, compares native JDBC/MyBatis handling with Spring's unified approach, details the core transaction APIs, and walks through the AOP‑based mechanism—including @EnableTransactionManagement, proxy generation, and CGLIB interception—that automatically opens, commits, and rolls back transactions at runtime.

Shepherd Advanced Notes
Shepherd Advanced Notes
Shepherd Advanced Notes
How Spring Uses AOP to Implement Transaction Management

Overview

Database‑backed systems require transaction handling. JDBC uses Connection, MyBatis uses SqlSession, and switching between data‑access technologies forces developers to rewrite transaction code. Spring provides a unified transaction abstraction that works with any persistence framework.

Programmatic transaction control : Spring supplies classes and methods that can be called directly, but this couples transaction code with business logic.

Declarative transaction control : Spring hides transaction code behind XML or annotation configuration, decoupling it from business logic.

Spring Transaction Management and Encapsulation

Native Transaction Control

Before any framework abstraction, developers manage transactions manually with the JDBC API:

// Load driver
Class.forName("com.mysql.jdbc.Driver");
// Get connection
Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT ...");
// Commit
conn.commit();
// Rollback (if needed)
// conn.rollback();

Repeating these steps for every database interaction leads to verbose and error‑prone code. Spring encapsulates this process so developers can focus on business logic while the framework handles transaction boundaries via AOP.

Spring Transaction API

Spring implements transaction management using the Template Method pattern. Core abstractions are:

PlatformTransactionManager : top‑level interface defining getTransaction, commit, and rollback.

TransactionDefinition : encapsulates attributes such as isolation level, propagation behavior, timeout, and read‑only flag.

TransactionStatus : holds runtime state (new, completed, rollback‑only, savepoint information).

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

Spring provides AbstractPlatformTransactionManager as a base class; concrete subclasses such as DataSourceTransactionManager (for MyBatis) and HibernateTransactionManager implement platform‑specific details. Mixing MyBatis and Hibernate in the same method can cause mismatched transaction boundaries because each framework supplies its own transaction manager.

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    boolean hasSavepoint();
    @Override void flush();
    boolean isNewTransaction();
    void setRollbackOnly();
    boolean isRollbackOnly();
    boolean isCompleted();
    Object createSavepoint() throws TransactionException;
    void rollbackToSavepoint(Object savepoint) throws TransactionException;
    void releaseSavepoint(Object savepoint) throws TransactionException;
}

Programmatic Transaction Implementation

Using the low‑level API, developers can control transactions with TransactionTemplate and TransactionCallback:

transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setTimeout(30000);
transactionTemplate.execute(new TransactionCallback<Object>() {
    @Override
    public Object doInTransaction(TransactionStatus status) {
        // business logic here
        return null;
    }
});

Declarative Transaction Management

Example

The @Transactional annotation declares a transaction. The following method adds a user and its roles:

@Transactional(rollbackFor = Exception.class)
public void addUser(UserParam param) {
    String username = param.getUsername();
    checkUsernameUnique(username);
    User user = PtcBeanUtils.copy(param, User.class);
    userDAO.insert(user);
    if (!CollectionUtils.isEmpty(param.getRoleIds())) {
        userRoleService.addUserRole(user.getId(), param.getRoleIds());
    }
}

When invoked, Spring automatically opens a transaction, commits on successful return, and rolls back if an exception propagates.

Implementation Principle

The @EnableTransactionManagement annotation activates annotation‑driven transaction handling. Its definition imports TransactionManagementConfigurationSelector, which decides between proxy‑based (default) and AspectJ‑based AOP:

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
            case ASPECTJ:
                return new String[] {determineTransactionAspectClass()};
            default:
                return null;
        }
    }
}

During container startup, AutoProxyRegistrar registers an AbstractAdvisorAutoProxyCreator that scans beans for eligible advisors. For transaction management, the key advisor is BeanFactoryTransactionAttributeSourceAdvisor, which matches methods annotated with @Transactional.

Spring AOP proxy generation diagram
Spring AOP proxy generation diagram

When a bean such as UserService is marked with @Transactional, the auto‑proxy creator generates a CGLIB subclass (Spring Boot 2.x defaults to CGLIB). The generated proxy intercepts method calls via DynamicAdvisedInterceptor:

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    // obtain target, build interceptor chain, invoke transaction interceptor, etc.
    return retVal;
}

The interceptor chain always contains a TransactionInterceptor. Its invoke method delegates to TransactionAspectSupport.invokeWithinTransaction, which performs the following steps:

Retrieve the TransactionAttribute (propagation, isolation, timeout, etc.) for the method.

Obtain the appropriate PlatformTransactionManager (e.g., DataSourceTransactionManager).

Create a transaction if necessary.

Execute the target method.

On normal return, commit the transaction; on exception, roll back.

Clean up thread‑local transaction info.

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
    TransactionAttributeSource tas = getTransactionAttributeSource();
    TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    TransactionManager tm = determineTransactionManager(txAttr);
    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    if (txAttr == null) {
        return invocation.proceed();
    }
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, methodIdentification(method, targetClass, txAttr));
    try {
        Object retVal = invocation.proceed();
        commitTransactionAfterReturning(txInfo);
        return retVal;
    } catch (Throwable ex) {
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    } finally {
        cleanupTransactionInfo(txInfo);
    }
}

Advisor Discovery Process

AutoProxyRegistrar

calls AopConfigUtils.registerAutoProxyCreatorIfNecessary to register AbstractAdvisorAutoProxyCreator. The method #getAdvicesAndAdvisorsForBean() determines whether a bean has matching advisors:

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}
#findEligibleAdvisors()

filters candidate advisors:

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}
#findCandidateAdvisors()

returns all Spring advisors, including BeanFactoryTransactionAttributeSourceAdvisor:

@Override
protected List<Advisor> findCandidateAdvisors() {
    List<Advisor> advisors = super.findCandidateAdvisors();
    if (this.aspectJAdvisorsBuilder != null) {
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    return advisors;
}

The transaction advisor matches methods annotated with @Transactional via TransactionAttributeSourcePointcut.matches(). The attribute source eventually calls

SpringTransactionAnnotationParser#parseTransactionAnnotation()

to read the annotation:

@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
    AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, Transactional.class);
    if (attributes != null) {
        return parseTransactionAnnotation(attributes);
    } else {
        return null;
    }
}

CGLIB Proxy Generation

Spring’s default CGLIB proxy creation is performed by ObjenesisCglibAopProxy, which extends CglibAopProxy. The core interception method is #intercept():

@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();
    try {
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            try {
                retVal = methodProxy.invoke(target, argsToUse);
            } catch (CodeGenerationException ex) {
                CglibMethodInvocation.logFastClassGenerationFailure(method);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, 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 && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

The interceptor chain includes TransactionInterceptor. Its #invoke() method adapts to TransactionAspectSupport.invokeWithinTransaction:

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

The core transaction logic resides in #invokeWithinTransaction():

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
    TransactionAttributeSource tas = getTransactionAttributeSource();
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
    final TransactionManager tm = determineTransactionManager(txAttr);
    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
        Object retVal;
        try {
            retVal = invocation.proceed();
        } catch (Throwable ex) {
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            cleanupTransactionInfo(txInfo);
        }
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
    // (Other branches omitted for brevity)
}

Summary

Spring leverages AOP to automate transaction boundaries. Adding @Transactional to a method triggers proxy creation, advisor matching, and the TransactionInterceptor workflow, which opens, commits, or rolls back a transaction based on method execution outcome. Understanding the proxy generation, advisor discovery, and interceptor chain helps diagnose issues such as transaction loss when multiple persistence frameworks use different transaction managers.

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.

AOPBackend DevelopmentSpring@TransactionalTransaction ManagementCGLIBPlatformTransactionManager
Shepherd Advanced Notes
Written by

Shepherd Advanced Notes

Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.

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.