Saturday, April 16, 2011

Spring Data - MongoDB (Revision for 1.0.0.M2)

Introduction

In this tutorial we will upgrade our previous project Spring Data - MongoDB Tutorial from 1.0.0.M1 to 1.0.0.M2. Our purpose here is to take advantage of the improvements and new features in 1.0.0.M2. We will also remove our custom CRUD operations and use Spring Data's Repository support. To appreciate Spring Data, a knowledge of basic CRUD functions using native MongoDB support would be beneficial (click here for a review).

Note: An updated version of this tutorial is now accessible at Spring MVC 3.1 - Implement CRUD with Spring Data MongoDB (Part 1)

MongoDB tutorials
Spring Data - MongoDB (Revision for 1.0.0.M2)
Spring Data - MongoDB Tutorial (1.0.0.M1)
Spring MVC 3: Using a Document-Oriented Database - MongoDB

What is MongoDB?
MongoDB (from "humongous") is a scalable, high-performance, open source, document-oriented database. Written in C++, MongoDB features:
  • Document-oriented storage
  • Full Index Support
  • Replication & High Availability
  • Scale horizontally without compromising functionality.
  • Rich, document-based queries.
  • Atomic modifiers for contention-free performance.
  • Flexible aggregation and data processing.
  • Store files of any size without complicating your stack.
  • Enterprise class support, training, and consulting available.

Source: http://www.mongodb.org/

What is Spring Data - Document?
The goal of the Spring Data Document (or DATADOC) framework is to provide an extension to the Spring programming model that supports writing applications that use Document databases. The Spring framework has always promoted a POJO programming model with a strong emphasis on portability and productivity. These values are caried over into Spring Data Document.

The programming model follows the familiar Spring 'template' style, so if you are familar with Spring template classes such as JdbcTemplate, JmsTemplate, RestTemplate, you will feel right at home. For example, MongoTemplate removes much of the boilerplate code you would have to write when using the MongoDB driver to save POJOs as well as a rich java based query interface to retrieve POJOs. The programming model also offers a new Repository approach in which the Spring container will provide an implementation of a Repository based soley off an interface definition which can also include custom finder methods.

Source: Spring Datastore Document - Reference Documentation 1.0.0.M2
In a nutshell MongoDB uses JSON format instead of SQL There's no static schema to create. All schemas are dynamic, meaning you create them on-the-fly. You can try a real-time online shell of MongoDB at http://try.mongodb.org/. Visit the official MongoDB site for a thorough discussion.

Prerequisites
In order to complete this tutorial, you will be required to install a copy of MongoDB. If you don't have one yet, grab a copy now by visiting http://www.mongodb.org/display/DOCS/Quickstart. Installation is amazingly simple and easy.

Comparison
If you had been following my blog about Spring and MongoDB integration, the following directory comparison shows what needs to be updated from our previous project based on 1.0.0.M1 (Spring Data - MongoDB Tutorial) to our new project based on 1.0.0.M2.

The left pane is the project based on 1.0.0.M1; whereas, the right pane is the project based on 1.0.0.M2. If you haven't seen the previous project, that's okay. You should still be able to follow this tutorial.

Development

Our application is a simple CRUD system for managing a list of Persons. Data is stored in MongoDB database. We'll start by declaring our domain object. Then we'll add a repository and discuss the service layer. And lastly we'll add the controllers.

The Domain Layer

Our application contains a single domain object named Person. It consists the following properties:
id
firstName
lastName
money
Here's the class declaration:

Person.java
package org.krams.tutorial.domain;

import java.io.Serializable;

import org.springframework.data.annotation.Id;
import org.springframework.data.document.mongodb.mapping.Document;

