Wednesday, May 16, 2012

MongoDB and Spring Data

This is a follow-up to my blog entry on 2012-04-28:  MongoDB - Jongo and Morphia.  In that post I talked about Java and MongoDB integration with the Jongo and Morphia APIs.  Today I am going to talk about using the Spring Data API with MongoDB; moreover, I will be focusing on the data repository approach.  At the time of this writing, Spring Data for MongoDB is at release 1.01 GA.

If you have not used Spring Data, then there is no time like now.  This is not a tutorial on Spring Data, but simply an example of how I used the Spring Data Repository approach to integrate with MongoDB.  Like my previous blog, I start with the Employee domain object.

The Employee object is composed of two other objects, Address and Department, and extends Person.  In the Employee object we use the class-level @Document annotation to map the Employee as a MongoDB document entity.  It will show up in the employee collection.  Optionally, I have included the @Id Spring Data annotation to map the internal _id field to the MongoDB ObjectID data type.  I have also elected to create two secondary indexes for the employeeId and hireDate fields using the @Indexed annotation.  When stored in MongoDB, there will be three indexes created.

The indexes created from the @Indexed annotation will be created with the default settings passed in as arguments to the annotations.  As seen below, I have defined the index on the employeeId field to be unique and sparse.  The unique index has a similar effect as a unique key constraint in a RDBMS solution.  When using the save() method of the repository, if storage of a document is attempted, and the document has a value in a field that is indexed uniquely, the document is overwritten; no duplicates are created.

The sparse property set to true tells MongoDB only to include documents in this index that actually contain a value in the indexed field.

That's it for the Employee object.  Unlike the Morphia approach there are no Spring Data annotations in the Address or Department domain objects (unless I want more indexing); these objects will be stored in MongoDB as embedded documents to the Employee document.

Next I create a MongoDB Spring Data Repository interlace by extending the MongoRepository interface.  Spring Data Repositories are very powerful, in so much as they rely on the typical Spring context loading mechanism, and they create the implementation code for you.  No where in my example will you see me writing the implementation for my repository interface.  Spring Data generates that implementation for me when the Spring context is loaded.

Code generation is augmented by a method naming convention seen below.  By naming my methods using the Spring Data convention, I can have Spring Data auto generate the implementation code.  For example, it is easy to see that if I use the findByLastName(String lastName) method Spring Data will be able to tell, using JavaBeans conventions, that there is field named lastName in my object and in the MongoDB database/collection.  This method can return multiple Employee documents, so the return type is a list of employees.

Of course this is mainly for the basic CRUD operations. 

According to Spring Data MongoDB documentation there are a set of available methods in the Repository interface.  Insert is not one of them.  If insert is needed, then the Spring Data Template for MongoDB should be used.

More complex queries to the database would require some specific implementation in the repository, possibly using the @Query annotation.  For example, if I wanted a specific query for lastName and, I could write the method findByEmployeeLastNameAndDepartmentName() and annotate it with the MongoDB JSON formatted query like so.  In this example I still don't write the Java implementation, just the specific MongoDB query.  Spring Data does the rest. 


You will also notice that findByLastNameAndDepartmentName() method.  In this version, you see the complexity and power of the Spring Data method parser and query generator.  It parses through the method signature, finds lastName as a field, and as a nested field.  I really did not need the @Query annotation after all.

The EmployeeServiceImpl uses the EmployeeRepository directly.  It is injected into the service implementation via Spring setter injection, as configured in the config XML.  The important thing to remember here is that there is never any implementation code written for EmployeeRepository.

Under the covers I have ensured the proper plumbing of database connectivity via the Spring Data config elements in a context config file.  In this file, I have the typical Spring headers with the appropriate schema definitions and locations.  After that house keeping, I define the mongo instance and the mongo repository locations.  Then I define the mongo template to be used by the mongo repository.  The repository needs the template for database connectivity via a factory pattern.

To run this example, I use the class below.  I loaded the Spring XML config via the GenericXmlApplicationContext.

Like many other Spring examples, there are usually several ways to accomplish the same thing.  I have used a mixture of annotations and XML configurations that I am comfortable with.  It's hard to beat the simplicity of "convention over configuration" offered by the Spring Data Repositories ORM approach.  The parsing of repository interfaces and implementation code generation is done at Spring container start-up time.  So there is no hit when your application code goes to use the data repository.