BackendBench
Coffee

Unravel › Java concepts › Spring and Boot

Spring

@Transactional

@Transactional makes a method run inside a database transaction: all its database changes commit together if it finishes normally and are rolled back together if it throws.

Learn these first

At a glance

Mental model

It is a shopping cart at checkout: either everything in the cart is bought, or nothing is.

What happens, in order
  1. 1Before your method runs, the proxy opens a database connection and starts a transactionEvery repository call inside the method uses the same connection. After the method returns, the proxy commits; if it throws, it rolls back.
  2. 2By default only unchecked exceptions (RuntimeException, Error) trigger a rollbackA checked exception commits unless you add rollbackFor = Exception.class.
  3. 3readOnly = true hints that no writes happen, letting the framework and database optimise (for example skipping dirty checking in Hibernate)
  4. 4propagation says what to do when a transaction already existsThe default REQUIRED joins it; REQUIRES_NEW suspends it and starts an independent one.
Remember
Watch out
Self-invocation, so no transactionSwallowing exceptions so a failed operation still commitsExpecting a checked exception to roll backLong network calls inside the transactionStarting new threads inside a transaction and expecting them to share it
Example (highlighted lines explain it)
@Transactionalpublic void transfer(long from, long to, BigDecimal amount) {  accounts.debit(from, amount);  accounts.credit(to, amount); // if this throws, the debit is undone too}
  1. line 1 uses Transactional, the core of @Transactional
  2. line 4 if this throws, the debit is undone too
Read the full explanation (about 2 min)

@Transactional makes a method run inside a database transaction: all its database changes commit together if it finishes normally and are rolled back together if it throws.

A mental model

It is a shopping cart at checkout: either everything in the cart is bought, or nothing is. If something fails midway, the cart is emptied and the shelves return to how they were.

How it works

Before your method runs, the proxy opens a database connection and starts a transaction. Every repository call inside the method uses the same connection. After the method returns, the proxy commits; if it throws, it rolls back.

By default only unchecked exceptions (RuntimeException, Error) trigger a rollback. A checked exception commits unless you add rollbackFor = Exception.class.

readOnly = true hints that no writes happen, letting the framework and database optimise (for example skipping dirty checking in Hibernate).

propagation says what to do when a transaction already exists. The default REQUIRED joins it; REQUIRES_NEW suspends it and starts an independent one.

Going deeper

It only works through the proxy. A @Transactional method called from another method of the same class runs without a transaction at all (self-invocation).

Catching the exception inside the method and not rethrowing means the proxy sees a normal return and commits, even though something went wrong. A marker like "UnexpectedRollbackException" appears when an inner transactional call marked the transaction rollback-only and the outer one tries to commit.

Keep transactions short. Calling a slow remote service or sending a Kafka message inside one holds a database connection and row locks for as long as the call takes, which exhausts the connection pool under load.

Lazy-loaded JPA relations only work inside the transaction (or session). Touching them in the controller afterwards gives LazyInitializationException.

Under the hood

The interceptor (TransactionInterceptor) asks a PlatformTransactionManager to begin, binds the connection to the current thread via TransactionSynchronizationManager, and later commits or rolls back. Since the transaction is tied to a thread, work handed to another thread is outside it.

Common mistakes

  • Self-invocation, so no transaction.
  • Swallowing exceptions so a failed operation still commits.
  • Expecting a checked exception to roll back.
  • Long network calls inside the transaction.
  • Starting new threads inside a transaction and expecting them to share it.

How Unravel recognises it

When you paste code, Unravel looks for these signs of @transactional. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Builds on this

Frequently asked questions

What is @Transactional?

@Transactional makes a method run inside a database transaction: all its database changes commit together if it finishes normally and are rolled back together if it throws.

What are the most common mistakes with @Transactional?

Self-invocation, so no transaction. Swallowing exceptions so a failed operation still commits. Expecting a checked exception to roll back. Long network calls inside the transaction. Starting new threads inside a transaction and expecting them to share it.

How can I see @Transactional in real code?

Paste your own Java into Unravel and it will point out where @transactional appears, explain how it works at the detail level you choose, and draw the flow of the code around it.