/**
 * A simple POJO representing a Person
 * <p>
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Document
public class Person implements Serializable {

 private static final long serialVersionUID = -5527566248002296042L;
 
 @Id
 private String id;
 private String firstName;
 private String lastName;
 private Double money;

 public String getId() {
  return id;
 }

 public void setId(String id) {
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public Double getMoney() {
  return money;
 }

 public void setMoney(Double money) {
  this.money = money;
 }
}
Notes
In 1.0.0.M1 we used pid to signify the primary identity of the Person due to a bug (or a design decision) from Spring. Using id as the field messes up the id property by merging it with the built-in _id property of MongoDB. See it here.

With 1.0.0.M2 Spring Data support for MongoDB has been improved. We now have annotations just like in JPA and Hibernate. We can now specify which field in our class should be the id field.

Let's discuss in detail the annotations we used in this class:

@Id
"Applied at the field level to mark the field used for identiy purpose."

"The @Id annotation tells the mapper which property you want to use for the MongoDB _id property"

"The @Id annotation is applied to fields. MongoDB lets you store any type as the _id field in the database, including long and string. It is of course common to use ObjectId for this purpose. If the value on the @Id field is not null, it is stored into the database as-is. If it is null, then the converter will assume you want to store an ObjectId in the database. For this to work the field type should be either ObjectId, String, or BigInteger."

What this basically means you can choose any field in your class, annotate it with @Id, and it becomes the id. However, when you examine your MongoDB database, the value is stored in the _id field regardless of the name of your Java class field.

For example, if you mark the following field:
@Id
private Long myCustomIdName
private String firstName
private String lastName

In MongoDB database, it will be stored as:
{ "_id" : "69133244-fcc0-42b2-aa59-308f00c75860", "firstName" : "John", "lastName" : "Smith" }
Notice the field myCustomIdName doesn't exist. But it's value is stored in the _id field.

@Document
"Applied at the class level to indicate this class is a candidate for mapping to the database. You can specify the name of the collection where the database will be stored."

"Although it is not necessary for the mapping framework to have this annotation (your POJOs will be mapped correctly, even without any annotations), it allows the classpath scanner to find and pre-process your domain objects to extract the necessary metadata. If you don't use this annotation, your application will take a slight performance hit the first time you store a domain object because the mapping framework needs to build up its internal metadata model so it knows about the properties of your domain object and how to persist them."

See: Mapping annotation overview - Spring Datastore Document - Reference Documentation 1.0.0.M2
What this basically means it's better to annotate your domain class with @Document for performance reasons. However there's a hidden charge. When you save data to MongoDB using Spring Data's Repository support, the collection name will be based on the class name. This means you cannot specify custom collection names.

For example, if we have the following domain class:
@Document
public class Animal() {
  ...
}
In MongoDB, it will be stored as a collection with the name "animal". To verify that, connect to your MongoDB in a terminal. Select your database. Then run "show collections".

For example:
krams@desktop:/$ '/home/krams/Desktop/mongodb-linux-i686-1.6.5/bin/mongo' 
MongoDB shell version: 1.6.5
connecting to: test
> show dbs
admin
local
mydb
> use mydb
switched to db mydb
> show collections
animal
system.indexes

The Service Layer

Our updated service class is essentially the same as with 1.0.0.M1. The main difference now is we will no longer implement our own CRUD operations. And we will no longer use MongoTemplate to provide CRUD service for our Person objects. Instead, we will take advantage of Spring Data's Repository support, which already exists in 1.0.0.M1, but we just didn't take advantage of it in the previous tutorial (Spring Data - MongoDB Tutorial).

What is Spring Data Repository?
Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code had to be written. Domain classes were anemic and haven't been designed in a real object oriented or domain driven manner.

Using both of these technologies makes developers life a lot easier regarding rich domain model's persistence. Nevertheless the amount of boilerplate code to implement repositories especially is still quite high. So the goal of the repository abstraction of Spring Data is to reduce the effort to implement data access layers for various persistence stores significantly

Source: Spring Datastore Document - Reference Documentation 1.0.0.M2

Here's our refactored PersonService declaration:

PersonService.java
package org.krams.tutorial.service;

import java.util.List;
import java.util.UUID;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.krams.tutorial.repositories.IPersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service for processing {@link Person} objects.
 * <p>
 * Uses Spring Data''s {@Repository)  to perform CRUD operations.
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Repository("personService")
@Transactional
public class PersonService {

 protected static Logger logger = Logger.getLogger("service");

 @Autowired
 private IPersonRepository personRepository;
 
 /**
  * Retrieves all persons
  */
 public List<Person> getAll() {
  
  logger.debug("Retrieving all persons");
        List<Person> persons = personRepository.findAll();
        
  return persons;
 }
 
 /**
  * Retrieves a single person
  */
 public Person get( String id ) {
  logger.debug("Retrieving an existing person");
  
  // Find an entry where id matches the id
        Person person = personRepository.findOne(id);
     
  return person;
 }
 
 /**
  * Adds a new person
  */
 public Boolean add(Person person) {
  logger.debug("Adding a new user");
  
  try {
   
   // Set a new value to the id property first since it's blank
   person.setId(UUID.randomUUID().toString());
   // Insert to db
   personRepository.save(person);

   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to add new user", e);
   return false;
  }
 }
 
 /**
  * Deletes an existing person
  */
 public Boolean delete(String id) {
  logger.debug("Deleting existing person");
  
  try {
   
   // Find an entry where id matches the id, then delete that entry
         personRepository.delete(personRepository.findOne(id));
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to delete new user", e);
   return false;
  }
 }
 
 /**
  * Edits an existing person
  */
 public Boolean edit(Person person) {
  logger.debug("Editing existing person");
  
  try {
   
   // Find an entry where id matches the id
         Person existingPerson = personRepository.findOne(person.getId());
         
         // Assign new values
         existingPerson.setFirstName(person.getFirstName());
         existingPerson.setLastName(person.getLastName());
         existingPerson.setMoney(person.getMoney());
         
         // Save to db
         personRepository.save(existingPerson);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to edit existing user", e);
   return false;
  }
  
 }
}
The code should should be self-explanatory. Notice we have injected a reference to a IPersonRepository. Let's examine the contents of this repository:

