Compound Operations
On this page
Overview
In this guide, you can learn how to perform compound operations with the MongoDB Java driver.
Compound operations consist of a read and write operation performed as one atomic operation. An atomic operation is an operation which either completes entirely, or does not complete at all. Atomic operations cannot partially complete.
Atomic operations can help you avoid race conditions in your code. A race condition occurs when your code's behavior is dependent on the order of uncontrollable events.
MongoDB supports the following compound operations:
Find and update one document
Find and replace one document
Find and delete one document
If you must perform more complex tasks atomically, such as reading and writing to more than one document, use transactions. Transactions are a feature of MongoDB and other databases that lets you define an arbitrary sequence of database commands as an atomic operation.
For more information about atomic operations and atomicity, see the MongoDB manual entry for atomicity and transactions.
For more information about transactions, see the MongoDB manual entry for transactions.
How to Use Compound Operations
This section shows how to use each compound operation with the MongoDB Java Driver.
The following examples use a collection containing these two sample documents.
{"_id": 1, "food": "donut", "color": "green"} {"_id": 2, "food": "pear", "color": "yellow"}
The full code for the following examples is available on Github here.
Note
Before or After the Write?
By default, each compound operation returns your found document in the state before your write operation. You can retrieve your found document in the state after your write operation by using the options class corresponding to your compound operation. You can see an example of this configuration in the Find and Replace example below.
Find and Update
To find and update one document, use the findOneAndUpdate()
method of the
MongoCollection
class. The findOneAndUpdate()
method returns your found
document or null
if no documents match your query.
Example
The following example uses the findOneAndUpdate()
method to find a
document with the color
field set to "green"
and update the
food
field in that document to "pizza"
.
The example also uses a FindOneAndUpdateOptions
instance to specify the
following options:
Exclude the
_id
field from the found document with a projection.Specify an upsert, which inserts the document specified by the query filter if no documents match the query.
Set a maximum execution time of 5 seconds for this operation on the MongoDB instance. If the operation takes longer, the
findOneAndUpdate()
method will throw aMongoExecutionTimeoutException
.
// <MongoCollection set up code here> // Creates a projection to exclude the "_id" field from the retrieved documents Bson projection = Projections.excludeId(); // Creates a filter to match documents with a "color" value of "green" Bson filter = Filters.eq("color", "green"); // Creates an update document to set the value of "food" to "pizza" Bson update = Updates.set("food", "pizza"); // Defines options that specify projected fields, permit an upsert and limit execution time FindOneAndUpdateOptions options = new FindOneAndUpdateOptions(). projection(projection). upsert(true). maxTime(5, TimeUnit.SECONDS); // Updates the first matching document with the content of the update document, applying the specified options Document result = collection.findOneAndUpdate(filter, update, options); // Prints the matched document in its state before the operation System.out.println(result.toJson());
The output of the preceding code resembles the following:
{"food": "pizza", "color": "green"}
For more information about the Projections
class, see our
guide on the Projections builder.
For more information about the upsert operation, see our guide on upserts.
For more information about the methods and classes mentioned in this section, see the following API Documentation:
Find and Replace
To find and replace one document, use the findOneAndReplace()
method of the
MongoCollection
class. The findOneAndReplace()
method returns your found
document or null
if no documents match your query.
Example
The following example uses the findOneAndReplace()
method to find a
document with the color
field set to "green"
and replace it
with the following document:
{"music": "classical", "color": "green"}
The example also uses a FindOneAndReplaceOptions
instance to specify that
the returned document should be in the state after our replace operation.
// <MongoCollection set up code here> // Creates instructions to replace the matching document with a new document Bson filter = Filters.eq("color", "green"); Document replace = new Document("music", "classical").append("color", "green"); // Defines options specifying that the operation should return a document in its post-operation state FindOneAndReplaceOptions options = new FindOneAndReplaceOptions(). returnDocument(ReturnDocument.AFTER); // Atomically finds and replaces the matching document and prints the replacement document Document result = collection.findOneAndReplace(filter, replace, options); System.out.println(result.toJson());
The output of the preceding code resembles the following:
{"_id": 1, "music": "classical", "color": "green"}
For more information about the methods and classes mentioned in this section, see the following API Documentation:
Find and Delete
To find and delete one document, use the findOneAndDelete()
method of the
MongoCollection
class. The findOneAndDelete()
method returns your found
document or null
if no documents match your query.
Example
The following example uses the findOneAndDelete()
method to find and
delete the document with the largest value in the _id
field.
The example uses a FindOneAndDeleteOptions
instance to specify a
descending sort on the _id
field.
// <MongoCollection set up code here> Bson sort = Sorts.descending("_id"); // Creates an empty filter to match all documents in the collection Bson filter = Filters.empty(); // Defines options that specify a descending sort on the "_id" field FindOneAndDeleteOptions options = new FindOneAndDeleteOptions(). sort(sort); // Deletes the document containing the highest "_id" value and prints the deleted document Document result = collection.findOneAndDelete(filter, options); System.out.println(result.toJson());
The output of the preceding code resembles the following:
{"_id": 2, "food": "pear", "color": "yellow"}
For more information about the Sorts
class, see our
guide on the Sorts builder.
For more information about the methods and classes mentioned in this section, see the following API Documentation:
Avoiding a Race Condition
In this section we explore two examples. The first example contains a race condition, the second example uses a compound operation to avoid the race condition present in the first example.
For both examples, let's imagine that we run a hotel with one room and that we have a small Java program to help us checkout this room to a guest.
The following document in MongoDB represents the room:
{"_id": 1, "guest": null, "room": "Blue Room", "reserved": false}
The full code for this example is available on Github here.
Example With Race Condition
Let's say our app uses this bookARoom
method to checkout our room to
a guest:
public void bookARoom() { // Creates a filter to match documents representing available rooms Bson filter = Filters.eq("reserved", false); // Retrieves a document that represents the first available room Document myRoom = this.collection.find(filter).first(); // Prints a message if no documents representing available rooms are found if (myRoom == null){ System.out.println("Sorry, we are booked " + this.guest); return; } String myRoomName = myRoom.getString("room"); // Prints a message that guest that successfully booked the room System.out.println("You got the " + myRoomName + " " + this.guest); // Creates an update document to mark a room as reserved Bson update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guest)); // Creates a filter that matches the "_id" field of the first available room Bson roomFilter = Filters.eq("_id", myRoom.get("_id", Integer.class)); // Updates the first matching document to mark it as reserved this.collection.updateOne(roomFilter, update); }
Imagine two separate guests, Jan and Pat, try to book the room with this method at the same time.
Jan sees this output:
You got the Blue Room Jan
And Pat sees this output:
You got the Blue Room Pat
When we look at our database, we see the following:
{"_id": 1, "guest": "Jan", "room": "Blue Room", "reserved": true}
Pat will be unhappy. When Pat shows up to our hotel, Jan will be occupying her room. What went wrong?
Here is the sequence of events that happened from the perspective of our MongoDB instance:
Find and return an empty room for Jan
Find and return an empty room for Pat
Update the room to booked for Pat
Update the room to booked for Jan
Notice that for a brief moment Pat had reserved the room, but as Jan's update
operation was the last to execute our document has "Jan"
as the guest.
Example Without Race Condition
Let's use a compound operation to avoid the race condition and always give our users the correct message.
public void bookARoom(){ // Creates an update document to mark a room as reserved Bson update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guest)); // Creates a filter to match a document representing an available room Bson filter = Filters.eq("reserved", false); // Updates the first document that matches the filter to mark it as reserved Document myRoom = this.collection.findOneAndUpdate(filter, update); // Prints a message when there are no available rooms if (myRoom == null){ System.out.println("Sorry, we are booked " + this.guest); return; } // Prints the name of the guest that successfully booked the room String myRoomName = myRoom.getString("room"); System.out.println("You got the " + myRoomName + " " + this.guest); }
Imagine two separate guests, Jan and Pat, try to book the room with this method at the same time.
Jan sees this output:
You got the Blue Room Jan
And Pat sees this output:
Sorry, we are booked Pat
When we look at our database, we see the following:
{"_id":1, "guest":"Jan", "room":"Blue Room", "reserved":true}
Pat got the correct message. While she might be sad she didn't get the reservation, at least she knows not to travel to our hotel.
Here is the sequence of events that happened from the perspective of our MongoDB instance:
Find an empty room for Jan and reserve it.
Try to find an empty room for Pat and reserve it. As there are not any rooms left, return
null
.
Important
Write Lock
Your MongoDB instance places a write lock on the document you are modifying for the duration of your compound operation.
For information on the Updates
class, see our
guide on the Updates builder.
For more information of the Filters
class, see our
guide on the Filters builder.
For more information about the findOneAndUpdate()
method, see
the API Documentation for the MongoCollection class.