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

Sunday, March 20, 2011

Spring - Hibernate: Many-To-One Association - Explicitly Specify Join Table, Cascade, and Fetch

Introduction

In this tutorial we'll expand our previous project Spring - Hibernate: Many-To-One Association by explicitly specifying the join table and join columns. We will also declare the Cascade type and Fetch strategy for the @ManyToOne annotation.

Spring MVC 3 and Hibernate Tutorials Series
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

Changes

Using a diff tool, we can easily visualize the changes from the previous project and our updated project. Notice only CreditCard.java has changed. Everything else have remained the same.


Development

Similar with the original project, we'll split our development in three layers: Domain, Service, and Controller. But since only the domain has changed, we will only discuss the Domain layer. For the remaining layers, please see the original tutorial: Spring - Hibernate: Many-To-One Association

Domain Layer

For the domain layer, only CreditCard class has changed by adding extra properties to the @ManyToOne annotation.

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

import java.io.Serializable;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

/**
 * Represents a credit card entity
 * 
 * @author Krams at {@link http://krams915@blogspot.com
 */
@Entity
@Table(name = "CREDIT_CARD")
public class CreditCard implements Serializable {

 private static final long serialVersionUID = 5924361831551833717L;

 @Id
 @Column(name = "ID")
 @GeneratedValue
 private Integer id;
 
 @Column(name = "TYPE")
 private String type;
 
 @Column(name = "NUMBER")
 private String number;

 @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch=FetchType.EAGER )
    @JoinTable(name="CREDIT_PERSON",
        joinColumns = @JoinColumn(name="CREDIT_ID"),
        inverseJoinColumns = @JoinColumn(name="PERSON_ID")
    )
 private Person person;
 
 public Integer getId() {
  return id;
 }

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

 public String getType() {
  return type;
 }

 public void setType(String type) {
  this.type = type;
 }

 public String getNumber() {
  return number;
 }

 public void setNumber(String number) {
  this.number = number;
 }

 public Person getPerson() {
  return person;
 }

 public void setPerson(Person person) {
  this.person = person;
 }
 
 
}

Pay extra attention to the @ManyToOne annotation:

@Entity
@Table(name = "CREDIT_CARD")
public class CreditCard implements Serializable {
 ...
 @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch=FetchType.EAGER )
    @JoinTable(name="CREDIT_PERSON",
        joinColumns = @JoinColumn(name="CREDIT_ID"),
        inverseJoinColumns = @JoinColumn(name="PERSON_ID")
    )
 private Person person;
 ...
}

We have explicitly declared the join table name and the join column names.

Using phpymyadmin's database designer, the Hibernate auto-generated relationship between Person and CreditCard looks as follows


Again using phpymyadmin, the auto-generated tables looks as follows:

By specifying the @JoinTable, we forced Hibernate to create a third table that contains the relationship between Person and CreditCard. By default, @ManyToOne creates two tables.

Notice we have also specified the Cascade and Fetch strategies as well.

What does CascadeType.PERSIST and CascadeType.MERGE do?
CascadeType.PERSIST: cascades the persist (create) operation to associated entities persist() is called or if the entity is managed
CascadeType.MERGE: cascades the merge operation to associated entities if merge() is called or if the entity is managed
CascadeType.REMOVE: cascades the remove operation to associated entities if delete() is called
CascadeType.REFRESH: cascades the refresh operation to associated entities if refresh() is called
CascadeType.DETACH: cascades the detach operation to associated entities if detach() is called

CascadeType.ALL: all of the above

Source: Hibernate Annotations Reference Guide

What does FetchType.EAGER do?
You have the ability to either eagerly or lazily fetch associated entities. The fetch parameter can be set to FetchType.LAZY or FetchType.EAGER. EAGER will try to use an outer join select to retrieve the associated object, while LAZY will only trigger a select when the associated object is accessed for the first time. @OneToMany and @ManyToMany associations are defaulted to LAZY and @OneToOne and @ManyToOne are defaulted to EAGER.

Source: Hibernate Annotations Reference Guide