IPersonRepository.java
package org.krams.tutorial.repositories;

import org.krams.tutorial.domain.Person;
import org.springframework.data.document.mongodb.repository.MongoRepository;
import org.springframework.transaction.annotation.Transactional;

@Transactional
public interface IPersonRepository extends  MongoRepository<Person, String> {

}
Nothing. It's basically an empty interface that extends from MongoRepository. That's the magic. We no longer provide our own DAO or CRUD implementations, but of course you can add your own methods if necessary.

Now let's examine how Spring Data has reduced the amount of code in our service. To appreciate this difference, we will do a side-by-side comparison of MongoTemplate and Repository. Then later as a review, we will also repost the side-by-side comparison between MongoTemplate and traditional MongoDB access to show justice to MongoTemplate.

Spring's MongoTemplate Comparison versus Spring's Repository

Retrieving all entries

old implementation
public List<Person> getAll() {
  logger.debug("Retrieving all persons");
 
        Query query = new Query(where("pid").exists(true));
        List<Person> persons = mongoTemplate.find(query, Person.class);
        
  return persons;
 }

new implementation
public List<Person> getAll() {
  
  logger.debug("Retrieving all persons");
        List<Person> persons = personRepository.findAll();
        
  return persons;
 }

Retrieving a single entry

old implementation
public Person get( String id ) {
  logger.debug("Retrieving an existing person");
  
  // Find an entry where pid matches the id
        Query query = new Query(where("pid").is(id));
        // Execute the query and find one matching entry
        Person person = mongoTemplate.findOne("mycollection", query, Person.class);
     
  return person;
 }

new implementation
public Person get( String id ) {
  logger.debug("Retrieving an existing person");
  
  // Find an entry where id matches the id
        Person person = personRepository.findOne(id);
     
  return person;
 }

Adding a new entry

old implementation
public Boolean add(Person person) {
  logger.debug("Adding a new user");
  
  try {
   
   // Set a new value to the pid property first since it's blank
   person.setPid(UUID.randomUUID().toString());
   // Insert to db
      mongoTemplate.insert("mycollection", person);

   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to add new user", e);
   return false;
  }
 }

new implementation
public Boolean add(Person person) {
  logger.debug("Adding a new user");
  
  try {
   
   // Set a new value to the id property first since it's blank
   person.setId(UUID.randomUUID().toString());
   // Insert to db
   personRepository.save(person);

   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to add new user", e);
   return false;
  }
 }

Deleting an entry

old implementation
public Boolean delete(String id) {
  logger.debug("Deleting existing person");
  
  try {
   
   // Find an entry where pid matches the id
         Query query = new Query(where("pid").is(id));
         // Run the query and delete the entry
         mongoTemplate.remove(query);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to delete new user", e);
   return false;
  }
 }

