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

Sunday, March 27, 2011

Spring JPA: One-To-Many Association: Using Hibernate as the JPA Vendor

Introduction

In this tutorial we'll convert our previous project
Spring - Hibernate: One-To-Many Association to use Spring JPA. We'll use Hibernate as the JPA provider and show the required changes to convert our project.

Spring MVC 3 and Hibernate Tutorials Series
Spring JPA: Many-To-One Association: Using Hibernate as the JPA Vendor
Spring JPA: One-To-Many Association: Using Hibernate as the JPA Vendor
Spring - Hibernate: Many-To-One Association - Explicitly Specify Join Table, Cascade, and Fetch
Spring - Hibernate: One-To-Many Association - Explicitly Specify Join Table, Cascade, and Fetch
Spring - Hibernate: Many-To-One Association
Spring - Hibernate: One-To-Many Association
Spring MVC 3, Hibernate Annotations, MySQL Integration Tutorial
Spring MVC 3, Hibernate Annotations, HSQLDB Integration Tutorial

What is Spring JPA?
The Spring JPA, available under the org.springframework.orm.jpa package, offers comprehensive support for the Java Persistence API in a similar manner to the integration with Hibernate or JDO, while being aware of the underlying implementation in order to provide additional features.

Source: Spring 3 Reference

What is JPA?
The Java Persistence API simplifies the programming model for entity persistence and adds capabilities that were not in EJB 2.1

The Java Persistence API deals with the way relational data is mapped to Java objects ("persistent entities"), the way that these objects are stored in a relational database so that they can be accessed at a later time, and the continued existence of an entity's state even after the application that uses it ends. In addition to simplifying the entity persistence model, the Java Persistence API standardizes object-relational mapping.

Source: The Java Persistence API - A Simpler Programming Model for Entity Persistence

Changes

Using a diff tool, we can visualize the changes from the original Hibernate project to the JPA project.


Development

Similar with the original project, we'll split our development in three layers: Domain, Service, and Controller.

Domain Layer

In the domain layer, we don't change any existing domain classes. That's because our domain is already using JPA annotation in the beginning.

Service Layer

For the service classes, we will drop all Hibernate references and instead use JPA. Let's start with the CreditCardService:

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

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.CreditCard;
import org.krams.tutorial.domain.Person;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service for processing Credit Cards
 * 
 * @author Krams at {@link http://krams915@blogspot.com
 */
@Service("creditCardService")
@Transactional
public class CreditCardService {

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

 @Resource(name="personService")
 private PersonService personService;
 
 private EntityManager entityManager;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this. entityManager = entityManager;
    }
    
 /**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll(Integer personId) {
  logger.debug("Retrieving all credit cards");
  
  // Create a Hibernate query (HQL)
  Query query = entityManager.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+personId);
  
  Person person = (Person) query.getSingleResult();
  
  // Retrieve all
  return  new ArrayList<CreditCard>(person.getCreditCards());
 }
 
 /**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll() {
  logger.debug("Retrieving all credit cards");
  
  // Create a JPA query
  Query query = entityManager.createQuery("FROM  CreditCard");
  
  // Retrieve all
  return  query.getResultList();
 }
 
 /**
  * Retrieves a single credit card
  */
 public CreditCard get( Integer id ) {
  
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) entityManager.createQuery("FROM CreditCard p WHERE p.id = :id")
  .setParameter("id", id).getSingleResult();
  
  // Persists to db
  return creditCard;
 }
 
 /**
  * Adds a new credit card
  */
 public void add(Integer personId, CreditCard creditCard) {
  logger.debug("Adding new credit card");
 
  // Persists to db
  entityManager.persist(creditCard);
  
  // Add to person as well
  // Retrieve existing person via id
  Person existingPerson = personService.get(personId);
  
  // Assign updated values to this person
  existingPerson.getCreditCards().add(creditCard);

  // Save updates
  personService.edit(existingPerson);
 }
 
 /**
  * Deletes an existing credit card
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing credit card");
  
     // Delete reference to foreign key credit card first
  // We need a SQL query instead of HQL query here to access the third table
     Query query = entityManager.createNativeQuery("DELETE FROM PERSON_CREDIT_CARD " +
       "WHERE creditCards_ID="+id);
     
     query.executeUpdate();
     
  // Retrieve existing credit card
  CreditCard creditCard = this.get(id);
  
  // Delete 
  entityManager.remove(creditCard);
 }
 
 /**
  * Edits an existing credit card
  */
 public void edit(CreditCard creditCard) {
  logger.debug("Editing existing creditCard");
  
  // Retrieve existing credit card via id
  CreditCard existingCreditCard = this.get(creditCard.getId());
  
  // Assign updated values to this credit card
  existingCreditCard.setNumber(creditCard.getNumber());
  existingCreditCard.setType(creditCard.getType());

  // Save updates
  entityManager.merge(existingCreditCard);
 }
}

Let's make a side-by-side comparison between the original Hibernate-based implementation and the new JPA-based implementation:

SessionFactory to EntityManager
To utilize JPA we have to remove Hibernate's SessionFactory and replace it with JPA's EntityManager. Essentially the EntityManager is equivalent to Hibernate's Session object.

old code
@Resource(name="sessionFactory")
private SessionFactory sessionFactory;

new code
@Resource(name="personService")
private PersonService personService;
 
private EntityManager entityManager;

@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
        this. entityManager = entityManager;
}

