Interview Prep Zoneby CuriouserLabs

Question 1 of 5 · senior level

How does @Transactional work under the hood? Where does the transaction actually live?

🎤 Say this first

An AOP proxy wraps the bean; on entry, TransactionInterceptor asks the PlatformTransactionManager for a transaction per the propagation rules. The manager begins one on a JDBC connection (autocommit off) and binds that connection to the current thread via TransactionSynchronizationManager (ThreadLocal). Every data-access call inside the method transparently picks up that same bound connection — that's how JdbcTemplate and JPA join in. On return: commit; on a matching exception: rollback. Thread-bound is the key mental model — and the reason async code silently escapes your transaction.

The full picture

  • Cast of components: proxy → TransactionInterceptorPlatformTransactionManager (JDBC: DataSourceTransactionManager, JPA: JpaTransactionManager) → TransactionSynchronizationManager (the ThreadLocal registry) → your DataSource's connection.
  • Why repositories 'just join': DataSourceUtils.getConnection(ds) / the JPA EntityManager both consult the ThreadLocal first — same thread, same transaction. Different thread (@Async, CompletableFuture, new Thread) = no bound resource = autocommit or a brand-new transaction.
  • Synchronizations: afterCommit/afterCompletion callbacks registered on the thread power @TransactionalEventListener and cache/messaging coordination.
  • Read-only is an optimization hint (Hibernate skips dirty-checking/flush; some drivers route to replicas), not an enforcement mechanism.
The async escape hatch bug
@Transactional
public void placeOrder(Order o) {
    repo.save(o);                              // TX1, thread A
    CompletableFuture.runAsync(() ->
        auditRepo.log(o.id()));                // ❌ thread B: NOT in TX1 —
}                                              //    commits even if TX1 rolls back

// If audit must share the outcome, keep it on the thread;
// if it must survive independently, make that explicit (REQUIRES_NEW or outbox).

🔄 Likely follow-up questions

  • What happens if you call a @Transactional method via this — connect it to the proxy model.
  • How does JpaTransactionManager coordinate the EntityManager and the JDBC connection?
  • What does @Transactional on a private method do? (nothing — and why)