new implementation
public Boolean delete(String id) {
  logger.debug("Deleting existing person");
  
  try {
   
   // Find an entry where id matches the id, then delete that entry
         personRepository.delete(personRepository.findOne(id));
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to delete new user", e);
   return false;
  }
 }

Updating an entry

old implementation
public Boolean edit(Person person) {
  logger.debug("Editing existing person");
  
  try {
   
   // Find an entry where pid matches the id
         Query query = new Query(where("pid").is(person.getPid()));
         
   // Declare an Update object. 
         // This matches the update modifiers available in MongoDB
   Update update = new Update();
         
         update.set("firstName", person.getFirstName());
         mongoTemplate.updateMulti(query, update);
         
         update.set("lastName", person.getLastName());
         mongoTemplate.updateMulti(query, update);
         
         update.set("money", person.getMoney());
         mongoTemplate.updateMulti(query, update);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to edit existing user", e);
   return false;
  }
  
 }

new implementation
public Boolean edit(Person person) {
  logger.debug("Editing existing person");
  
  try {
   
   // Find an entry where id matches the id
         Person existingPerson = personRepository.findOne(person.getId());
         
         // Assign new values
         existingPerson.setFirstName(person.getFirstName());
         existingPerson.setLastName(person.getLastName());
         existingPerson.setMoney(person.getMoney());
         
         // Save to db
         personRepository.save(existingPerson);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to edit existing user", e);
   return false;
  }
  
 }
Notice how we managed to make the implementation simpler. There are less lines of code and it's more readable, especially in the edit() method.

To appreciate even further what Spring Data and MongoTemplate has really accomplished, let's do a comparison with MongoTemplate and traditional MongoDB access. This also applies for the Repository interface. (The following can be found at my Spring Data - MongoDB Tutorial). Feel free to skip this section if you don't care about this information.

Spring's MongoTemplate versus Traditional MongoDB access

Retrieving all entries

old implementation
public List<person> getAll() {
  logger.debug("Retrieving all persons");
   
  // Retrieve collection
  DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
  // Retrieve cursor for iterating records
     DBCursor cur = coll.find();
     // Create new list
  List<person> items = new ArrayList<person>();
  // Iterate cursor
        while(cur.hasNext()) {
         // Map DBOject to Person
         DBObject dbObject = cur.next();
         Person person = new Person();
          
         person.setId(dbObject.get("id").toString());
         person.setFirstName(dbObject.get("firstName").toString());
         person.setLastName(dbObject.get("lastName").toString());
         person.setMoney(Double.valueOf(dbObject.get("money").toString()));
 
         // Add to new list
         items.add(person);
        }
         
        // Return list
  return items;
 }

new implementation
public List<Person> getAll() {
  logger.debug("Retrieving all persons");
 
        Query query = new Query(where("pid").exists(true));
        List<Person> persons = mongoTemplate.find(query, Person.class);
        
  return persons;
 }

Retrieving a single entry

old implementation
public Person get( String id ) {
  logger.debug("Retrieving an existing person");
   
  // Retrieve collection
  DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
  // Create a new object
  DBObject doc = new BasicDBObject();
  // Put id to search
        doc.put("id", id);
         
        // Find and return the person with the given id
        DBObject dbObject = coll.findOne(doc);
         
        // Map DBOject to Person
     Person person = new Person();
     person.setId(dbObject.get("id").toString());
     person.setFirstName(dbObject.get("firstName").toString());
     person.setLastName(dbObject.get("lastName").toString());
     person.setMoney(Double.valueOf(dbObject.get("money").toString()));
      
        // Return person
  return person;
 }

new implementation
public Person get( String id ) {
  logger.debug("Retrieving an existing person");
  
  // Find an entry where pid matches the id
        Query query = new Query(where("pid").is(id));
        // Execute the query and find one matching entry
        Person person = mongoTemplate.findOne("mycollection", query, Person.class);
     
  return person;
 }

Adding a new entry