Retrieve all credit cards by person's id
old code
/**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll(Integer personId) {
  logger.debug("Retrieving all credit cards");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+personId);
  
  Person person = (Person) query.uniqueResult();
  
  // Retrieve all
  return  new ArrayList<CreditCard>(person.getCreditCards());
 }

new code
/**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll(Integer personId) {
  logger.debug("Retrieving all credit cards");
  
  // Create a Hibernate query (HQL)
  Query query = entityManager.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+personId);
  
  Person person = (Person) query.getSingleResult();
  
  // Retrieve all
  return  new ArrayList<CreditCard>(person.getCreditCards());
 }

Retrieve all credit cards
old code
/**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll() {
  logger.debug("Retrieving all credit cards");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("FROM  CreditCard");
  
  // Retrieve all
  return  query.list();
 }

new code
/**
  * Retrieves all credit cards
  */
 public List getAll() {
  logger.debug("Retrieving all credit cards");
  
  // Create a JPA query
  Query query = entityManager.createQuery("FROM  CreditCard");
  
  // Retrieve all
  return  query.getResultList();
 }

Add new credit card
old code
/**
  * Adds a new credit card
  */
 public void add(Integer personId, CreditCard creditCard) {
  logger.debug("Adding new credit card");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
 
  // Persists to db
  session.save(creditCard);
  
  // Add to person as well
  // Retrieve existing person via id
  Person existingPerson = (Person) session.get(Person.class, personId);
  
  // Assign updated values to this person
  existingPerson.getCreditCards().add(creditCard);

  // Save updates
  session.save(existingPerson);
 }

new code
/**
  * Adds a new credit card
  */
 public void add(Integer personId, CreditCard creditCard) {
  logger.debug("Adding new credit card");
 
  // Persists to db
  entityManager.persist(creditCard);
  
  // Add to person as well
  // Retrieve existing person via id
  Person existingPerson = personService.get(personId);
  
  // Assign updated values to this person
  existingPerson.getCreditCards().add(creditCard);

  // Save updates
  personService.edit(existingPerson);
 }

Retrieve a single credit card
old code
/**
  * Retrieves a single credit card
  */
 public CreditCard get( Integer id ) {
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) session.get(CreditCard.class, id);
  
  // Persists to db
  return creditCard;
 }

new code
/**
  * Retrieves a single credit card
  */
 public CreditCard get( Integer id ) {
  
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) entityManager.createQuery("FROM CreditCard p WHERE p.id = :id")
  .setParameter("id", id).getSingleResult();
  
  // Persists to db
  return creditCard;
 }

Delete an existing credit card
old code
/**
  * Deletes an existing credit card
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing credit card");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
     // Delete reference to foreign key credit card first
  // We need a SQL query instead of HQL query here to access the third table
     Query query = session.createSQLQuery("DELETE FROM PERSON_CREDIT_CARD " +
       "WHERE creditCards_ID="+id);
     
     query.executeUpdate();
     
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) session.get(CreditCard.class, id);
  
  // Delete 
  session.delete(creditCard);
 }

new code
/**
  * Deletes an existing credit card
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing credit card");
  
     // Delete reference to foreign key credit card first
  // We need a SQL query instead of HQL query here to access the third table
     Query query = entityManager.createNativeQuery("DELETE FROM PERSON_CREDIT_CARD " +
       "WHERE creditCards_ID="+id);
     
     query.executeUpdate();
     
  // Retrieve existing credit card
  CreditCard creditCard = this.get(id);
  
  // Delete 
  entityManager.remove(creditCard);
 }

Edit an existing credit card
old code
/**
  * Edits an existing credit card
  */
 public void edit(CreditCard creditCard) {
  logger.debug("Editing existing creditCard");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing credit card via id
  CreditCard existingCreditCard = (CreditCard) session.get(CreditCard.class, creditCard.getId());
  
  // Assign updated values to this credit card
  existingCreditCard.setNumber(creditCard.getNumber());
  existingCreditCard.setType(creditCard.getType());

  // Save updates
  session.save(existingCreditCard);
 }

new code
/**
  * Edits an existing credit card
  */
 public void edit(CreditCard creditCard) {
  logger.debug("Editing existing creditCard");
  
  // Retrieve existing credit card via id
  CreditCard existingCreditCard = this.get(creditCard.getId());
  
  // Assign updated values to this credit card
  existingCreditCard.setNumber(creditCard.getNumber());
  existingCreditCard.setType(creditCard.getType());

  // Save updates
  entityManager.merge(existingCreditCard);
 }

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

import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.CreditCard;
import org.krams.tutorial.domain.Person;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service for processing Persons
 * 
 * @author Krams at {@link http://krams915@blogspot.com
 */
@Service("personService")
@Transactional
public class PersonService {

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

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this. entityManager = entityManager;
    }
 
 /**
  * Retrieves all persons
  * 
  * @return a list of persons
  */
 public List<Person> getAll() {
  logger.debug("Retrieving all persons");
  
  // Create a JPA query
  Query query  = entityManager.createQuery("FROM Person");
  
  // Retrieve all
  return query.getResultList();
 }
 
 /**
  * Retrieves a single person
  */
 public Person get( Integer id ) {
  
  // Retrieve existing person
  // Create a JPA query
  Query query = entityManager.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+id);
  
  return (Person) query.getSingleResult();
 }
 
 /**
  * Adds a new person
  */
 public void add(Person person) {
  logger.debug("Adding new person");
  
  // Persists to db
  entityManager.persist(person);
 }
 
 /**
  * Deletes an existing person
  * @param id the id of the existing person
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing person");
  
  // Create a Hibernate query (HQL)
  Query query = entityManager.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+id);
 
  // Retrieve record
  Person person = (Person) query.getSingleResult();
  
  Set<CreditCard> creditCards =person.getCreditCards();
  
  // Delete person
  entityManager.remove(person);
  
  // Delete associated credit cards
  for (CreditCard creditCard: creditCards) {
   entityManager.remove(creditCard);
  }
 }
 
 /**
  * Edits an existing person
  */
 public void edit(Person person) {
  logger.debug("Editing existing person");
  
  // Retrieve existing person via id
  Person existingPerson = this.get(person.getId());
  
  // Assign updated values to this person
  existingPerson.setFirstName(person.getFirstName());
  existingPerson.setLastName(person.getLastName());
  existingPerson.setMoney(person.getMoney());

  // Save updates
  entityManager.merge(existingPerson);
 }
}

