How Transactions Work in Spring
IntermediateA transaction groups database operations so they all succeed or all fail — the "A" in ACID. In Spring you rarely manage this by hand: annotating a method @Transactional tells Spring to start a transaction before it runs, commit if it returns normally, and roll back if it throws. Spring implements this with a proxy around your bean, which is why a few well-known rules — public methods, no self-invocation, correct rollback config — decide whether it actually works.
Think of a transaction as a bank transfer
Moving money means debit one account and credit another. If the credit fails after the debit, money vanishes. A transaction wraps both steps as one unit: either both happen or neither does. @Transactional is like telling the bank "treat everything I do in this method as one transfer" — if any step fails, the bank reverses all of it and the accounts look untouched.
Step by Step
Key Concepts
Propagation
How a transactional method behaves relative to an existing transaction. REQUIRED joins or creates one; REQUIRES_NEW always starts an independent transaction (useful for audit logs that must persist even if the outer transaction rolls back).
Rollback Rules
By default Spring rolls back on unchecked (RuntimeException) exceptions but commits on checked ones. Override with @Transactional(rollbackFor = Exception.class) when checked exceptions should also roll back.
Self-Invocation Gotcha
Because transactions run through a proxy, calling a @Transactional method from another method in the same class bypasses the proxy — so the transaction never starts. Call it from a different bean instead.
Isolation Level
The degree to which concurrent transactions are isolated. Higher levels prevent anomalies (dirty, non-repeatable, phantom reads) but reduce concurrency and increase locking.
Key Facts
- Only unchecked exceptions trigger an automatic rollback by default — set rollbackFor to include checked exceptions when needed.
- A @Transactional call to another method of the same class does nothing, because self-invocation does not pass through the Spring proxy.
- Keep transactions short: never make slow remote/API calls inside a transaction, or you hold database locks far longer than necessary.
Real-World Applications
Multi-step business operations
Placing an order reserves stock, charges payment, and writes the order in one @Transactional method — if the charge fails, the reservation and order are rolled back, leaving no partial state.
Guaranteed audit logging
Wrapping the audit write in a REQUIRES_NEW transaction ensures the log entry commits independently, so even if the main operation rolls back, you still have a record of the attempt.
Frequently Asked Questions
Why does my @Transactional annotation have no effect?
Usually one of three reasons: the method is not public, it is called from within the same class (self-invocation bypasses the proxy), or the bean is not managed by Spring. Transactions apply only to proxied, externally-invoked public methods.
Does @Transactional roll back on checked exceptions?
No — by default Spring rolls back only on unchecked (RuntimeException/Error) exceptions and commits on checked ones. To roll back on a checked exception, use @Transactional(rollbackFor = MyCheckedException.class).
What is the difference between REQUIRED and REQUIRES_NEW propagation?
REQUIRED (the default) joins an existing transaction or creates one if none exists. REQUIRES_NEW always suspends any existing transaction and starts an independent one, so its commit or rollback is separate from the outer transaction.
What does transaction isolation control?
It controls how much one transaction sees of another concurrent, uncommitted transaction. Lower levels (READ_COMMITTED) allow more concurrency but risk anomalies; higher levels (SERIALIZABLE) prevent anomalies but reduce concurrency and increase locking.