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

  • @Transactional wraps all database calls in a method into a single unit
  • If any call fails, all changes roll back automatically
  • Spring rolls back on RuntimeException by default; use rollbackFor for checked exceptions
  • Propagation controls how nested transactions interact
  • Use readOnly = true on read-only methods for better performance

Leave a Comment

Your email address will not be published. Required fields are marked *