Let's make a side-by-side comparison between the original Hibernate-based implementation and the new JPA-based implementation:

SessionFactory to EntityManager
Similar with the CreditCardService, we have to remove Hibernate's SessionFactory and replace it with JPA's EntityManager.

old code
@Resource(name="sessionFactory")
private SessionFactory sessionFactory;

new code
private EntityManager entityManager;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this. entityManager = entityManager;
    }


Retrieve all persons
old code
/**
  * Retrieves all persons
  * 
  * @return a list of persons
  */
 public List getAll() {
  logger.debug("Retrieving all persons");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("FROM  Person");
  
  // Retrieve all
  return  query.list();
 }

new code
/**
  * Retrieves all persons
  * 
  * @return a list of persons
  */
 public List getAll() {
  logger.debug("Retrieving all persons");
  
  // Create a JPA query
  Query query  = entityManager.createQuery("FROM Person");
  
  // Retrieve all
  return query.getResultList();
 }

Retrieve a single person
old code
/**
  * Retrieves a single person
  */
 public Person get( Integer id ) {
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing person
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+id);
  
  return (Person) query.uniqueResult();
 }

new code
/**
  * Retrieves a single person
  */
 public Person get( Integer id ) {
  
  // Retrieve existing person
  // Create a JPA query
  Query query = entityManager.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+id);
  
  return (Person) query.getSingleResult();
 }

Add a new person
old code
/**
  * Adds a new person
  */
 public void add(Person person) {
  logger.debug("Adding new person");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Persists to db
  session.save(person);
 }

new code
/**
  * Adds a new person
  */
 public void add(Person person) {
  logger.debug("Adding new person");
  
  // Persists to db
  entityManager.persist(person);
 }


Delete an existing person
old code
/**
  * Deletes an existing person
  * @param id the id of the existing person
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing person");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+id);
 
  // Retrieve record
  Person person = (Person) query.uniqueResult();
  
  Set creditCards =person.getCreditCards();
  
  // Delete person
  session.delete(person);
  
  // Delete associated credit cards
  for (CreditCard creditCard: creditCards) {
   session.delete(creditCard);
  }
 }

new code
/**
  * Deletes an existing person
  * @param id the id of the existing person
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing person");
  
  // Create a Hibernate query (HQL)
  Query query = entityManager.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+id);
 
  // Retrieve record
  Person person = (Person) query.getSingleResult();
  
  Set creditCards =person.getCreditCards();
  
  // Delete person
  entityManager.remove(person);
  
  // Delete associated credit cards
  for (CreditCard creditCard: creditCards) {
   entityManager.remove(creditCard);
  }
 }

Edit an existing person
old code
/**
  * Edits an existing person
  */
 public void edit(Person person) {
  logger.debug("Editing existing person");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing person via id
  Person existingPerson = (Person) session.get(Person.class, person.getId());
  
  // Assign updated values to this person
  existingPerson.setFirstName(person.getFirstName());
  existingPerson.setLastName(person.getLastName());
  existingPerson.setMoney(person.getMoney());

  // Save updates
  session.save(existingPerson);
 }

new code
/**
  * Edits an existing person
  */
 public void edit(Person person) {
  logger.debug("Editing existing person");
  
  // Retrieve existing person via id
  Person existingPerson = this.get(person.getId());
  
  // Assign updated values to this person
  existingPerson.setFirstName(person.getFirstName());
  existingPerson.setLastName(person.getLastName());
  existingPerson.setMoney(person.getMoney());

  // Save updates
  entityManager.merge(existingPerson);
 }

Controller Layer

For the controller layer, we just need to fix a mapping bug when adding a new CreditCard. Everything else are the same:

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

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.CreditCard;
import org.krams.tutorial.service.CreditCardService;
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 credit card requests
 * 
 * @author Krams at {@link http://krams915@blogspot.com
 */
@Controller
@RequestMapping("/main/creditcard")
public class CreditCardController {

 protected static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="creditCardService")
 private CreditCardService creditCardService;
 
    /**
     * Retrieves the "Add New Credit Card" page
     */
    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String getAdd(@RequestParam("id") Integer personId, Model model) {
     logger.debug("Received request to show add page");
    
     // Prepare model object
     CreditCard creditCard = new CreditCard();
     
     // Add to model
     model.addAttribute("personId", personId);
     model.addAttribute("creditCardAttribute", creditCard);

     // This will resolve to /WEB-INF/jsp/add-credit-card.jsp
     return "add-credit-card";
 }
 
    /**
     * Adds a new credit card
     */
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String postAdd(@RequestParam("pid") Integer personId, 
              @ModelAttribute("creditCardAttribute") CreditCard creditCard) {
  logger.debug("Received request to add new credit card");
  
  // Delegate to service
  creditCardService.add(personId, creditCard);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }
    
    
    /**
     * Deletes a credit card
     */
    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    public String getDelete(@RequestParam("id") Integer creditCardId) {
     logger.debug("Received request to delete credit card");
    
     
     // Delegate to service
  creditCardService.delete(creditCardId);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }
   
    /**
     * Retrieves the "Edit Existing Credit Card" page
     */
    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    public String getEdit(@RequestParam("pid") Integer personId, 
      @RequestParam("cid") Integer creditCardId, Model model) {
     logger.debug("Received request to show edit page");
     
     // Retrieve credit card by id
     CreditCard existingCreditCard = creditCardService.get(creditCardId);

     // Add to model
     model.addAttribute("personId", personId);
     model.addAttribute("creditCardAttribute", existingCreditCard);

     // This will resolve to /WEB-INF/jsp/edit-credit-card.jsp
     return "edit-credit-card";
 }
 
    /**
     * Edits an existing credit card
     */
    @RequestMapping(value = "/edit", method = RequestMethod.POST)
    public String postEdit(@RequestParam("id") Integer creditCardId, 
              @ModelAttribute("creditCardAttribute") CreditCard creditCard) {
  logger.debug("Received request to add new credit card");
  
  // Assign id
  creditCard.setId(creditCardId);
  
  // Delegate to service
  creditCardService.edit(creditCard);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }
}

