Spring Data MongoDB Transactions

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>3.0.3.RELEASE</version>
</dependency>

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.maixuanviet.repository")
public class MongoConfig extends AbstractMongoClientConfiguration{

    @Bean
    MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
        return new MongoTransactionManager(dbFactory);
    }

    @Override
    protected String getDatabaseName() {
        return "test";
    }

    @Override
    public MongoClient mongoClient() {
        final ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/test");
        final MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
            .applyConnectionString(connectionString)
            .build();
        return MongoClients.create(mongoClientSettings);
    }
}

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.

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>4.1.0</version>
</dependency>

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>4.0.5</version>
</dependency>
        
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <version>3.2.0.RELEASE</version>
    <scope>test</scope>
</dependency>

The mongodb-driver-reactivestreamsmongodb-driver-sync and reactor-test dependencies are available on Maven Central.

And of course, we need to configure our Reactive MongoDB:

@Configuration
@EnableReactiveMongoRepositories(basePackages 
  = "com.maixuanviet.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)));
}

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.

Related posts:

Java Program to Implement LinkedBlockingQueue API
Sending Emails with Java
Java Program to Implement Dijkstra’s Algorithm using Queue
Spring 5 Functional Bean Registration
Java Byte Array to InputStream
Java Program to Perform Polygon Containment Test
Guide to PriorityBlockingQueue in Java
Java Program to Implement Cubic convergence 1/pi Algorithm
Java Program to Implement Dijkstra’s Algorithm using Priority Queue
Java Program to Find Shortest Path Between All Vertices Using Floyd-Warshall’s Algorithm
Java Program to Implement Hash Tables
The Spring @Controller and @RestController Annotations
Tính trừu tượng (Abstraction) trong Java
Spring Security Remember Me
REST Web service: HTTP Status Code và xử lý ngoại lệ RESTful web service với Jersey 2.x
Java Program to Implement an Algorithm to Find the Global min Cut in a Graph
How to Break from Java Stream forEach
So sánh ArrayList và LinkedList trong Java
Java Program to Implement Skew Heap
Introduction to Spring Data REST
@Before vs @BeforeClass vs @BeforeEach vs @BeforeAll
Java Program to Perform Left Rotation on a Binary Search Tree
Spring RequestMapping
Hướng dẫn sử dụng Java Reflection
Giới thiệu Swagger – Công cụ document cho RESTfull APIs
Tránh lỗi ConcurrentModificationException trong Java như thế nào?
Control the Session with Spring Security
Guide to Escaping Characters in Java RegExps
Spring Cloud AWS – EC2
Spring Boot Integration Testing with Embedded MongoDB
Java TreeMap vs HashMap
Java Program to Implement SimpeBindings API