1. Overview
Starting from the 4.0 release, MongoDB supports multi-document ACID transactions. And, Spring Data Lovelace now provides support for these native MongoDB transactions.
In this tutorial, we’ll discuss Spring Data MongoDB support for synchronous and reactive transactions.
We’ll also take a look at Spring Data TransactionTemplate for non-native transactions support.
For an introduction to this Spring Data module, have a look at our introductory write-up.
2. Setup MongoDB 4.0
First, we’ll need to setup latest MongoDB to try the new native transactions support.
To get started, we have to download the latest version from the MongoDB Download Center.
Next, we’ll start mongod service using the command line:
mongod --replSet rs0
Finally, initiate replica set – if not already:
mongo --eval "rs.initiate()"
Note that MongoDB currently supports transactions over a replica set.
3. Maven Configuration
Next, we need to add the following dependencies to our pom.xml:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> <version>2.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-releasetrain</artifactId> <version>Lovelace-M3</version> <type>pom</type> <scope>import</scope> </dependency>
Note that we need to add the milestone repository to our pom.xml as well:
<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
The latest release of the library can be found on the Central Repository
4. MongoDB Configuration
Now, let’s take a look at our configuration:
@Configuration @EnableMongoRepositories(basePackages = "com.baeldung.repository") public class MongoTransactionConfig extends AbstractMongoConfiguration{ @Bean MongoTransactionManager transactionManager(MongoDbFactory dbFactory) { return new MongoTransactionManager(dbFactory); } @Override protected String getDatabaseName() { return "test"; } @Override public MongoClient mongoClient() { return new MongoClient("127.0.0.1", 27017); } }
Note that we need to register MongoTransactionManager in our configuration to enable native MongoDB transactions as they are disabled by default.
5. Synchronous Transactions
After we finished the configuration, all we need to do to use native MongoDB transactions – is to annotate our method with @Transactional.
Everything inside the annotated method will be executed in one transaction:
@Test @Transactional public void whenPerformMongoTransaction_thenSuccess() { userRepository.save(new User("John", 30)); userRepository.save(new User("Ringo", 35)); Query query = new Query().addCriteria(Criteria.where("name").is("John")); List<User> users = mongoTemplate.find(query, User.class); assertThat(users.size(), is(1)); }
Note that we can’t use listCollections command inside a multi-document transaction – for example:
@Test(expected = MongoTransactionException.class) @Transactional public void whenListCollectionDuringMongoTransaction_thenException() { if (mongoTemplate.collectionExists(User.class)) { mongoTemplate.save(new User("John", 30)); mongoTemplate.save(new User("Ringo", 35)); } }
This example throws a MongoTransactionException as we used the collectionExists() method.
We also can’t run count inside a multi-document transaction:
@Test(expected = MongoCommandException.class) @Transactional public void whenCountDuringMongoTransaction_thenException() { userRepository.save(new User("John", 30)); userRepository.save(new User("Ringo", 35)); userRepository.count(); }
But, we can work around this one with a simple query and then get the size of the resulting list:
@Test @Transactional public void whenQueryDuringMongoTransaction_thenSuccess() { userRepository.save(new User("Jane", 20)); userRepository.save(new User("Nick", 33)); List<User> users = mongoTemplate.find(new Query(), User.class); assertTrue(users.size() > 1); }
6. TransactionTemplate
We saw how Spring Data support new MongoDB native transaction. Additionally, Spring Data also provides the non-native option.
We can perform non-native transactions using Spring Data TransactionTemplate:
@Test public void givenTransactionTemplate_whenPerformTransaction_thenSuccess() { mongoTemplate.setSessionSynchronization(SessionSynchronization.ALWAYS); TransactionTemplate transactionTemplate = new TransactionTemplate(mongoTransactionManager); transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { mongoTemplate.insert(new User("Kim", 20)); mongoTemplate.insert(new User("Jack", 45)); }; }); Query query = new Query().addCriteria(Criteria.where("name").is("Jack")); List<User> users = mongoTemplate.find(query, User.class); assertThat(users.size(), is(1)); }
We need to set SessionSynchronization to ALWAYS to use non-native Spring Data transactions.
7. Reactive Transactions
Finally, we’ll take a look at Spring Data support for MongoDB reactive transactions.
We’ll need to add a few more dependencies to the pom.xml to work with reactive MongoDB:
<dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-reactivestreams</artifactId> <version>1.9.2</version> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <version>3.2.0.RELEASE</version> <scope>test</scope> </dependency>
The mongodb-driver-reactivestreams and reactor-test dependencies are available on Maven Central.
And of course, we need to configure our Reactive MongoDB:
@Configuration @EnableReactiveMongoRepositories(basePackages = "com.baeldung.reactive.repository") public class MongoReactiveConfig extends AbstractReactiveMongoConfiguration { @Override public MongoClient reactiveMongoClient() { return MongoClients.create(); } @Override protected String getDatabaseName() { return "reactive"; } }
To use transactions in reactive MongoDB, we need to use the inTransaction() method in ReactiveMongoOperations:
@Autowired private ReactiveMongoOperations reactiveOps; @Test public void whenPerformTransaction_thenSuccess() { User user1 = new User("Jane", 23); User user2 = new User("John", 34); reactiveOps.inTransaction() .execute(action -> action.insert(user1) .then(action.insert(user2))); }
More information on reactive repositories in Spring Data is available here.
8. Conclusion
In this write-up, we learned how to use native and non-native MongoDB transactions using Spring Data.
The full source code for the examples is available over on GitHub.