Here's the change we did for the CreditCardController:

Add a new credit card
We simply changed the @RequestParam("id") to @RequestParam("pid"):
old code
/**
     * Adds a new credit card
     */
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String postAdd(@RequestParam("id") Integer personId, 
              @ModelAttribute("creditCardAttribute") CreditCard creditCard) {
  logger.debug("Received request to add new credit card");
  
  // Delegate to service
  creditCardService.add(personId, creditCard);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }

new code
/**
     * Adds a new credit card
     */
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String postAdd(@RequestParam("pid") Integer personId, 
              @ModelAttribute("creditCardAttribute") CreditCard creditCard) {
  logger.debug("Received request to add new credit card");
  
  // Delegate to service
  creditCardService.add(personId, creditCard);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }

Since we've modified the @RequestMapping for the Add page, we have to update our add-credit-card.jsp

add-credit-card.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Add New Credit Card</h1>

<c:url var="saveUrl" value="/krams/main/creditcard/add?pid=${personId}" />
<form:form modelAttribute="creditCardAttribute" method="POST" action="${saveUrl}">
 <table>
 
  <tr>
   <td>Person Id:</td>
   <td><input type="text" value="${personId}" disabled="true"/>
  </tr>
  
  <tr>
   <td><form:label path="type">Type:</form:label></td>
   <td><form:input path="type"/></td>
  </tr>

  <tr>
   <td><form:label path="number">Number:</form:label></td>
   <td><form:input path="number"/></td>
  </tr>
 </table>
 
 <input type="submit" value="Save" />
</form:form>

</body>
</html>

Here's the change we did for the add-credit-card.jsp:
old
<c:url var="saveUrl" value="/krams/main/creditcard/add?id=${creditCardAttribute.person.id}" />

new
<c:url var="saveUrl" value="/krams/main/creditcard/add?pid=${creditCardAttribute.person.id}" />

Configuration

We have completed the necessary Java classes. Our next step is to update our configuration files.

Here are the steps:

1. Update our hibernate-context.xml so that it uses JPA. Here's the updated config:
hibernate-context.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:tx="http://www.springframework.org/schema/tx"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd
      ">

 <context:property-placeholder location="/WEB-INF/spring.properties" />
 
 <tx:annotation-driven transaction-manager="transactionManager" /> 
 
 <!-- Declare a datasource that has pooling capabilities-->  
 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close"
    p:driverClass="${app.jdbc.driverClassName}"
    p:jdbcUrl="${app.jdbc.url}"
    p:user="${app.jdbc.username}"
    p:password="${app.jdbc.password}"
    p:acquireIncrement="5"
    p:idleConnectionTestPeriod="60"
    p:maxPoolSize="100"
    p:maxStatements="50"
    p:minPoolSize="10" />

  <!-- Declare a JPA entityManagerFactory-->
 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
  <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml"></property>
  <property name="persistenceUnitName" value="hibernatePersistenceUnit" />
  <property name="dataSource" ref="dataSource"/>
     <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" >
             <property name="showSql" value="true"/>
         </bean>
     </property>
    </bean>

 <!-- Declare a transaction manager-->
 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
     <property name="entityManagerFactory" ref="entityManagerFactory" />
 </bean>  
</beans>

Here's the change we did for the hibernate-context.xml:
Remove the following lines:
<!-- Declare the Hibernate SessionFactory for retrieving Hibernate sessions -->
    <!-- See http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.html -->        
 <!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/SessionFactory.html -->
 <!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/Session.html -->
 <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
     p:dataSource-ref="dataSource"
     p:configLocation="${hibernate.config}"
     p:packagesToScan="org.krams.tutorial"/>

 <!-- Declare a transaction manager-->
 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" 
          p:sessionFactory-ref="sessionFactory" />

And replace it with:
<!-- Declare a JPA entityManagerFactory-->
 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
  <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml"></property>
  <property name="persistenceUnitName" value="hibernatePersistenceUnit" />
  <property name="dataSource" ref="dataSource"/>
     <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" >
             <property name="showSql" value="true"/>
         </bean>
     </property>
    </bean>

 <!-- Declare a transaction manager-->
 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
     <property name="entityManagerFactory" ref="entityManagerFactory" />
 </bean>  
</beans>

2. Delete the hibernate.cfg.xml.

3. Declare a META-INF/persistence.xml

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">

  <persistence-unit name="hibernatePersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <properties>
   <property name="hibernate.hbm2ddl.auto" value="create" />
  </properties>
  </persistence-unit>

</persistence>


Run the Application


Setup the database

Our application uses MySQL as its database. To run the application, make sure to setup the database first.

To create the database, follow these steps.
1. Open phpmyadmin (or any tool you prefer with)
2. Create a new database named mydatabase
3. Run the application to automatically create the database schema.

To populate the database with sample data, import the mydatabase.sql SQL script which is located under the WEB-INF folder of the application.

Access the main application

