In the previous recipe, we saw how to execute find
and insert
operations in MongoDB using the Java client; in this recipe, we will see how updates and deletes work in the Java client.
For this recipe, we will start a standalone instance. Refer to the Installing single node MongoDB recipe from Chapter 1, Installing and Starting the Server for instructions on how to start the server.
The next step is to download the Java project, mongo-cookbook-javadriver
, from the Packt website. This recipe uses a JUnit test case to test out various features of the Java client. In this whole process, we will use some of the most common API calls and thus learn to use them.
To execute the test case, one can either import the project in an IDE-like Eclipse and execute the test case or execute the test case from the command prompt using Maven.
The test case that we are going to execute for this recipe is com.packtpub.mongo.cookbook.MongoDriverUpdateAndDeleteTest
.
$ mvn -Dtest=com.packtpub.mongo.cookbook.MongoDriverUpdateAndDeleteTest test
Everything should get executed 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 the incoming connections.
We created a test data for the recipes using a setupUpdateTestData()
method. Here, we simply put documents in the javaTest
collection in the javaDriverTest
database. We add 20 documents to 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 first create the test data and then execute the following update:
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 is the query that would 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 update, {'$set' : {'gtTen' : true}}
. The result of the update is an instance of com.mongodb.WriteResult
. The instance of WriteResult
tells us the number of documents that got updated and gets the error that occurred while executing the write
operation and write concern used for the update. Refer to the Javadocs of the WriteConcern
class for more details. This update only updates the first matching document by default 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 that we used is updateMulti
on the collection instance. The updateMulti
method is a convenient way to update multiple documents. The following is the call that we 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 did was 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 is the query for which the matching documents will be removed from the collection. Note that all matching documents will be removed by default unlike 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. When you use Write Concern on such machines, it may cause 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 would require 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 Javadocs of com.mongodb.DBCollection
for more methods. The findAndModify
method that we used ultimately invokes the method we discussed with the fields and sort parameters as null with the remaining parameters, remove
, returnNew
, and upsert
being false
.
Finally, we look at the query builder support in MongoDB's 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 would mean using BasicDBObject
instances, which is even painful and 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
, which is a utility class to build the complex queries. The preceding query is built using a 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 easy to read as well. There are a lot of methods in the com.mongodb.QueryBuilder
class and I would encourage you to go through the Javadocs of this class. The basic idea is to start construction using the start
method and the key. We then chain the method 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 the MongoDB Java API.
There are some more operations using the GridFS and geospatial indexes. We will see how to use them in the Java application with a small sample in the advanced query chapter. Refer to Chapter 5, Advanced Operations for such recipes.
The Javadocs for the current version of the MongoDB driver can be found at https://api.mongodb.org/java/current/.