What is Rollback in Transactions
A Rollback is a fundamental database management operation that aborts the active transaction, completely undoing all data mutations executed within it and returning the database...
🟢 Junior Level
A Rollback is a fundamental database management operation that aborts the active transaction, completely undoing all data mutations executed within it and returning the database to the exact consistent state it occupied before BEGIN or START TRANSACTION was issued.
The Core Concept in 30 Seconds
Rollback is the direct realization of Atomicity (the “A” in ACID):
“All operations within a transaction must either be permanently committed together (
COMMIT), or upon the slightest failure, completely reversed (ROLLBACK) as if they had never occurred.”
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
-- If an error occurs (e.g., negative balance check-constraint fails):
ROLLBACK;
-- Both accounts are restored to their original balances instantaneously!
Automatic Rollback in Spring Boot
In Spring Framework, the @Transactional annotation intercepts unhandled exceptions through an AOP proxy and instructs the underlying transaction manager to issue a rollback:
@Service
public class PaymentProcessingService {
private final WalletRepository walletRepository;
public PaymentProcessingService(WalletRepository walletRepository) {
this.walletRepository = walletRepository;
}
@Transactional // Defaults to rollback on RuntimeException and Error
public void processPayment(Long walletId, BigDecimal amount) {
Wallet wallet = walletRepository.findById(walletId).orElseThrow();
wallet.setBalance(wallet.getBalance().subtract(amount));
walletRepository.save(wallet);
if (wallet.getBalance().compareTo(BigDecimal.ZERO) < 0) {
// Throwing an exception triggers an automatic connection.rollback()!
throw new IllegalStateException("Wallet balance cannot be negative!");
}
}
}
The Primary Spring Default Rule
[!WARNING] By default, Spring triggers a
ROLLBACKstrictly for unchecked exceptions (RuntimeException) and system errors (Error). If a method throws a checked exception (IOException,SQLException, or custom subclasses ofjava.lang.Exception), Spring interprets it as an anticipated business alternative and executes aCOMMIT!
🟡 Middle Level
Rollback Trigger Mechanisms in Spring
1. Declarative Rollback (Via Exceptions)
The standard, most expressive approach:
@Transactional(rollbackFor = Exception.class)
public void executeOrder() throws Exception { ... }
2. Programmatic Rollback (Without Throwing Exceptions)
When business requirements require cancelling database writes while returning a structured error response to the client (avoiding HTTP 500 exceptions):
@Service
public class SafeOrderService {
@Transactional
public OrderResponse placeOrderSafely(OrderRequest request) {
saveOrder(request);
boolean paymentSuccess = externalGateway.charge(request.getAmount());
if (!paymentSuccess) {
// Programmatically mark the transaction for rollback without raising an exception:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return new OrderResponse(false, "Payment declined by issuing bank");
}
return new OrderResponse(true, "Order completed successfully");
}
}
The Rollback-Only Flag and UnexpectedRollbackException
When transactional methods are composed under default Propagation.REQUIRED:
- Method
outer()invokes methodinner(). - Both methods participate in the same underlying physical database transaction.
- Method
inner()throws aRuntimeException. - The inner proxy’s
TransactionInterceptorcatches the exception and invokestransactionStatus.setRollbackOnly(). - If
outer()catches this exception in atry-catchblock and attempts to complete cleanly, Spring inspects the transaction status at the outer boundary. - Seeing
isRollbackOnly() == true, Spring executesconnection.rollback()and throwsUnexpectedRollbackExceptionto prevent the caller from mistakenly assuming the operation succeeded.
[ outer() REQUIRED ] ───────────────────────────┐
│ │ (Shared physical transaction)
▼ │
[ inner() REQUIRED ] │
│ │
├── Throws RuntimeException! │
└── TransactionStatus.setRollbackOnly() ────┘
│
[ outer() catches error in try-catch and attempts clean exit ]
│
▼
Spring detects Rollback-Only flag:
──► connection.rollback()
──► throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");
🔴 Senior Level
Physical Rollback Execution in DBMS Kernels: PostgreSQL vs. MySQL
Different relational database engines implement transaction rollbacks with fundamentally different algorithmic complexity:
| Engine | Rollback Complexity | Physical Engine Mechanism | Performance Impact |
|---|---|---|---|
| PostgreSQL | Logical Rollback: $O(1)$ | Writes TRANSACTION_STATUS_ABORTED (2 bits) into CLOG (pg_xact) and logs XACT_ABORT to WAL. Row locks are released in RAM. |
Rollback completes in sub-milliseconds, regardless of whether 10 rows or 10,000,000 rows were modified. Dead tuples are cleaned up later by autovacuum. |
| MySQL (InnoDB) | Physical Rollback: $O(N)$ | Traverses the transaction’s Undo Log records in reverse order, physically restoring historical column values across data pages in the buffer pool and Clustered Index (B+Tree). | Rollback duration is proportional to the number of mutations. Rolling back a bulk update of 10 million rows can paralyze MySQL for several minutes! |
The JVM Heap / Hibernate L1 Cache Inconsistency Trap
The most subtle architectural hazard of transaction rollbacks in Java applications:
The database cleanly reverts row state on disk, but the Java Virtual Machine DOES NOT revert field values of objects in the JVM Heap!
@Transactional
public void modifyAccount(Long accountId) {
Account account = accountRepository.findById(accountId).orElseThrow();
account.setBalance(BigDecimal.ZERO); // Heap object mutated in RAM!
// Suppose an unexpected database constraint or exception occurs here:
throw new RuntimeException("Database error");
}
What happens:
- The database executes
ROLLBACK: in the database table,balanceremains$10,000. - Inside the JVM heap, the Java
accountinstance still hasbalance == 0! - Hibernate’s
PersistenceContext(L1 Cache) is now desynchronized from the actual database state. - If this
EntityManagerinstance is reused (such as in batch jobs or background tasks), subsequent reads will retrieve stale, corrupted entity data from the L1 cache. - Senior Solution: After an unexpected rollback, the active Hibernate session must be cleared via
entityManager.clear()or discarded completely.
Non-Transactional Side Effects
// 💥 HAZARDOUS PRODUCTION PATTERN:
@Transactional
public void processOrder(Order order) {
orderRepository.save(order);
emailService.sendConfirmationEmail(order); // Network I/O!
paymentGateway.chargeCard(order); // 💥 If this fails:
// Database rolls back, BUT the user ALREADY received the confirmation email!
}
[!TIP] Production Fix: Defer external side effects (emails, message queue dispatches) until the transaction has committed successfully using
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)orTransactionSynchronizationManager.registerSynchronization().
4 Tricky Questions
1. What happens to auto-increment sequences (SEQUENCE in PostgreSQL / AUTO_INCREMENT in MySQL) upon a ROLLBACK?
Answer: Auto-increment sequence values are NEVER rolled back. Database sequence generators operate outside the transactional MVCC model:
- Calling
nextval('seq')atomically advances the sequence counter in shared memory using short-lived spinlocks and immediately commits the new value. - If a transaction that consumed ID 105 rolls back, ID 105 is permanently lost. The subsequent transaction receives ID 106.
- This creates permanent “gaps” in primary key numbering (e.g., IDs 1, 2, 5, 8). This is standard and intentional in relational databases. Enforcing strictly contiguous sequence numbers would require locking the entire table exclusively until transaction commit, destroying concurrent write throughput.
2. Why does rolling back a 1,000,000-row update take 1 millisecond in PostgreSQL, but several minutes in MySQL InnoDB?
Answer:
- In PostgreSQL, rollback is purely logical ($O(1)$). The backend simply sets 2 bits in the commit log (
pg_xact) toTRANSACTION_STATUS_ABORTEDand appends an abort record to WAL. The database does not touch the mutated table pages. The newly written tuples simply become invisible to other transactions under MVCC rules, and their physical cleanup is deferred to the asynchronousautovacuumprocess. - In MySQL InnoDB, rollback is an active physical undo operation ($O(N)$). The storage engine must sequentially traverse the transaction’s Undo Log chain in reverse and physically overwrite modified B+Tree pages in the buffer pool with their original column values. For 1,000,000 rows, InnoDB performs 1,000,000 page modifications, saturating CPU and disk I/O.
3. How does ROLLBACK affect entity versioning (@Version / Optimistic Locking) in Spring Data JPA?
Answer:
Upon transaction rollback, the @Version field of in-memory Java entity instances is not restored to its previous value.
When Hibernate prepares an entity for update, it pre-increments the @Version attribute in memory from 1 to 2. If an exception triggers a rollback:
- In the database, the record retains version 1.
- In the JVM heap, the entity object retains version 2.
- If application code attempts to persist this same entity instance in a subsequent transaction, Hibernate generates
UPDATE ... WHERE id = ? AND version = 2. Because the database row still has version 1, zero rows are updated, and Hibernate throws a spuriousOptimisticLockException(StaleObjectStateException). Entities involved in rolled-back transactions must be evicted and re-read from the database.
4. What happens if an exception is caught in a try-catch block inside a @Transactional method without re-throwing it or taking further action?
Answer:
A Silent Commit occurs!
Spring’s AOP proxy intercepts exceptions only when they propagate out of the method boundary. If application code catches and swallows an exception (catch (Exception e) { log.error("Ignored", e); }), Spring assumes the business operation completed successfully. The interceptor invokes connection.commit(). Mutations executed prior to the error are permanently persisted to the database, potentially leaving business entities in a corrupt, half-finished state.
🎯 Interview Cheat Sheet
- Core Definition: Reverts all uncommitted changes of a transaction to enforce ACID Atomicity.
- Spring Default Policy: Automatically rolls back on
RuntimeExceptionandError; commits on checked exceptions (Exception). - Rollback-Only Flag: If an inner
REQUIREDmethod fails, it marks the transactionrollback-only. Outertry-catchhandling will fail at commit withUnexpectedRollbackException. - Programmatic Rollback: Invoke
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(). - Engine Mechanics:
PostgreSQL: Logical $O(1)$ abort via CLOG status update; cleanup deferred toautovacuum.MySQL InnoDB: Physical $O(N)$ undo log replay overwriting clustered index pages.
- Sequence Gaps: Sequence counters (
nextval) are non-transactional and never roll back. - Heap Desynchronization: JVM heap objects retain their mutated values after a database rollback; call
entityManager.clear()to prevent corrupted L1 reads.