old implementation
public Boolean add(Person person) {
  logger.debug("Adding a new user");
   
  try {
   // Retrieve collection
   DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
   // Create a new object
   BasicDBObject doc = new BasicDBObject();
   // Generate random id using UUID type 4
   // See http://en.wikipedia.org/wiki/Universally_unique_identifier
         doc.put("id", UUID.randomUUID().toString() ); 
         doc.put("firstName", person.getFirstName());
         doc.put("lastName", person.getLastName());
         doc.put("money", person.getMoney());
         // Save new person
         coll.insert(doc);
          
   return true;
    
  } catch (Exception e) {
   logger.error("An error has occurred while trying to add new user", e);
   return false;
  }
 }

new implementation
public Boolean add(Person person) {
  logger.debug("Adding a new user");
  
  try {
   
   // Set a new value to the pid property first since it's blank
   person.setPid(UUID.randomUUID().toString());
   // Insert to db
      mongoTemplate.insert("mycollection", person);

   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to add new user", e);
   return false;
  }
 }

Deleting an entry

old implementation
public Boolean delete(String id) {
  logger.debug("Deleting existing person");
   
  try {
   // Retrieve person to delete
   BasicDBObject item = (BasicDBObject) getDBObject( id );
   // Retrieve collection
   DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
   // Delete retrieved person
         coll.remove(item);
          
   return true;
    
  } catch (Exception e) {
   logger.error("An error has occurred while trying to delete new user", e);
   return false;
  }
 }

new implementation
public Boolean delete(String id) {
  logger.debug("Deleting existing person");
  
  try {
   
   // Find an entry where pid matches the id
         Query query = new Query(where("pid").is(id));
         // Run the query and delete the entry
         mongoTemplate.remove(query);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to delete new user", e);
   return false;
  }
 }

Updating an entry

old implementation
public Boolean edit(Person person) {
  logger.debug("Editing existing person");
   
  try {
   // Retrieve person to edit
   BasicDBObject existing = (BasicDBObject) getDBObject( person.getId() );
    
   DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
    
   // Create new object
   BasicDBObject edited = new BasicDBObject();
   // Assign existing details
   edited.put("id", person.getId()); 
   edited.put("firstName", person.getFirstName());
   edited.put("lastName", person.getLastName());
   edited.put("money", person.getMoney());
   // Update existing person
         coll.update(existing, edited);
          
   return true;
    
  } catch (Exception e) {
   logger.error("An error has occurred while trying to edit existing user", e);
   return false;
  }
   
 }

new implementation
public Boolean edit(Person person) {
  logger.debug("Editing existing person");
  
  try {
   
   // Find an entry where pid matches the id
         Query query = new Query(where("pid").is(person.getPid()));
         
   // Declare an Update object. 
         // This matches the update modifiers available in MongoDB
   Update update = new Update();
         
         update.set("firstName", person.getFirstName());
         mongoTemplate.updateMulti(query, update);
         
         update.set("lastName", person.getLastName());
         mongoTemplate.updateMulti(query, update);
         
         update.set("money", person.getMoney());
         mongoTemplate.updateMulti(query, update);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to edit existing user", e);
   return false;
  }
  
 }

Configuration

To use Spring Data's Repository support we need to update our mongo-config.xml configuration. We also need a reference to a MongoDB database. Here's our updated configuration.

mongo-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:mongo="http://www.springframework.org/schema/data/mongo"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/data/mongo
      http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd">
 
   <mongo:repositories base-package="org.krams.tutorial.repositories" />
  
 <!-- Default bean name is 'mongo' -->
 <mongo:mongo host="localhost" port="27017"/>
 
 <!-- Offers convenience methods and automatic mapping between MongoDB JSON documents and your domain classes. -->
   <bean id="mongoTemplate" class="org.springframework.data.document.mongodb.MongoTemplate">
     <constructor-arg ref="mongo"/>
      <constructor-arg name="databaseName" value="mydb5"/>
      <constructor-arg name="defaultCollectionName" value="person"/>
   </bean>

   <bean id="initService" class="org.krams.tutorial.service.InitService" init-method="init"/>
</beans>
Notice we're using the mongo namespace:
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
We've declared a reference to a MongoDB database by declaring:
<mongo:mongo host="localhost" port="27017"/>

Then we declared a mongo:repositories which is reponsible for instantiating our repository.

