From the perspective of developers, when a program needs to interact with a MongoDB instance, they need to use the respective client APIs for their specific platforms. The trouble with doing this is that we need to write a lot of boilerplate code, and it is not necessarily object-oriented. For instance, we have a class called Person
with various attributes such as name
, age
, and address
. The corresponding JSON document too shares a similar structure to this Person
class as follows:
{ name:"…", age:.., address:{lineOne:"…", …} }
However, to store this document, we need to convert the Person
class to a DBObject
, which is a map with key and value pairs. What is really desired is to let us persist this Person
class itself as an object in the database, without having to convert it to DBObject
.
Also, some of the operations such as searching by a particular field of a document, saving an entity, deleting an entity, and searching by ID are pretty common, and we tend to repeatedly write similar boilerplate code. In this recipe, we will see how spring-data-mongodb
relieves us of these laborious and cumbersome tasks not only to reduce the development effort but also to dramatically reduce the possibility of introducing bugs in these commonly written functions.
The SpringDataMongoTest
project present in the bundle in the chapter is a Maven project and is to be imported into any IDE of your choice. The required Maven artifacts will automatically be downloaded. A single MongoDB instance that listens to port 27017
is required to be up-and-running. Refer to the Single node installation of MongoDB recipe in Chapter 1, Installing and Starting the MongoDB Server, to know how to start a standalone instance.
For the aggregation example, we will use the postal code data. Refer to the Creating test data recipe in Chapter 2, Command-line Operations and Indexes, for the creation of test data.
com.packtpub.mongo.cookbook.MongoCrudRepositoryTest
from your IDE and execute it. If all goes well and the MongoDB server instance is reachable, the test case will execute successfully.com.packtpub.mongo.cookbook.MongoCrudRepositoryTest2
is used to explore more features of the repository support provided by spring-data-mongodb
. This test case too should get executed successfully.MongoTemplate
of spring-data-mongodb can be used to perform CRUD operations and other common operations on MongoDB. Open the com.packtpub.mongo.cookbook.MongoTemplateTest
class and execute it.SpringDataMongoTest
project:$ mvn clean test
We will first look at what we did in com.packtpub.mongo.cookbook.MongoCrudRepositoryTest
, where we saw the repository support provided by spring-data-mongodb
. Just in case you didn't notice, we haven't written a single line of code for the repository. The magic of implementing the required code for us is done by the Spring data project. Let's start by looking at the relevant portions of the XML config
file:
<mongo:repositories base-package="com.packtpub.mongo.cookbook" /> <mongo:mongo id="mongo" host="localhost" port="27017"/> <mongo:db-factory id="factory" dbname="test" mongo-ref="mongo"/> <mongo:template id="mongoTemplate" db-factory-ref="factory"/>
We will first look at the last three lines: spring-data-mongodb
namespace declarations to instantiate com.mongodb.Mongo
, instantiating a factory for the com.mongodb.DB
instances from the client, and a template
instance, used to perform various operations on MongoDB, respectively. We will see org.springframework.data.mongodb.core.MongoTemplate
in more detail later.
The first line is a namespace declaration for the base package of all the CRUD repositories we have. In this package, we have an interface with the following body:
public interface PersonRepository extends PagingAndSortingRepository<Person, Integer>{ /** * * @param lastName * @return */ Person findByLastName(String lastName); }
The PagingAndSortingRepository
interface is from the org.springframework.data.repository
package of the Spring data core project and extends from CrudRepository
of the same project. These interfaces give us some most common methods such as searching by the ID/primary key, deleting an entity, inserting an entity, and updating an entity. The repository needs an object that it maps to the underlying data store. The Spring data project supports a large number of data stores that are not just limited to SQL (using Java Database Connectivity (JDBC) and JPA) or MongoDB but also to other NoSQL stores such as Redis and Hadoop and search engines such as Solr and Elasticsearch. In the case of spring-data-mongodb
, the object is mapped to a document in the collection.
The PagingAndSortingRepository<Person, Integer>
signature indicates that the first one is the entity that the CRUD repository is built for, and the second one is the type of the primary key/ID field.
We have added just one method named findByLastName
; this accepts one string value for the last name as a parameter. This is an interesting operation; it is specific to our repository and not even implemented by us, but it will still work just as expected. The Person
class is a POJO where we have annotated the ID field with the org.springframework.data.annotation.Id
annotation. Nothing else is really special about this class; it just has some plain getters and setters.
With all these small details, let's join these dots together by answering some questions you'll have in mind. First, we will see which server, database, and collection our data goes to. If we look at the mongo:mongo
XML definition for the config
file, we will see that we instantiated the com.mongodb.Mongo
class by connecting to a localhost and port 27017
. The mongo:db-factory
declaration is used to denote that the database to be used is test
. One final question is, which collection? The simple name of our class is Person
. The name of the collection is the simple name with the first character lowercased; thus, Person
will become person
, and something like BillingAddress
will become the billingAddress
collection. These are the default values. However, if you need to override this value, you can annotate your class with the org.springframework.data.mongodb.core.mapping.Document
annotation and use its attribute collection to give any name of your choice, as we will see in an example later.
To view the document in the collection, execute just one test case method called saveAndQueryPerson
from the com.packtpub.mongo.cookbook.MongoCrudRepositoryTest
class. Now, connect to the MongoDB instance from the Mongo shell and execute the following query:
> use test > db.person.findOne({_id:1}) { "_id" : 1, "_class" : "com.packtpub.mongo.cookbook.domain.Person", "firstName" : "Steve", "lastName" : "Johnson", "age" : 20, "gender" : "Male" … }
As we can see in the preceding result, the contents of the document are similar to the object we persisted using the CRUD repository. The names of the field in the document are the same as the names of the respective attributes in the Java object, with two exceptions. The field annotated with @Id
is now _id
, irrespective of the name of the field in the Java class, and an additional _class
attribute is added to the document whose value is the fully qualified name of the Java class itself. This is not of any use for the application but is used by spring-data-mongodb
as metadata.
Now, it makes more sense and gives us an idea about what spring-data-mongodb
must be doing for all the basic CRUD methods. All the operations we perform will use the MongoTemplate
class (MongoOperations
to be precise, which is an interface that MongoTemplate
implements) from the spring-data-mongodb
project. To find it by using the primary key, it will invoke a find
query by the _id
field on the collection derived using the Person
entity class. The save
method simply calls the save
method on MongoOperations
; this in turn calls the save
method on the com.mongodb.DBCollection
class.
We still haven't answered how the findByLastName
method worked. How does Spring know what query to invoke to return the data? These are the special types of methods that begin with find
, findBy
, get
, or getBy
. There are some rules that one needs to follow while naming a method, and the proxy object on the repository interface is able to correctly convert this method into an appropriate query on the collection. For instance, the findByLastName
method in the repository of the Person
class will execute a query on the lastName
field in the document of the Person
class. Hence, the findByLastName(String lastName)
method will fire the following query in the database:
db.person.find({'lastName': lastName })
Based on the return type of the method defined, it will return either a list or the first result in the returned result from the database. We have used findBy
in our queries. However, for anything that begins with find
, having any text in between and having text that ends in By
works. For instance, findPersonBy
is the same as findBy
.
For more information on these findBy
methods, we have another test class, MongoCrudRepositoryTest2
. Open this class in your IDE where it can be read along with this text. We have already executed this test case; now, let's see these findBy
methods used and their behavior. This class has seven findBy
methods in it, with one of the methods being a variant of another method in the same interface. To get a clear idea of the queries, we will first look at one of the documents in the personTwo
collection in the test
database. Execute the following commands on the Mongo shell connected to the MongoDB server that runs on a localhost:
> use test > db.personTwo.findOne({firstName:'Amit'}) { "_id" : 2, "_class" : "com.packtpub.mongo.cookbook.domain.Person2", "firstName" : "Amit", "lastName" : "Sharma", "age" : 25, "gender" : "Male", "residentialAddress" : { "addressLineOne" : "20, Central street", "city" : "Mumbai", "state" : "Maharashtra", "country" : "India", "zip" : "400101" } }
Also, note that the repository uses the Person2
class. However, the name of the collection used is personTwo
. This was possible because we used the @Document(collection="personTwo")
annotation on top of the Person2
class.
Getting back to the seven methods in the com.packtpub.mongo.cookbook.PersonRepositoryTwo
repository class, let's look at them one by one:
We saw how the findBy
or getBy
method is automatically translated to queries for MongoDB. Similarly, we have some well-known prefixes for the methods. The countBy
prefix returns the long number for the count for a given condition, which is derived from the rest of the method names that are similar to findBy
. We can have deleteBy
or removeBy
to delete the documents by the derived condition. Also, one thing to note about the com.packtpub.mongo.cookbook.domain.Person2
class is that it does not have a no-argument constructor or setter to set the values. Spring will, instead, use reflection to instantiate this object.
A lot of findBy
methods are supported by spring-data-mongodb
, and all are not covered here. For more details, refer to the spring-data-mongodb
reference manual. A lot of XML-based or Java-based configuration options are available too and can be found in the reference manual. The links are given in the See also section later in this recipe.
We are not done yet, though; we have another test case, com.packtpub.mongo.cookbook.MongoTemplateTest
. This uses org.springframework.data.mongodb.core.MongoTemplate
to perform various operations. We can open the test case class, and we will see what operations are performed and which methods of the MongoTemplate
class are invoked.
Let's look at some of the important and frequently used methods of the MongoTemplate
class:
Method |
Description |
---|---|
|
This method is used to save (insert if new; otherwise, update) an entity in MongoDB. The method takes one parameter, the entity, and finds the target collection based on its name or the There is an overloaded version of the |
|
This method will be used to remove documents from the collection. It has got some overloaded versions in this class. All of them accept either an entity to be deleted or the |
|
This is the function invoked to update multiple documents with one update call. The first parameter is the query that will be used to match the documents. The second parameter is an instance of |
|
This is the opposite of the |
|
We mentioned that the However, as we saw in the test case in the |
|
Both these operations are used to find and then remove the document(s). The |
|
This method is functionally similar to |
In the preceding table, we mentioned the Query
and Update
classes when talking about update. These are special convenience classes in spring-data-mongodb
; they let us build MongoDB queries using a syntax that is easy to understand and improves readability. For instance, the query to check whether lastName
is Johnson
in the Mongo query language is {'lastName':'Johnson'}
. The same query can be constructed in spring-data-mongodb
as follows:
new Query(Criteria.where("lastName").is("Johnson"))
This syntax looks neat as compared to giving the query in JSON. Let's take another example where we want to find all females under 30 years of age in our database. The query will now be built as follows:
new Query(Criteria.where("age").lt(30).and("gender").is("Female"))
Similarly, for update, we want to set a youngCustomer
Boolean flag to be true
for some of the customers, based on some conditions. To set this flag in the document, the MongoDB format will be as follows:
{'$set' : {'youngCustomer' : true}}
In spring-data-mongodb
, the same will be achieved as follows:
new Update().set("youngCustomer", true)
Refer to the Javadoc for all the possible methods that are available to build the query and updates in spring-data-mongodb
that are to be used with MongoTemplate
.
The methods mentioned earlier are by no means the only ones available in the MongoTemplate
class. There are a lot of other methods for geospatial indexes, convenience methods to get the count of documents in the collection, aggregation, and MapReduce support, and so on. Refer to the Javadoc of MongoTemplate
for more details and methods.
Speaking of aggregation, we also have a test case method called aggregationTest
to perform the aggregation
operation on the collection. We have a postalCodes
collection in MongoDB; this collection contains the postal code details of various cities. An example document in the collection is as follows:
{ "_id" : ObjectId("539743b26412fd18f3510f1b"), "postOfficeName" : "A S D Mello Road Fuller Marg", "pincode" : 400001, "districtsName" : "Mumbai", "city" : "Mumbai", "state" : "Maharashtra" }
Our aggregation
operation intends to find the top five states by the number of documents in the collection. In Mongo, the aggregation pipeline will look as follows:
[ {'$project':{'state':1, '_id':0}}, {'$group':{'_id':'$state', 'count':{'$sum':1}}} {'$sort':{'count':-1}}, {'$limit':5} ]
In spring-data-mongodb
, we invoked the aggregation
operation using the MongoTemplate
class as follows:
Aggregation aggregation = newAggregation( project("state", "_id"), group("state").count().as("count"), sort(Direction.DESC, "count"), limit(5) ); AggregationResults<DBObject> results = mongoTemplate.aggregate( aggregation, "postalCodes", DBObject.class);
The key is in creating an instance of the org.springframework.data.mongodb.core.aggregation.Aggregation
class. The newAggregation
method is statically imported from the same class and accepts varargs for different instances of org.springframework.data.mongodb.core.aggregation.AggregationOperation
that correspond to the one operation in the chain. The Aggregation
class has various static methods to create the instances of AggregationOperation
. We have used a few of them, such as project
, group
, sort
, and limit
. For more details and available methods, refer to the Javadoc. The aggregate
method in MongoTemplate
takes three arguments. The first one is the instance of the Aggregation
class, the second one is the name of the collection, and the third one is the return type of the aggregation result. Refer to the aggregation
operation test case for more details.
spring-data-mongodb
project at http://docs.spring.io/spring-data/data-mongodb/docs/current/reference/