skip to content

When you put @Transactional on a method, how does Spring get from that annotation to PlatformTransactionManager.getTransaction/commit/rollback? And how do you handle multiple transaction managers?

level: seniorimportance: should knowfreq 45%

answer

  1. @EnableTransactionManagement registers advisor + TransactionInterceptor
  2. Proxy (JDK/CGLIB) → TransactionInterceptor.invoke
  3. Attributes → determineTransactionManager → getTransaction
  4. Unchecked rolls back; checked commits unless rollbackFor
  5. Multiple managers → @Transactional("beanName")

basics

~10 s

An AOP proxy wraps the bean. Its TransactionInterceptor reads the @Transactional attributes, calls getTransaction before your method, then commit on success or rollback on a matching exception. With several managers, name one via @Transactional("managerBeanName").

solid answer

~40 s

@Transactional is declarative AOP. @EnableTransactionManagement registers an infrastructure advisor; a proxy (JDK dynamic or CGLIB) wraps the bean. On a call, TransactionInterceptor resolves the transaction attributes (propagation, isolation, rollbackFor, timeout, read-only, and the manager qualifier), obtains the right PlatformTransactionManager, and calls getTransaction(definition). After your method returns it calls commit; if a rollback-eligible exception propagates (RuntimeException/Error by default, or types listed in rollbackFor) it calls rollback. Because it's proxy-based, self-invocation within the same bean bypasses the advice, and by default only public methods are advised. With multiple managers, mark each @Transactional with the target bean name — @Transactional("orderTxManager") — or the transactionManager attribute; otherwise Spring looks for a single/primary PlatformTransactionManager bean.

code

java · 20 lines
java
@Configuration
@EnableTransactionManagement
class Config {
    @Bean JpaTransactionManager orderTxManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); }
    @Bean JpaTransactionManager auditTxManager(EntityManagerFactory emf2) { return new JpaTransactionManager(emf2); }
}

@Service
class OrderService {

    // Picks the 'orderTxManager' bean; TransactionInterceptor calls its
    // getTransaction/commit/rollback around this method.
    @Transactional(transactionManager = "orderTxManager", rollbackFor = Exception.class)
    public void placeOrder(Order o) throws BusinessException {
        // ... work; a thrown BusinessException now triggers rollback
    }

    @Transactional("auditTxManager")
    public void writeAudit(AuditRecord r) { /* separate transaction */ }
}

go deeper

for a junior

Know that @Transactional uses a proxy that commits on success and rolls back on runtime exceptions.

for a middle

Explain the interceptor flow and the checked-vs-unchecked rollback default.

for a senior

Detail determineTransactionManager, the qualifier for multiple managers, and self-invocation/visibility gotchas.

for a principal

Discuss TransactionManagementConfigurer, custom qualifier meta-annotations, ASPECTJ mode trade-offs, and why multiple managers ≠ XA.

## From annotation to the SPI `@Transactional` is **declarative** transaction management built on Spring AOP. The annotation itself does nothing; the machinery that reacts to it does. ### 1. Enablement `@EnableTransactionManagement` (or Spring Boot's `TransactionAutoConfiguration`) registers a `BeanFactoryTransactionAttributeSourceAdvisor` plus a `TransactionInterceptor` and a `TransactionAttributeSource` (usually `AnnotationTransactionAttributeSource`, which reads `@Transactional`). ### 2. Proxy creation At startup, any bean with a `@Transactional` method matches the advisor's pointcut, so Spring wraps it in a **proxy** — a JDK dynamic proxy if the bean implements an interface, otherwise a CGLIB subclass. Callers get the proxy, not the raw bean. ### 3. Per-call interception When a proxied method is invoked, control enters `TransactionInterceptor.invoke()` (which extends `TransactionAspectSupport`). It: 1. Reads the merged **transaction attributes** for that method from the `TransactionAttributeSource` — propagation, isolation, timeout, read-only, `rollbackFor`/`noRollbackFor`, and the **manager qualifier** (`transactionManager`/value). 2. **Resolves the `PlatformTransactionManager`** via `determineTransactionManager()`: uses the qualifier if present, else a cached/primary/unique manager bean. 3. Calls `manager.getTransaction(definition)` — the definition is a `TransactionDefinition` adapted from the attributes. 4. Invokes your actual method. 5. On normal return → `commitTransactionAfterReturning()` → `manager.commit(status)`. 6. On a thrown exception → `completeTransactionAfterThrowing()` which checks the rollback rules: by default **unchecked** exceptions (`RuntimeException`, `Error`) trigger `manager.rollback(status)`; **checked** exceptions commit unless listed in `rollbackFor`. So the annotation is ultimately just a driver of the three SPI methods. ## Multiple transaction managers When an application has several `PlatformTransactionManager` beans (e.g., two datasources, or JPA + JMS): - **Qualify per method**: `@Transactional("orderTxManager")` or `@Transactional(transactionManager = "orderTxManager")`. The string is the **bean name / qualifier**, matched by `determineTransactionManager`. - You can define a **custom qualifier annotation** meta-annotated with `@Transactional` to avoid repeating the name. - If no qualifier is given, Spring needs an unambiguous choice — a single bean, or one marked `@Primary`; otherwise it fails with `NoUniqueBeanDefinitionException`. - `TransactionManagementConfigurer` lets you specify the default `annotationDrivenTransactionManager()` programmatically. Important: each manager is a **separate transaction**. Two managers do *not* give you distributed atomicity; for that you need `JtaTransactionManager`/XA. ## Proxy gotchas (because it's AOP) - **Self-invocation**: calling one `@Transactional` method from another method *in the same class* goes through `this`, not the proxy, so the advice — and thus `getTransaction` — never runs. Extract to another bean or use `AopContext.currentProxy()`. - **Visibility**: with the default proxy mode only `public` methods are advised (Spring 6 relaxes some of this, but assume public). - **Checked exceptions don't roll back by default** — a frequent surprise; use `rollbackFor = Exception.class` when needed. - **AspectJ mode** (`mode = ASPECTJ`) weaves the aspect into the bytecode, removing the self-invocation limitation, but is rarely used. ## Summary `@Transactional` → advisor + `TransactionInterceptor` → resolve attributes + manager → `getTransaction` → your method → `commit`/`rollback`. Multiple managers are selected by the qualifier on the annotation.

  • Why does calling one @Transactional method from another method in the same class often not start a transaction?
    Self-invocation goes through 'this', not the Spring proxy, so TransactionInterceptor never runs and getTransaction is never called. Move the method to another bean or use AspectJ weaving.
  • By default, does a thrown checked exception roll back the transaction?
    No. Only unchecked exceptions (RuntimeException/Error) roll back by default. Checked exceptions commit unless you add rollbackFor = SomeCheckedException.class.
  • What happens if there are two PlatformTransactionManager beans and @Transactional has no qualifier?
    Resolution is ambiguous; Spring fails (NoUniqueBeanDefinitionException) unless one bean is @Primary or you implement TransactionManagementConfigurer to pick the default.

saying these in an interview costs you the question

  • Believing @Transactional works via reflection on the annotation at call time without a proxy/AOP.
  • Expecting self-invoked @Transactional methods to open a transaction.
  • Assuming checked exceptions roll back by default.
  • Thinking multiple transaction managers give distributed atomicity.

context