Then we declared a MongoTemplate that references a MongoDB database (mongo), a database (mydb), and a collection (mycollection):
<bean id="mongoTemplate" class="org.springframework.data.document.mongodb.MongoTemplate">
     <constructor-arg ref="mongo"/>
      <constructor-arg name="databaseName" value="mydb5"/>
      <constructor-arg name="defaultCollectionName" value="person"/>
   </bean>

Correction:
You might ask why we still have a reference to MongoTemplate if we're using a Repository? That's because for low-level functions like dropping a collection, a Repository does not support such (for now). Whereas, MongoTemplate is able to.
Based on the comments of Kalaivanan Durairaj,
As I understand this, You need MongoTemplate even if don't plan on using low-level functions because the mongo:repository will be still using mongoTemplate to do all the work. Its just being auto wired right now sine you used the standard name "mongoTemplate".

MongoRepository consume mongoTemplate and provide us a set of common DAO API.
This means MongoTemplate is a required bean regardless if you're using Repository or not.

We also declared an initService
<bean id="initService" class="org.krams.tutorial.service.InitService" init-method="init"></bean>
The purpose of the initService is to prepopulate our MongoDB with sample data. We have updated the implementation of this service to take account on how Spring Data forces us to use the class name as the collection name (Previously, I was using a custom collection name "mycollection").

Here's the class declaration:

InitService.java
package org.krams.tutorial.service;

import java.util.UUID;

import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.springframework.data.document.mongodb.MongoTemplate;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service for initializing MongoDB with sample data.
 * <p>
 * To recreate the collection, we must use {@link MongoTemplate} 
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Transactional
public class InitService {

 protected static Logger logger = Logger.getLogger("service");
 
 @Resource(name="mongoTemplate")
 private MongoTemplate mongoTemplate;

 private void init() {
  // Populate our MongoDB database
  logger.debug("Init MongoDB");
  
  // Drop existing collection
  mongoTemplate.dropCollection("person");

  // Create new object
  Person p = new Person ();
  p.setId(UUID.randomUUID().toString());
  p.setFirstName("John");
  p.setLastName("Smith");
  p.setMoney(1000.0);
  
  // Insert to db
     mongoTemplate.insert("person", p);

     // Create new object
  p = new Person ();
  p.setId(UUID.randomUUID().toString());
  p.setFirstName("Jane");
  p.setLastName("Adams");
  p.setMoney(2000.0);
  
  // Insert to db
     mongoTemplate.insert("person", p);
        
     // Create new object
  p = new Person ();
  p.setId(UUID.randomUUID().toString());
  p.setFirstName("Jeff");
  p.setLastName("Mayer");
  p.setMoney(3000.0);
  
  // Insert to db
     mongoTemplate.insert("person", p);
 }
}

The Controller Layer

After creating the domain and service classes, we need to declare a controller that will handle the web requests.

If you'd been following the previous tutorial, we've made a minor update in this tutorial. We changed all references to "pid" to "id"

Before:
@RequestParam(value="pid", required=true)

Now:
@RequestParam(value="id", required=true)

Here's the class declaration:

MainController.java
package org.krams.tutorial.controller;

import java.util.List;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.krams.tutorial.service.PersonService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;