To access the main application, use the following URL:
http://localhost:8080/spring-hibernate-jpa-one-to-many-default/krams/main/record/list

You should see the following application:


Conclusion

That's it. We've successfully updated our original Hibernate-based application to Spring JPA-based implementation. The advantage of this project is we can now switch JPA providers seamlessly without any direct dependencies with Hibernate.

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

You can download the project as a Maven build. Look for the spring-hibernate-jpa-one-to-many-default.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 JPA: One-To-Many Association: Using Hibernate as the JPA Vendor ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring JPA: Many-To-One Association: Using Hibernate as the JPA Vendor

Introduction

In this tutorial we'll convert our previous project Spring - Hibernate: Many-To-One Association to use Spring JPA. We'll use Hibernate as the JPA provider and show the required changes to convert our project.

Spring MVC 3 and Hibernate Tutorials Series
Spring JPA: Many-To-One Association: Using Hibernate as the JPA Vendor
Spring JPA: One-To-Many Association: Using Hibernate as the JPA Vendor
Spring - Hibernate: Many-To-One Association - Explicitly Specify Join Table, Cascade, and Fetch
Spring - Hibernate: One-To-Many Association - Explicitly Specify Join Table, Cascade, and Fetch
Spring - Hibernate: Many-To-One Association
Spring - Hibernate: One-To-Many Association
Spring MVC 3, Hibernate Annotations, MySQL Integration Tutorial
Spring MVC 3, Hibernate Annotations, HSQLDB Integration Tutorial

What is Spring JPA?
The Spring JPA, available under the org.springframework.orm.jpa package, offers comprehensive support for the Java Persistence API in a similar manner to the integration with Hibernate or JDO, while being aware of the underlying implementation in order to provide additional features.

Source: Spring 3 Reference

What is JPA?
The Java Persistence API simplifies the programming model for entity persistence and adds capabilities that were not in EJB 2.1

The Java Persistence API deals with the way relational data is mapped to Java objects ("persistent entities"), the way that these objects are stored in a relational database so that they can be accessed at a later time, and the continued existence of an entity's state even after the application that uses it ends. In addition to simplifying the entity persistence model, the Java Persistence API standardizes object-relational mapping.

Source: The Java Persistence API - A Simpler Programming Model for Entity Persistence

Changes

Using a diff tool, we can visualize the changes from the original Hibernate project to the JPA project.


Development

Similar with the original project, we'll split our development in three layers: Domain, Service, and Controller.

Domain Layer

In the domain layer, we don't change any existing domain classes. That's because our domain is already using JPA annotation in the beginning.

Service Layer

For the service classes, we will drop all Hibernate references and instead use JPA. Let's start with the CreditCardService:

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

import java.util.List;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.CreditCard;
import org.krams.tutorial.domain.Person;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service for processing Credit Cards
 * 
 * @author Krams at {@link http://krams915@blogspot.com
 */
@Service("creditCardService")
@Transactional
public class CreditCardService {

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

 @Resource(name="personService")
 private PersonService personService;
 
 private EntityManager entityManager;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this. entityManager = entityManager;
    }
 
 /**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll(Integer personId) {
  logger.debug("Retrieving all credit cards");
  
  // Create a JPA query
  Query query = entityManager.createQuery("FROM  CreditCard WHERE person.id=" +personId);
  
  // Retrieve all
  return query.getResultList();
 }
 
 /**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll() {
  logger.debug("Retrieving all credit cards");
  
  // Create a JPA query
  Query query = entityManager.createQuery("FROM  CreditCard");
  
  // Retrieve all
  return query.getResultList();
 }
 
 /**
  * Retrieves a single credit card
  */
 public CreditCard get( Integer id ) {
  
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) entityManager.createQuery("FROM CreditCard p WHERE p.id = :id")
      .setParameter("id", id).getSingleResult();

  // Persists to db
  return creditCard;
 }
 
 /**
  * Adds a new credit card
  */
 public void add(Integer personId, CreditCard creditCard) {
  logger.debug("Adding new credit card");
  
  // Retrieve existing person via id
  Person existingPerson = personService.get(personId);
  
  // Add person to credit card
  creditCard.setPerson(existingPerson);
  
  // Persists to db
  entityManager.merge(creditCard);
 }
 
 /**
  * Deletes an existing credit card
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing credit card");
  
  // Retrieve existing credit card
  CreditCard creditCard = this.get(id);
  
  // Delete 
  entityManager.remove(creditCard);
 }
 
 /**
  * Deletes all credit cards based on the person's id
  */
 public void deleteAll(Integer personId) {
  logger.debug("Deleting existing credit cards based on person's id");
  
  // Create a JPA query
  Query query = entityManager.createQuery("DELETE FROM CreditCard WHERE person.id=" +personId);
  
  // Delete all
  query.executeUpdate();
 }
 
 /**
  * Edits an existing credit card
  */
 public void edit(CreditCard creditCard) {
  logger.debug("Editing existing creditCard");
  
  // Retrieve existing credit card via id
  CreditCard existingCreditCard = this.get(creditCard.getId());
  
  // Assign updated values to this credit card
  existingCreditCard.setNumber(creditCard.getNumber());
  existingCreditCard.setType(creditCard.getType());

  // Save updates
  entityManager.merge(existingCreditCard);
 }
}

Let's make a side-by-side comparison between the original Hibernate-based implementation and the new JPA-based implementation:

SessionFactory to EntityManager
To utilize JPA we have to remove Hibernate's SessionFactory and replace it with JPA's EntityManager. Essentially the EntityManager is equivalent to Hibernate's Session object.

old code
@Resource(name="sessionFactory")
private SessionFactory sessionFactory;

new code
@Resource(name="personService")
private PersonService personService;
 
private EntityManager entityManager;

@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
        this. entityManager = entityManager;
}

