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?
answer
- @EnableTransactionManagement registers advisor + TransactionInterceptor
- Proxy (JDK/CGLIB) → TransactionInterceptor.invoke
- Attributes → determineTransactionManager → getTransaction
- Unchecked rolls back; checked commits unless rollbackFor
- Multiple managers → @Transactional("beanName")
basics
~10 sAn 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@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
Know that @Transactional uses a proxy that commits on success and rolls back on runtime exceptions.
Explain the interceptor flow and the checked-vs-unchecked rollback default.
Detail determineTransactionManager, the qualifier for multiple managers, and self-invocation/visibility gotchas.
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.