

Table of Contents
Introduction
MongoDB 4.0 adds support for multi-document ACID transactions.
But wait… Does that mean MongoDB did not support transactions until now? No, actually MongoDB has always supported transactions in the form of single document transactions. MongoDB 4.0 extends these transactional guarantees across multiple documents, multiple statements, multiple collections, and multiple databases. What good would a database be without any form of transactional data integrity guarantee?
Before we dive into this blog post, you can find all the code and try multi-document ACID transactions here.
Quick start
Step 1: Start MongoDB
Start a single node MongoDB ReplicaSet in version 4.0.0 minimum on localhost, port 27017.
If you use Docker:
- You can use
start-mongo.sh
. - When you are done, you can use
stop-mongo.sh
. - If you want to connect to MongoDB with the Mongo Shell, you can use
connect-mongo.sh
.
If you prefer to start mongod manually:
mkdir /tmp/data && mongod --dbpath /tmp/data --replSet rs
mongo --eval 'rs.initiate()'
Step 2: Start Java
This demo contains two main programs: ChangeStreams.java
and Transactions.java
.
- Change Steams allow you to be notified of any data changes within a MongoDB collection or database.
- The Transaction process is the demo itself.
You need two shells to run them.
If you use Docker:
First shell:
Second shell:
If you do not use Docker, you will need to install Maven 3.5.X and a JDK 10 (or JDK 8 minimum but you will need to update the Java versions in the pom.xml):
First shell:
Second shell:
Let’s compare our existing single document transactions with MongoDB 4.0’s ACID compliant multi-document transactions and see how we can leverage this new feature with Java.
Prior to MongoDB 4.0
Even in MongoDB 3.6 and earlier, every write operation is represented as a transaction-scoped to the level of an individual document in the storage layer. Because the document model brings together related data that would otherwise be modeled across separate parent-child tables in a tabular schema, MongoDB’s atomic single-document operations provide transaction semantics that meet the data integrity needs of the majority of applications.
Every typical write operation modifying multiple documents actually happens in several independent transactions: one for each document.
Let’s take an example with a very simple stock management application.
First of all, I need a MongoDB Replica Set so please follow the instructions given above to start MongoDB.
Now let’s insert the following documents into a product
collection:
Let’s imagine there is a sale on and we want to offer our customers a 20% discount on all our products.
But before applying this discount, we want to monitor when these operations are happening in MongoDB with Change Streams.
Execute the following in Mongo Shell:
Keep this shell on the side, open another Mongo Shell and apply the discount:
As you can see, both documents were updated with a single command line but not in a single transaction. Here is what we can see in the Change Stream shell:
As you can see the cluster times (see the clusterTime key) of the two operations are different: the operations occurred during the same second but the counter of the timestamp has been incremented by one.
Thus here each document is updated one at a time and even if this happens really fast, someone else could read the documents while the update is running and see only one of the two products with the discount.
Most of the time, it is something you can tolerate in your MongoDB database because, as much as possible, we try to embed tightly linked, or related data in the same document. As a result, two updates on the same document happen within a single transaction :
However, sometimes, you cannot model all of your related data in a single document, and there are a lot of valid reasons for choosing not to embed documents.
MongoDB 4.0 with multi-document ACID transactions
Multi-document ACID transactions in MongoDB are very similar to what you probably already know from traditional relational databases.
MongoDB’s transactions are a conversational set of related operations that must atomically commit or fully rollback with all-or-nothing execution.
Transactions are used to make sure operations are atomic even across multiple collections or databases. Thus, with snapshot isolation reads, another user can only see all the operations or none of them.
Let’s now add a shopping cart to our example.
For this example, 2 collections are required because we are dealing with 2 different business entities: the stock management and the shopping cart each client can create during shopping. The lifecycle of each document in these collections is different.
A document in the product collection represents an item I’m selling. This contains the current price of the product and the current stock. I created a POJO to represent it : Product.java.
A shopping cart is created when a client adds its first item in the cart and is removed when the client proceeds to checkout or leaves the website. I created a POJO to represent it : Cart.java.
The challenge here resides in the fact that I cannot sell more than I possess: if I have 5 beers to sell, I cannot have more than 5 beers distributed across the different client carts.
To ensure that, I have to make sure that the operation creating or updating the client cart is atomic with the stock update. That’s where the multi-document transaction comes into play. The transaction must fail in the case someone tries to buy something I do not have in my stock. I will add a constraint on the product stock:
Node that this is already included in the Java code.
To monitor our example, we are going to use MongoDB Change Streams that were introduced in MongoDB 3.6.
In each of the threads of this process called ChangeStreams.java, I am going to monitor one of the 2 collections and print each operation with its associated cluster time.
In this example we have 5 beers to sell. Alice wants to buy 2 beers but we are not going to use the new MongoDB 4.0 multi-document transactions for this. We will observe in the change streams two operations : one creating the cart and one updating the stock at 2 different cluster times.
Then Alice adds 2 more beers in her cart and we are going to use a transaction this time. The result in the change stream will be 2 operations happening at the same cluster time.
Finally, she will try to order 2 extra beers but the jsonSchema validator will fail the product update and result in a rollback. We will not see anything in the change stream. Here is the Transaction.java source code:
Here is the console of the Change Stream :
As you can see here, we only get four operations because the two last operations were never committed to the database, and therefore the change stream has nothing to show.
You can also note that the two first cluster times are different because we did not use a transaction for the two first operations, and the two last operations share the same cluster time because we used the new MongoDB 4.0 multi-document transaction system, and thus they are atomic.
Here is the console of the Transaction java process that sum up everything I said earlier.