Retrieve all credit cards by person's id
old code
/**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll(Integer personId) {
  logger.debug("Retrieving all credit cards");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("FROM  CreditCard WHERE person.id=" +personId);
  
  // Retrieve all
  return  query.list();
 }

new code
/**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll(Integer personId) {
  logger.debug("Retrieving all credit cards");
  
  // Create a JPA query
  Query query = entityManager.createQuery("FROM  CreditCard WHERE person.id=" +personId);
  
  // Retrieve all
  return query.getResultList();
 }

Retrieve all credit cards
old code
/**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll() {
  logger.debug("Retrieving all credit cards");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("FROM  CreditCard");
  
  // Retrieve all
  return  query.list();
 }

new code
/**
  * Retrieves all credit cards
  */
 public List<CreditCard> getAll() {
  logger.debug("Retrieving all credit cards");
  
  // Create a JPA query
  Query query = entityManager.createQuery("FROM  CreditCard");
  
  // Retrieve all
  return query.getResultList();
 }

Add new credit card
old code
/**
  * Adds a new credit card
  */
 public void add(Integer personId, CreditCard creditCard) {
  logger.debug("Adding new credit card");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing person via id
  Person existingPerson = (Person) session.get(Person.class, personId);
  
  // Add person to credit card
  creditCard.setPerson(existingPerson);
  
  // Persists to db
  session.save(creditCard);
 }

new code
/**
  * Adds a new credit card
  */
 public void add(Integer personId, CreditCard creditCard) {
  logger.debug("Adding new credit card");
  
  // Retrieve existing person via id
  Person existingPerson = personService.get(personId);
  
  // Add person to credit card
  creditCard.setPerson(existingPerson);
  
  // Persists to db
  entityManager.merge(creditCard);
 }

Retrieve a single credit card
old code
/**
  * Retrieves a single credit card
  */
 public CreditCard get( Integer id ) {
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) session.get(CreditCard.class, id);
  
  // Persists to db
  return creditCard;
 }

new code
/**
  * Retrieves a single credit card
  */
 public CreditCard get( Integer id ) {
  
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) entityManager.createQuery("FROM CreditCard p WHERE p.id = :id")
      .setParameter("id", id).getSingleResult();

  // Persists to db
  return creditCard;
 }

Delete an existing credit card
old code
/**
  * Deletes an existing credit card
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing credit card");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) session.get(CreditCard.class, id);
  
  // Delete 
  session.delete(creditCard);
 }

new code
/**
  * Deletes an existing credit card
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing credit card");
  
  // Retrieve existing credit card
  CreditCard creditCard = this.get(id);
  
  // Delete 
  entityManager.remove(creditCard);
 }

Delete all credit cards based on the person's id
old code
/**
  * Deletes all credit cards based on the person's id
  */
 public void deleteAll(Integer personId) {
  logger.debug("Deleting existing credit cards based on person's id");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("DELETE FROM CreditCard WHERE person.id=" +personId);
  
  // Delete all
  query.executeUpdate();
 }

new code
/**
  * Deletes all credit cards based on the person's id
  */
 public void deleteAll(Integer personId) {
  logger.debug("Deleting existing credit cards based on person's id");
  
  // Create a JPA query
  Query query = entityManager.createQuery("DELETE FROM CreditCard WHERE person.id=" +personId);
  
  // Delete all
  query.executeUpdate();
 }

Edit an existing credit card
old code
/**
  * Edits an existing credit card
  */
 public void edit(CreditCard creditCard) {
  logger.debug("Editing existing creditCard");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing credit card via id
  CreditCard existingCreditCard = (CreditCard) session.get(CreditCard.class, creditCard.getId());
  
  // Assign updated values to this credit card
  existingCreditCard.setNumber(creditCard.getNumber());
  existingCreditCard.setType(creditCard.getType());

  // Save updates
  session.save(existingCreditCard);
 }

new code
/**
  * Edits an existing credit card
  */
 public void edit(CreditCard creditCard) {
  logger.debug("Editing existing creditCard");
  
  // Retrieve existing credit card via id
  CreditCard existingCreditCard = this.get(creditCard.getId());
  
  // Assign updated values to this credit card
  existingCreditCard.setNumber(creditCard.getNumber());
  existingCreditCard.setType(creditCard.getType());

  // Save updates
  entityManager.merge(existingCreditCard);
 }

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

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service for processing Persons
 * 
 * @author Krams at {@link http://krams915@blogspot.com
 */
@Service("personService")
@Transactional
public class PersonService {

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

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this. entityManager = entityManager;
    }
    
 /**
  * Retrieves all persons
  * 
  * @return a list of persons
  */
 public List<Person> getAll() {
  logger.debug("Retrieving all persons");
  
  // Create a JPA query
  Query query  = entityManager.createQuery("FROM Person");
  
  // Retrieve all
  return query.getResultList();
 }
 
 /**
  * Retrieves a single person
  */
 public Person get( Integer id ) {
  
  // Retrieve existing person
  Person person = (Person) entityManager.createQuery("FROM Person p where p.id = :id")
         .setParameter("id", id).getSingleResult();
  
  return person;
 }
 
 /**
  * Adds a new person
  */
 public void add(Person person) {
  logger.debug("Adding new person");
  
  // Persists to db
  entityManager.persist(person);
 }
 
 /**
  * Deletes an existing person
  * @param id the id of the existing person
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing person");
  
  // Retrieve existing person
  Person person = this.get(id);
  
  // Delete 
  entityManager.remove(person);
 }
 
 /**
  * Edits an existing person
  */
 public void edit(Person person) {
  logger.debug("Editing existing person");
  
  // Retrieve existing person via id
  Person existingPerson = this.get(person.getId());
  
  // Assign updated values to this person
  existingPerson.setFirstName(person.getFirstName());
  existingPerson.setLastName(person.getLastName());
  existingPerson.setMoney(person.getMoney());

  // Save updates
  entityManager.merge(person);
 }
}