Notice @ManyToOne are defaulted to EAGER. That means it's not necessary to declare the fetch=FetchType.EAGER inside the @ManyToOne. But we declared it anyway for the purpose of demonstrating its feature in this tutorial.

Configuration

We've completed the changes necessary for the project. The next step is to declare the configuration files. But since nothing has changed in the configuration files, we won't be posting them again here. Please see the original tutorial Spring - Hibernate: Many-To-One Association if you want to see the files.

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-many-to-one-jointable/krams/main/record/list

You should see the following application:


Conclusion

That's it. We've successfully expanded our previous project Spring - Hibernate: Many-To-One Association by explicitly specifying the join table and join columns. We also declared the Cascade and Fetch strategies explicitly.

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-many-to-one-jointable.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 - Hibernate: Many-To-One Association - Explicitly Specify Join Table, Cascade, and Fetch ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring - Hibernate: One-To-Many Association - Explicitly Specify Join Table, Cascade, and Fetch

Introduction

In this tutorial we'll expand our previous project Spring - Hibernate: One-To-Many Association by explicitly specifying the join table and join columns. We will also declare the Cascade type and Fetch strategy for the @OneToMany annotation.

Spring MVC 3 and Hibernate Tutorials Series
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

Changes

Using a diff tool, we can easily visualize the changes from the previous project and our updated project. Notice only Person.java, CreditCardService.java, and PersonService.java have changed. Everything else have remained the same.


Development

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

Domain Layer

For the domain layer, only Person class has changed by adding extra properties to the @OneToMany annotation.

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

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;

/**
 * Represents a person entity
 * 
 *  @author Krams at {@link http://krams915@blogspot.com}
 */
@Entity
@Table(name = "PERSON")
public class Person implements Serializable {

 private static final long serialVersionUID = -5527566248002296042L;
 
 @Id
 @Column(name = "ID")
 @GeneratedValue
 private Integer id;
 
 @Column(name = "FIRST_NAME")
 private String firstName;
 
 @Column(name = "LAST_NAME")
 private String lastName;
 
 @Column(name = "MONEY")
 private Double money;

 @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
 @JoinTable(
            name="PersonCreditCards",
            joinColumns = @JoinColumn( name="PERSON_ID"),
            inverseJoinColumns = @JoinColumn( name="CREDIT_ID")
    )
 private Set<CreditCard> creditCards;
 
 public Integer getId() {
  return id;
 }

 public void setId(Integer 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;
 }

 public Set<CreditCard> getCreditCards() {
  return creditCards;
 }

 public void setCreditCards(Set<CreditCard> creditCards) {
  this.creditCards = creditCards;
 }
}

Pay extra attention to the @OneToMany annotation:

@Entity
@Table(name = "PERSON")
public class Person implements Serializable {
 ...
 @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
 @JoinTable(
            name="PersonCreditCards",
            joinColumns = @JoinColumn( name="PERSON_ID"),
            inverseJoinColumns = @JoinColumn( name="CREDIT_ID")
    )
 private Set<CreditCard> creditCards;
 ...
}
We have explicitly declared the join table name and the join column names.

Using phpymyadmin's database designer, the Hibernate auto-generated relationship between Person and CreditCard looks as follows:


Again using phpymyadmin, the auto-generated tables looks as follows:


Notice we have also specified the Cascade and Fetch strategies as well.

What does CascadeType.ALL do?
CascadeType.PERSIST: cascades the persist (create) operation to associated entities persist() is called or if the entity is managed
CascadeType.MERGE: cascades the merge operation to associated entities if merge() is called or if the entity is managed
CascadeType.REMOVE: cascades the remove operation to associated entities if delete() is called
CascadeType.REFRESH: cascades the refresh operation to associated entities if refresh() is called
CascadeType.DETACH: cascades the detach operation to associated entities if detach() is called

CascadeType.ALL: all of the above

Source: Hibernate Annotations Reference Guide

What does FetchType.EAGER do?
You have the ability to either eagerly or lazily fetch associated entities. The fetch parameter can be set to FetchType.LAZY or FetchType.EAGER. EAGER will try to use an outer join select to retrieve the associated object, while LAZY will only trigger a select when the associated object is accessed for the first time. @OneToMany and @ManyToMany associations are defaulted to LAZY and @OneToOne and @ManyToOne are defaulted to EAGER.

