Spring Boot Transactions
A transaction is a group of database operations that succeed or fail together. If any one operation fails, all changes roll back — the database stays in a consistent state. Spring Boot makes transaction management simple with a single annotation.
The Bank Transfer Analogy
Transfer ₹5000 from Alice to Bob: Step 1: Deduct ₹5000 from Alice → SUCCESS Step 2: Add ₹5000 to Bob → FAILURE (server crash) Without transaction: Alice loses ₹5000. Bob gets nothing. Money vanishes. With transaction: Both steps roll back. Alice keeps her ₹5000.
Transactions enforce the ACID principle — Atomicity, Consistency, Isolation, Durability.
Using @Transactional
Annotate a service method with @Transactional to wrap all its database calls in one transaction:
@Service
public class BankService {
@Transactional ← All DB ops below run as one unit
public void transfer(Long fromId, Long toId, double amount) {
Account from = accountRepo.findById(fromId).orElseThrow();
Account to = accountRepo.findById(toId).orElseThrow();
from.setBalance(from.getBalance() - amount); ← Step 1
to.setBalance(to.getBalance() + amount); ← Step 2
accountRepo.save(from);
accountRepo.save(to);
// If any step throws an exception → both changes roll back
}
}
Transaction Lifecycle
Method called
│
▼
Transaction starts (BEGIN)
│
▼
Step 1: DB operation ─── success? ──▶ Step 2: DB operation
│ │
│ ▼ success?
│ COMMIT — all changes saved
│
▼ any exception?
ROLLBACK — all changes cancelled
Rollback Rules
By default, Spring rolls back only on unchecked exceptions (RuntimeException and its subclasses). Checked exceptions do NOT trigger a rollback unless you specify them.
// Roll back on a checked exception:
@Transactional(rollbackFor = IOException.class)
public void processFile() throws IOException { ... }
// Roll back on all exceptions:
@Transactional(rollbackFor = Exception.class)
public void criticalOperation() throws Exception { ... }
// Never roll back on a specific exception:
@Transactional(noRollbackFor = ValidationException.class)
public void process() { ... }
Transaction Propagation
Propagation controls what happens when a @Transactional method calls another @Transactional method:
Propagation Type Behavior ───────────────── ──────────────────────────────────────────────────── REQUIRED (default) Join existing transaction; start new one if none REQUIRES_NEW Always start a new transaction (suspends existing) SUPPORTS Join if exists; run without transaction if not MANDATORY Must have an existing transaction; error if none NEVER Must NOT have a transaction; error if one exists NOT_SUPPORTED Suspend existing transaction; run without one
// Example: always run in a fresh transaction
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAuditEvent(String event) {
// This saves even if the outer transaction rolls back
auditRepo.save(new AuditLog(event));
}
Transaction Isolation Levels
Level Dirty Non-Repeatable Phantom Use When ──────────────── Read Read Read ────────────────────── READ_UNCOMMITTED Yes Yes Yes Low — rare READ_COMMITTED No Yes Yes Default in most DBs REPEATABLE_READ No No Yes MySQL default SERIALIZABLE No No No Strictest (slowest)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void criticalInventoryUpdate() { ... }
Where to Place @Transactional
Layer Use @Transactional? Why ──────────── ─────────────────── ────────────────────────────────────── Service YES ✓ Business logic spans multiple DB calls Repository Rarely Spring Data already applies it Controller NO Too high a layer; mixes concerns
Read-Only Transactions
Mark read-only operations to allow database optimizations:
@Transactional(readOnly = true)
public List<User> getAllUsers() {
return userRepository.findAll();
}
Hibernate skips dirty-checking for read-only transactions, which improves performance for heavy read operations.
Summary
@Transactionalwraps all database calls in a method into a single unit- If any call fails, all changes roll back automatically
- Spring rolls back on
RuntimeExceptionby default; userollbackForfor checked exceptions - Propagation controls how nested transactions interact
- Use
readOnly = trueon read-only methods for better performance