Let's make a side-by-side comparison between the original Hibernate-based implementation and the new JPA-based implementation:

SessionFactory to EntityManager
Similar with the CreditCardService, we have to remove Hibernate's SessionFactory and replace it with JPA's EntityManager.

old code
@Resource(name="sessionFactory")
private SessionFactory sessionFactory;

new code
private EntityManager entityManager;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this. entityManager = entityManager;
    }


Retrieve all persons
old code
/**
  * Retrieves all persons
  * 
  * @return a list of persons
  */
 public List getAll() {
  logger.debug("Retrieving all persons");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Create a Hibernate query (HQL)
  Query query = session.createQuery("FROM  Person");
  
  // Retrieve all
  return  query.list();
 }

new code
/**
  * Retrieves all persons
  * 
  * @return a list of persons
  */
 public List getAll() {
  logger.debug("Retrieving all persons");
  
  // Create a JPA query
  Query query  = entityManager.createQuery("FROM Person");
  
  // Retrieve all
  return query.getResultList();
 }

Retrieve a single person
old code
/**
  * Retrieves a single person
  */
 public Person get( Integer id ) {
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing person
  Person person = (Person) session.get(Person.class, id);
  
  return person;
 }

new code
/**
  * Retrieves a single person
  */
 public Person get( Integer id ) {
  
  // Retrieve existing person
  Person person = (Person) entityManager.createQuery("FROM Person p where p.id = :id")
         .setParameter("id", id).getSingleResult();
  
  return person;
 }

Add a new person
old code
/**
  * Adds a new person
  */
 public void add(Person person) {
  logger.debug("Adding new person");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Persists to db
  session.save(person);
 }

new code
/**
  * Adds a new person
  */
 public void add(Person person) {
  logger.debug("Adding new person");
  
  // Persists to db
  entityManager.persist(person);
 }


Delete an existing person
old code
/**
  * Deletes an existing person
  * @param id the id of the existing person
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing person");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing person
  Person person = (Person) session.get(Person.class, id);
  
  // Delete 
  session.delete(person);
 }

new code
/**
  * Deletes an existing person
  * @param id the id of the existing person
  */
 public void delete(Integer id) {
  logger.debug("Deleting existing person");
  
  // Retrieve existing person
  Person person = this.get(id);
  
  // Delete 
  entityManager.remove(person);
 }

Edit an existing person
old code
/**
  * Edits an existing person
  */
 public void edit(Person person) {
  logger.debug("Editing existing person");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing person via id
  Person existingPerson = (Person) session.get(Person.class, person.getId());
  
  // Assign updated values to this person
  existingPerson.setFirstName(person.getFirstName());
  existingPerson.setLastName(person.getLastName());
  existingPerson.setMoney(person.getMoney());

  // Save updates
  session.save(existingPerson);
 }

new code
/**
  * Edits an existing person
  */
 public void edit(Person person) {
  logger.debug("Editing existing person");
  
  // Retrieve existing person via id
  Person existingPerson = this.get(person.getId());
  
  // Assign updated values to this person
  existingPerson.setFirstName(person.getFirstName());
  existingPerson.setLastName(person.getLastName());
  existingPerson.setMoney(person.getMoney());

  // Save updates
  entityManager.merge(person);
 }

Controller Layer

For the controller layer, we just need to fix a mapping bug when adding a new CreditCard. Everything else are the same:

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

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.CreditCard;
import org.krams.tutorial.service.CreditCardService;
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/creditcard")
public class CreditCardController {

 protected static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="personService")
 private PersonService personService;
 
 @Resource(name="creditCardService")
 private CreditCardService creditCardService;
 
    /**
     * Retrieves the "Add New Credit Card" page
     */
    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String getAdd(@RequestParam("id") Integer personId, Model model) {
     logger.debug("Received request to show add page");
    
     // Prepare model object
     CreditCard creditCard = new CreditCard();
     creditCard.setPerson(personService.get(personId));
     
     // Add to model
     model.addAttribute("creditCardAttribute", creditCard);

     // This will resolve to /WEB-INF/jsp/add-credit-card.jsp
     return "add-credit-card";
 }
 
    /**
     * Adds a new credit card
     */
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String postAdd(@RequestParam("pid") Integer personId, 
              @ModelAttribute("creditCardAttribute") CreditCard creditCard) {
  logger.debug("Received request to add new credit card");
  
  // Delegate to service
  creditCardService.add(personId, creditCard);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }
    
    
    /**
     * Deletes a credit card
     */
    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    public String getDelete(@RequestParam("id") Integer creditCardId) {
     logger.debug("Received request to delete credit card");
    
     // Delegate to service
  creditCardService.delete(creditCardId);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }
   
    /**
     * Retrieves the "Edit Existing Credit Card" page
     */
    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    public String getEdit(@RequestParam("id") Integer creditCardId, Model model) {
     logger.debug("Received request to show edit page");
     
     // Retrieve credit card by id
     CreditCard existingCreditCard = creditCardService.get(creditCardId);

     // Add to model
     model.addAttribute("creditCardAttribute", existingCreditCard);

     // This will resolve to /WEB-INF/jsp/edit-credit-card.jsp
     return "edit-credit-card";
 }
 
    /**
     * Edits an existing credit card
     */
    @RequestMapping(value = "/edit", method = RequestMethod.POST)
    public String postEdit(@RequestParam("id") Integer creditCardId, 
              @ModelAttribute("creditCardAttribute") CreditCard creditCard) {
  logger.debug("Received request to add new credit card");
  
  // Assign id
  creditCard.setId(creditCardId);
  
  // Delegate to service
  creditCardService.edit(creditCard);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }
}

