Chapter: @Transactional Annotation and Transaction Management in Spring Boot
In distributed data-backed applications, a single business operation frequently spans multiple database collections or tables. When an unexpected exception occurs halfway through such a multi-step operation, it leaves the database in an inconsistent, partially updated state. Spring Boot provides an abstraction layer to guarantee data integrity using declarative transaction management.
This chapter explores how to configure and use the @Transactional and @EnableTransactionManagement annotations in Spring Boot with MongoDB, the role of PlatformTransactionManager and MongoDatabaseFactory, exception propagation for rollbacks, and the underlying infrastructure requirements such as MongoDB replica sets.
1. The Problem: Partial Writes and Data Inconsistency
When building a feature such as adding a new journal entry for a user, the application must execute two distinct database operations:
- Insert the new journal entry into the
journal_entriescollection. - Update the user document in the
userscollection to store a reference (ObjectId) to the newly created journal entry.
The Inconsistency Scenario
If Step 1 succeeds and the document is persisted in MongoDB, but Step 2 encounters an unexpected runtime error (such as validation failure, null pointer exception, or network glitch), the system reaches a corrupted state:
- The journal entry exists in
journal_entries. - The user's document in
usersdoes not contain the reference to this entry. - The orphaned entry can never be queried or managed through regular user workflows.
Similarly, in deletion workflows, deleting a record from journal_entries without successfully removing its reference from users leaves a dangling reference to a non-existent document.
2. Core Mental Model: ACID Transactions
To prevent partial writes, database operations must be wrapped inside a Transaction that adheres to ACID properties—specifically Atomicity and Isolation.
Key ACID Dimensions in This Context
- Atomicity ("All or Nothing"): Either every single database operation inside the method executes successfully and commits, or if any error occurs, all previously executed operations within that session are reverted (rolled back).
- Isolation: When two concurrent requests arrive simultaneously (e.g., User A and User B both executing journal creation), Spring Boot and the database isolate each execution inside its own independent transactional context to prevent dirty reads and race conditions.
3. Spring Transaction Management Architecture
Spring abstracts transaction management across different data stores (RDBMS via JPA/Hibernate, MongoDB, Neo4j, etc.) through a unified interface hierarchy.
Core Architecture Components
4. Step-by-Step Implementation
Step 1: Annotating the Service Method with @Transactional
Apply the @Transactional annotation directly above the service method that orchestrates multiple database writes.
package com.example.journalApp.service;
import com.example.journalApp.entity.JournalEntry;
import com.example.journalApp.entity.User;
import com.example.journalApp.repository.JournalEntryRepository;
import com.example.journalApp.repository.UserRepository;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
public class JournalEntryService {
@Autowired
private JournalEntryRepository journalEntryRepository;
@Autowired
private UserService userService;
@Transactional
public void saveEntry(JournalEntry journalEntry, String userName) {
try {
User user = userService.findByUserName(userName);
journalEntry.setDate(LocalDateTime.now());
// Step 1: Save Journal Entry
JournalEntry saved = journalEntryRepository.save(journalEntry);
// Step 2: Associate with User and Save User
user.getJournalEntries().add(saved);
userService.saveUser(user);
} catch (Exception e) {
// CRITICAL: Re-throw to allow Spring's Transaction Interceptor to catch it
throw new RuntimeException("An error occurred while saving the entry: ", e);
}
}
}
5. Decoupling Configuration: TransactionConfig
While @EnableTransactionManagement and the transaction manager @Bean can be placed directly on the main application class, best practices dictate isolating database and transaction configuration into a dedicated @Configuration class.
package com.example.journalApp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
}
How Spring Injects MongoDatabaseFactory
When Spring Boot reads the MongoDB connection properties from application.properties or application.yml (e.g., spring.data.mongodb.uri), it automatically registers a default implementation of MongoDatabaseFactory (SimpleMongoClientDatabaseFactory) in the application context.
When creating the transactionManager bean, Spring injects this factory instance automatically via method argument dependency injection.
6. Critical Pitfall: Swallowing Exceptions
A frequent anti-pattern in transactional services is catching an exception inside a try-catch block without re-throwing it or explicitly signaling failure to the transaction interceptor.
Rules for Transaction Rollback
- Unchecked Exceptions (
RuntimeExceptionandError): By default, Spring rolls back transactions automatically only when an unchecked exception propagates out of the@Transactionalboundary. - Checked Exceptions (
Exception): Checked exceptions do not trigger a rollback by default unless explicitly configured:JAVA@Transactional(rollbackFor = Exception.class) - Never Swallow Silently: If a
try-catchblock handles errors, ensure that aRuntimeExceptionis re-thrown so the proxy interceptor can initiatemanager.rollback().
7. Real-World Debugging: The MongoDB Replica Set Requirement
When running @Transactional with MongoDB on a standard standalone local instance (mongodb://localhost:27017), the application may fail with the following error during transaction execution:
Command failed with error 20 (IllegalOperation):
'Transaction numbers are only allowed on a replica set member or mongos'
Cause and Resolution
- Underlying Cause: MongoDB uses the Oplog (Operations Log) to coordinate multi-document atomic commits and rollbacks across sessions. Oplog is only active when MongoDB runs as a Replica Set. Standalone instances do not maintain an Oplog.
- Production Solution: Use a managed cloud cluster such as MongoDB Atlas (hosted on AWS, GCP, or Azure), which runs multi-node replica sets by default, or initialize a local single-node replica set (
mongod --replSet rs0).
8. Complete Request-Response Workflow
9. Summary and Key Takeaways
- Multi-Document Consistency: Modifying multiple documents or collections in a single business flow without transaction control leads to partial writes and orphaned records.
@Transactional: Designates a method or class boundary as a single atomic unit of work adhering to ACID principles.@EnableTransactionManagement: Instructs the Spring container to detect@Transactionalannotations and wrap beans with transaction interceptor proxies.PlatformTransactionManagerInterface: The core abstraction for transaction lifecycle operations (commit,rollback); implemented byMongoTransactionManagerfor MongoDB.MongoDatabaseFactory: Provides the underlying client database connections and multi-document session contexts required byMongoTransactionManager.- Exception Re-throwing: Swallowing exceptions in a
try-catchblock prevents the transaction interceptor from intercepting the failure, resulting in unintended commits. Always re-throw runtime exceptions across the transactional boundary. - Replica Set Mandate: MongoDB requires a Replica Set (such as MongoDB Atlas) to support multi-document transactions; standalone local instances fail with
error code 20.