In the previous recipe, we saw how to execute the find
and insert
operations in MongoDB using a Java client. In this recipe, we will see how the update
and delete
operations work from a Java client.
For this recipe, we will start a standalone instance. Refer to the Single node installation of MongoDB recipe in Chapter 1, Installing and Starting the MongoDB Server, to learn how to start the server.
The next step is to download the Java project mongo-cookbook-javadriver
from the book's website. This recipe uses a JUnit test case to test out various features of the Java client. In this whole process, we will make use of some of the most common API calls and, thus, learn how to use them.
To execute the test case, one can either import the project in an IDE such as Eclipse and execute the test case or execute the test case from the command prompt using Maven.
The test case we are going to execute for this recipe is com.packtpub.mongo.cookbook.MongoDriverUpdateAndDeleteTest
.
If you are using an IDE, open this test class and execute it as a JUnit test case. If you plan to use Maven to execute this test case, go to the command prompt, change the directory to the root of the project, and execute the following command to execute this single test case:
$ mvn -Dtest=com.packtpub.mongo.cookbook.MongoDriverUpdateAndDeleteTest test
Everything should execute fine if the Java SDK and Maven are properly set up and the MongoDB server is up and running and listening to port 27017
for incoming connections.
We will create test data for the recipes using the setupUpdateTestData()
method. Here, we will simply put documents in the javaTest
collection in the javaDriverTest
database. We will add 20 documents in this collection with the value of i
ranging from 1 to 20. This test data is used in different test case methods to create test data.
Let's now take a look at the methods in this class. We will first look at basicUpdateTest()
. In this method, we will first create test data and then execute the following update
method:
collection.update( new BasicDBObject("i", new BasicDBObject("$gt", 10)), new BasicDBObject("$set", new BasicDBObject("gtTen", true)));
The update
method here takes two arguments: the first one is the query that will be used to select the eligible documents for the update, and the second parameter is the actual update. The first parameter looks confusing due to nested BasicDBObject
instances; however, it is the {'i' : {'$gt' : 10}}
condition, and the second parameter is the {'$set' : {'gtTen' : true}}
update. The result of the update is an instance of com.mongodb.WriteResult
. The instance of WriteResult
tells us about the number of documents that got updated, the error that occurred while executing the write operation, and the write concern used for the update. Refer to the Java docs of the WriteConcern
class for more details. This update, by default, only updates the first matching document, and only if multiple documents match the query.
The next method that we will look at is multiUpdateTest
, which will update all the matching documents for the given query instead of the first matching document. The method we used on the collection instance is updateMulti
. The updateMulti
method is just a convenient method to update multiple documents. The following is the call that we will make to update multiple documents:
collection.updateMulti(new BasicDBObject("i", new BasicDBObject("$gt", 10)), new BasicDBObject("$set", new BasicDBObject("gtTen", true)));
The next operation that we will perform is to remove documents. The test case method to remove documents is deleteTest()
. The documents are removed as follows:
collection.remove(new BasicDBObject( "i", new BasicDBObject("$gt", 10)), WriteConcern.JOURNALED);
We have two parameters here. The first one is the query for which matching documents will be removed from the collection. Note that all the matching documents will be removed by default, unlike in update, where only the first matching document will be removed by default. The second parameter is the write concern to be used for the remove operation.
Note that, when the server is started on a 32-bit machine, journaling is disabled by default. Using the journaling write concern on such machines causes the operation to fail with the following exception:
com.mongodb.CommandFailureException: { "serverUsed" : "localhost/127.0.0.1:27017" , "connectionId" : 5 , "n" : 0 , "badGLE" : { "getlasterror" : 1 , "j" : true} , "ok" : 0.0 , "errmsg" : "cannot use 'j' option when a host does not have journaling enabled" , "code" : 2}
This will need the server to be started with the --journal
option. On 64-bit machines, this is not necessary, as journaling is enabled by default.
We will look at the findAndModify
operation next. The test case method to perform this operation is findAndModifyTest
. The following lines of code are used to perform this operation:
DBObject old = collection.findAndModify( new BasicDBObject("i", 10), new BasicDBObject("i", 100));
The operation is the query that will find the matching documents and then update them. The return type of the operation is an instance of DBObject
before the update is applied. One important feature of the findAndModify
operation is that the find
and update
operations are performed atomically.
The preceding method is a simple version of the findAndModify
operation. There is an overloaded version of this method with the following signature:
DBObject findAndModify(DBObject query, DBObject fields, DBObject sort ,boolean remove, DBObject update, boolean returnNew, boolean upsert)
Let's see what these parameters are in the following table:
There are more overloaded methods of this operation. Refer to the Java docs of com.mongodb.DBCollection
for more methods. The findAndModify
method we used ultimately invokes the method we discussed earlier with the fields and sort parameters as null and the remaining remove
, returnNew
, and upsert
parameters being false.
Finally, we will look at query builder support in MongoDB Java API.
All the queries in Mongo are DBObject
instances with possibly more nested DBObject
instances in them. Things are simple for small queries, but they start getting ugly for more complicated queries. Consider a relatively simple query where we want to query for documents with i > 10
and i < 15
. The Mongo query for this is {$and:[{i:{$gt:10}}, {i:{$lt:15}}]}
. Writing this in Java using BasicDBObject
instances is painful, and it looks as follows:
DBObject query = new BasicDBObject("$and", new BasicDBObject[] { new BasicDBObject("i", new BasicDBObject("$gt", 10)), new BasicDBObject("i", new BasicDBObject("$lt", 15)) } );
Thankfully, however, there is a class called com.mongodb.QueryBuilder
that is a utility class to build complex queries. The preceding query is built using query builder as follows:
DBObject query = QueryBuilder.start("i").greaterThan(10).and("i").lessThan(15).get();
This is less error-prone when writing a query and is easy to read as well. There are a lot of methods in the com.mongodb.QueryBuilder
class, and I would like you to go through the Java docs of this class. The basic idea is to start construction using the start method and the key. We will then chain the method's calls to add different conditions and, when the addition of various conditions is done, the query is constructed using the get()
method, which returns DBObject
. Refer to the queryBuilderSample
method in the test class for a sample usage of query builder support of MongoDB Java API.