Here's the change we did for the CreditCardController:

Add a new credit card
We simply changed the @RequestParam("id") to @RequestParam("pid"):
old code
/**
     * Adds a new credit card
     */
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String postAdd(@RequestParam("id") Integer personId, 
              @ModelAttribute("creditCardAttribute") CreditCard creditCard) {
  logger.debug("Received request to add new credit card");
  
  // Delegate to service
  creditCardService.add(personId, creditCard);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }

new code
/**
     * Adds a new credit card
     */
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String postAdd(@RequestParam("pid") Integer personId, 
              @ModelAttribute("creditCardAttribute") CreditCard creditCard) {
  logger.debug("Received request to add new credit card");
  
  // Delegate to service
  creditCardService.add(personId, creditCard);

  // Redirect to url
  return "redirect:/krams/main/record/list";
 }

Since we've modified the @RequestMapping for the Add page, we have to update our add-credit-card.jsp

add-credit-card.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Add New Credit Card</h1>

<c:url var="saveUrl" value="/krams/main/creditcard/add?pid=${creditCardAttribute.person.id}" />
<form:form modelAttribute="creditCardAttribute" method="POST" action="${saveUrl}">
 <table>
 
  <tr>
   <td><form:label path="person.id">Person Id:</form:label></td>
   <td><form:input path="person.id" disabled="true"/></td>
  </tr>
  
  <tr>
   <td><form:label path="type">Type:</form:label></td>
   <td><form:input path="type"/></td>
  </tr>

  <tr>
   <td><form:label path="number">Number:</form:label></td>
   <td><form:input path="number"/></td>
  </tr>
 </table>
 
 <input type="submit" value="Save" />
</form:form>

</body>
</html>

Here's the change we did for the add-credit-card.jsp:
old
<c:url var="saveUrl" value="/krams/main/creditcard/add?id=${creditCardAttribute.person.id}" />

new
<c:url var="saveUrl" value="/krams/main/creditcard/add?pid=${creditCardAttribute.person.id}" />


Configuration

We have completed the necessary Java classes. Our next step is to update our configuration files.

Here are the steps:

1. Update our hibernate-context.xml so that it uses JPA. Here's the updated config:
hibernate-context.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:tx="http://www.springframework.org/schema/tx"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd
      ">

 <context:property-placeholder location="/WEB-INF/spring.properties" />
 
 <tx:annotation-driven transaction-manager="transactionManager" /> 
 
 <!-- Declare a datasource that has pooling capabilities-->  
 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close"
    p:driverClass="${app.jdbc.driverClassName}"
    p:jdbcUrl="${app.jdbc.url}"
    p:user="${app.jdbc.username}"
    p:password="${app.jdbc.password}"
    p:acquireIncrement="5"
    p:idleConnectionTestPeriod="60"
    p:maxPoolSize="100"
    p:maxStatements="50"
    p:minPoolSize="10" />

  <!-- Declare a JPA entityManagerFactory-->
 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
  <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml"></property>
  <property name="persistenceUnitName" value="hibernatePersistenceUnit" />
  <property name="dataSource" ref="dataSource"/>
     <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" >
             <property name="showSql" value="true"/>
         </bean>
     </property>
    </bean>

 <!-- Declare a transaction manager-->
 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
     <property name="entityManagerFactory" ref="entityManagerFactory" />
 </bean>  
</beans>


Here's the change we did for the hibernate-context.xml:
Remove the following lines:
<!-- Declare the Hibernate SessionFactory for retrieving Hibernate sessions -->
    <!-- See http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.html -->        
 <!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/SessionFactory.html -->
 <!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/Session.html -->
 <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
     p:dataSource-ref="dataSource"
     p:configLocation="${hibernate.config}"
     p:packagesToScan="org.krams.tutorial"/>

 <!-- Declare a transaction manager-->
 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" 
          p:sessionFactory-ref="sessionFactory" />

And replace it with:
<!-- Declare a JPA entityManagerFactory-->
 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
  <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml"></property>
  <property name="persistenceUnitName" value="hibernatePersistenceUnit" />
  <property name="dataSource" ref="dataSource"/>
     <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" >
             <property name="showSql" value="true"/>
         </bean>
     </property>
    </bean>

 <!-- Declare a transaction manager-->
 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
     <property name="entityManagerFactory" ref="entityManagerFactory" />
 </bean>  
</beans>

2. Delete the hibernate.cfg.xml.

3. Declare a META-INF/persistence.xml

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">

  <persistence-unit name="hibernatePersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <properties>
   <property name="hibernate.hbm2ddl.auto" value="create" />
  </properties>
  </persistence-unit>

</persistence>

Run the Application

Run the Application


Setup the database

Our application uses MySQL as its database. To run the application, make sure to setup the database first.

To create the database, follow these steps.
1. Open phpmyadmin (or any tool you prefer with)
2. Create a new database named mydatabase
3. Run the application to automatically create the database schema.

To populate the database with sample data, import the mydatabase.sql SQL script which is located under the WEB-INF folder of the application.

Access the main application

To access the main application, use the following URL:
http://localhost:8080/spring-hibernate-jpa-many-to-one-default/krams/main/record/list

You should see the following application:


Conclusion

That's it. We've successfully updated our original Hibernate-based application to Spring JPA-based implementation. The advantage of this project is we can now switch JPA providers seamlessly without any direct dependencies with Hibernate.

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

You can download the project as a Maven build. Look for the spring-hibernate-jpa-many-to-one-default.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 JPA: Many-To-One Association: Using Hibernate as the JPA Vendor ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share