Source: Hibernate Annotations Reference Guide

Notice @OneToMany are defaulted to LAZY.

Service Layer

Specifying the Cascade and Fetch strategies with @OneToMany annotation requires us to change our service classes to take advantage of those settings.

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

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

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
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");
 
 @Resource(name="sessionFactory")
 private SessionFactory sessionFactory;
 
 /**
  * Retrieves all persons
  * 
  * @return a list of persons
  */
 public List<Person> 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();
 }
 
 /**
  * Retrieves a single person
  */
 public Person get( Integer id ) {
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing person
  return (Person) session.get(Person.class, id);
 }
 
 /**
  * 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);
 }
 
 /**
  * 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 record
  Person person = (Person) session.get(Person.class, id);
  
  // Delete person
  session.delete(person);
 }
 
 /**
  * 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);
 }
}

What exactly has changed from the original implementation to our new code? Here are the changes:

Get Method
old code
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
public Person get( Integer id ) {
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
  
  // Retrieve existing person
  return (Person) session.get(Person.class, id);
 }

Delete Method
old code
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<:CreditCard> creditCards =person.getCreditCards();
  
  // Delete person
  session.delete(person);
  
  // Delete associated credit cards
  for (CreditCard creditCard: creditCards) {
   session.delete(creditCard);
  }
 }

new code
public void delete(Integer id) {
  logger.debug("Deleting existing person");
  
  // Retrieve session from Hibernate
  Session session = sessionFactory.getCurrentSession();
 
  // Retrieve record
  Person person = (Person) session.get(Person.class, id);
  
  // Delete person
  session.delete(person);
 }

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

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
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="sessionFactory")
 private SessionFactory sessionFactory;
 
 /**
  * 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 WHERE p.id="+personId);
  
  Person person = (Person) query.uniqueResult();
  
  // Retrieve all
  return  new ArrayList<CreditCard>(person.getCreditCards());
 }
 
 /**
  * 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();
 }
 
 /**
  * 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;
 }
 
 /**
  * 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);
 }
 
 /**
  * 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 PersonCreditCards " +
       "WHERE CREDIT_ID="+id);
     
     query.executeUpdate();
     
  // Retrieve existing credit card
  CreditCard creditCard = (CreditCard) session.get(CreditCard.class, id);
  
  // Delete 
  session.delete(creditCard);
 }
 
 /**
  * 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);
 }
}

What exactly has changed from the original implementation to our new code? Here are the changes:

Fetch Query
Since we have specified a Fetch strategy, we can now remove the LEFT JOIN FETCH from the HQL query.

old code
Query query = session.createQuery("FROM Person as p LEFT JOIN FETCH  p.creditCards WHERE p.id="+personId);

new code
Query query = session.createQuery("FROM Person as p WHERE p.id="+personId);

When we specified FetchType.EAGER we're letting Hibernate load the associated records automatically:
@OneToMany(fetch=FetchType.EAGER)

Delete Query
Since we have explicitly specified the join table name and join column names, we have to adjust our SQL query.

old code
Query query = session.createSQLQuery("DELETE FROM PERSON_CREDIT_CARD " +
       "WHERE creditCards_ID="+id);

new code
Query query = session.createSQLQuery("DELETE FROM PersonCreditCards " +
       "WHERE CREDIT_ID="+id);

Configuration

We've completed the changes necessary for the project. The next step is to declare the configuration files. But since nothing has changed in the configuration files, we won't be posting them again here. Please see the original tutorial Spring - Hibernate: One-To-Many Association if you want to see the files.

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-one-to-many-jointable/krams/main/record/list

You should see the following application:


Conclusion

That's it. We've successfully expanded our previous project Spring - Hibernate: One-To-Many Association by explicitly specifying the join table and join columns. We also declared the Cascade and Fetch strategies explicitly.

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-one-to-many-jointable.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 - Hibernate: One-To-Many Association - Explicitly Specify Join Table, Cascade, and Fetch ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share