/**
 * Handles and retrieves person request
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Controller
@RequestMapping("/main")
public class MainController {

 protected static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="personService")
 private PersonService personService;
 
 /**
  * Handles and retrieves all persons and show it in a JSP page
  * 
  * @return the name of the JSP page
  */
    @RequestMapping(value = "/persons", method = RequestMethod.GET)
    public String getPersons(Model model) {
     
     logger.debug("Received request to show all persons");
     
     // Retrieve all persons by delegating the call to PersonService
     List<Person> persons = personService.getAll();
     
     // Attach persons to the Model
     model.addAttribute("persons", persons);
     
     // This will resolve to /WEB-INF/jsp/personspage.jsp
     return "personspage";
 }
    
    /**
     * Retrieves the add page
     * 
     * @return the name of the JSP page
     */
    @RequestMapping(value = "/persons/add", method = RequestMethod.GET)
    public String getAdd(Model model) {
     logger.debug("Received request to show add page");
    
     // Create new Person and add to model
     // This is the formBackingOBject
     model.addAttribute("personAttribute", new Person());

     // This will resolve to /WEB-INF/jsp/addpage.jsp
     return "addpage";
 }
 
    /**
     * Adds a new person by delegating the processing to PersonService.
     * Displays a confirmation JSP page
     * 
     * @return  the name of the JSP page
     */
    @RequestMapping(value = "/persons/add", method = RequestMethod.POST)
    public String add(@ModelAttribute("personAttribute") Person person) {
  logger.debug("Received request to add new person");
  
     // The "personAttribute" model has been passed to the controller from the JSP
     // We use the name "personAttribute" because the JSP uses that name
  
  // Call PersonService to do the actual adding
  personService.add(person);

     // This will resolve to /WEB-INF/jsp/addedpage.jsp
  return "addedpage";
 }
    
    /**
     * Deletes an existing person by delegating the processing to PersonService.
     * Displays a confirmation JSP page
     * 
     * @return  the name of the JSP page
     */
    @RequestMapping(value = "/persons/delete", method = RequestMethod.GET)
    public String delete(@RequestParam(value="id", required=true) String id, 
              Model model) {
   
  logger.debug("Received request to delete existing person");
  
  // Call PersonService to do the actual deleting
  personService.delete(id);
  
  // Add id reference to Model
  model.addAttribute("id", id);
     
     // This will resolve to /WEB-INF/jsp/deletedpage.jsp
  return "deletedpage";
 }
    
    /**
     * Retrieves the edit page
     * 
     * @return the name of the JSP page
     */
    @RequestMapping(value = "/persons/edit", method = RequestMethod.GET)
    public String getEdit(@RequestParam(value="id", required=true) String id,  
              Model model) {
     logger.debug("Received request to show edit page");
    
     // Retrieve existing Person and add to model
     // This is the formBackingOBject
     model.addAttribute("personAttribute", personService.get(id));
     
     // This will resolve to /WEB-INF/jsp/editpage.jsp
     return "editpage";
 }
    
    /**
     * Edits an existing person by delegating the processing to PersonService.
     * Displays a confirmation JSP page
     * 
     * @return  the name of the JSP page
     */
    @RequestMapping(value = "/persons/edit", method = RequestMethod.POST)
    public String saveEdit(@ModelAttribute("personAttribute") Person person, 
                 @RequestParam(value="id", required=true) String id, 
                Model model) {
     logger.debug("Received request to update person");
    
     // The "personAttribute" model has been passed to the controller from the JSP
     // We use the name "personAttribute" because the JSP uses that name
     
     // We manually assign the id because we disabled it in the JSP page
     // When a field is disabled it will not be included in the ModelAttribute
     person.setId(id);
     
     // Delegate to PersonService for editing
     personService.edit(person);
     
     // Add id reference to Model
  model.addAttribute("id", id);
  
     // This will resolve to /WEB-INF/jsp/editedpage.jsp
  return "editedpage";
 }
    
}
Our controller is a simple class that delegates actual processing to PersonService. When the service is done processing, the controller forwards the result to a JSP view.

Other Configurations and Files

To make the tutorial manageable, I've decided not to post the following configuration files in this tutorial:
web.xml
spring-servlet.xml
applicationContext.xml
These files are standard Spring MVC related configuration files. You can find them in the downloadable application at the end of this tutorial.

I have also left out the JSP declarations. You can find a description of them in the following tutorial: Spring MVC 3: Using a Document-Oriented Database - MongoDB. There's a slight modification. I've updated all JSP references to "pid" to "id". Same idea with the MainController.

Run the Application

To run the application, open your browser and enter the following URL:
http://localhost:8080/spring-data-mongodb-m2-repository/krams/main/persons
You should see the following CRUD view:


Conclusion

That's it. We have successfully refactored our existing Spring Data - MongoDB application based on 1.0.0.M1 to 1.0.0.M2. We've also updated our service by removing our own CRUD implementations and relying instead on Spring Data's Repository support.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-mongodb/

You can download the project as a Maven build. Look for the spring-data-mongodb-m2-repository.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Data - MongoDB (Revision for 1.0.0.M2) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

19 comments:

  1. Thanks a lot for this very useful tutorial

    ReplyDelete
  2. Hi, fantastic intro to spring data with mongodb.

    I have one comment on this:

    "You might ask why we still have a reference to MongoTemplate if we're using a Repository? That's because for low-level functions like dropping a collection, a Repository does not support such (for now). Whereas, MongoTemplate is able to."

    As I understand this, You need MongoTemplate even if don't plan on using low-level functions because the mongo:repository will be still using mongoTemplate to do all the work. Its just being auto wired right now sine you used the standard name "mongoTemplate".

    MongoRepository consume mongoTemplate and provide us a set of common DAO API.

    Thanks.

    ReplyDelete
  3. @Kalaivanan, thanks for pointing that out. I just tested it and it's really required. I think I got mixed up when I extending an abstract class that didn't need a MongoTemplate. Thanks for pointing that detail.

    ReplyDelete
  4. Great tutorial, thanks for sharing. And you keep it very up to date.

    ReplyDelete
  5. Hi ,

    Is there an OSGi based bundle to use it ?? I tried the dependency

    org.springframework.data
    spring-data-mongodb
    1.0.0.M2

    but it doesn't work with me it required OSGi bundle

    Please advise.

    ReplyDelete
  6. @Krams: I have one request for you. Do you have any examples on how to use @DBRef annotation. There is no proper documentation around it. Also, if the example covers both embedded vs reference documents using a JSON input that would be great. Thanks again.

    ReplyDelete
  7. Great tutorial. Do you know how to do filters using repository to simulate SQL where clause?

    Thanks

    ReplyDelete
  8. @Krams: Excelent tutorial. When are you planning on upgrading it to M3? And for the @DBRef suggestion from Sairam, +1

    ReplyDelete
  9. Hello all ,
    I'm currently getting this error

    Unexpected exception parsing XML document from ServletContext resource [/WEB-INF/mongo-config.xml]; nested exception is java.lang.AbstractMethodError: org.springframework.data.document.mongodb.config.SimpleMongoRepositoryConfiguration.getRepositoryBaseInterface()Ljava/lang/Class;

    It seems that I'm missing some important jar.
    Can anyone please list the jars , needed to implement this example.

    ReplyDelete
  10. Hi,

    I'm getting following error.
    org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from ServletContext resource [/WEB-INF/mongo-config.xml]; nested exception is java.lang.AbstractMethodError: org.springframework.data.document.mongodb.config.SimpleMongoRepositoryConfiguration.getRepositoryBaseInterface()Ljava/lang/Class;

    It seems, I am missing one of the important jar.

    Can you please list the jar files you used for this example.

    ReplyDelete
  11. Even more awesome that the previous post!! :)

    ReplyDelete
  12. Can you please update this example to include some validations based on JSR 303?

    ReplyDelete
  13. @Ravi Ensure that you have the correct version of Spring Data Commons library on your classpath. For Spring Data Document 1.0.0.M3 you have to use Spring Dataa Commons 1.1.0.M1. You may need to include Spring Mongo Java driver 2.6.x on the classpath.

    ReplyDelete
  14. Hi,
    It would be great if you post some examples of single sign on web application using spring

    ReplyDelete
  15. Will you please post an example having some complex queries like cont() and max() etc. ????

    ReplyDelete
  16. Great post, I am really enjoying mongo db and spring data. I am having one issue though. I need to have multiple databases, and want to use MongoRepository. But it seems to default to looking for mongoTemplate. Is there a way to have more than one? I started by having multiple templates and using the standard spring data by injecting each template and using things like Update, but I would rather use Repositories.

    ReplyDelete
  17. Thank you for great post..
    I'm very newbie with monggodb. And I'm curious about @transactional annotaion operation with monggodb. Could you give example of it??

    from Korea

    ReplyDelete
  18. @Anonymous, this is an old tutorial. For historical purposes, I have kept this original guide. You should take a look at the following guide instead: http://krams915.blogspot.com/2012/01/spring-mvc-31-implement-crud-with_4739.html I have noted this at the very top of this tutorial

    ReplyDelete
    Replies
    1. Oh!~ Thank you, very much!~ It would be awesome tutorial!~

      ^^

      Delete