Friday, February 19, 2010

JSON RESTfull Service Using Jersey with Hibernate Persistence on Glassfish

Tips:
  • to setup Hibernate to use a datasource defined under Glassfish, see my other post.
  • usefull Jersey annotations:
    • for the resource class: @Path("/person/")
    • for a method: @GET @Path("{personId}/") @Produces("application/json"). Then, you can use public Person getUser(@PathParam("personId") int personId) for method declaration.
    • add @Consumes("application/json") with public Person post(Person person) method declaration. Jersey takes care of unmarshaling the Json into Person object.
  • to return an error code from the restfull webservice, throw a new WebApplicationException(Response.Status.NOT_FOUND), for example. This one would return 404 error. No Need to declare the jersey handler method as "throws ...".
  • to unit test the service, use com.sun.jersey.api.client.Client:
    Person result = Client.create().resource("http://localhost:8080/myapp/person")
       .type("application/json")
       .post(Person.class,new Person("Jon Smith",133));
    Assert.assertEquals("Jon Smith", result.getName()); 
  • To pass an object retrieved via Hibernate as a response from a webservice, it needs to be serializable. But objects retrieved this way have additional members and so they need to be converted to a pure entity object (Person, in my case). I have used the Dozer library for this purpose (see the code below).
  • The entity classes (Person and PersonCollection, in my example)need to be annotated with @XmlRootElement.

Code:
Complete web service implementation class code (in RESTfull, call a resource):
package olcc.jersey.service;

import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import olcc.entity.HibernateUtil;
import olcc.entity.Person;
import olcc.entity.PersonCollection;
import org.dozer.DozerBeanMapper;
import org.dozer.Mapper;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;

@Path("/person/")
public class PersonResource {
 
 @GET @Path("{personId}/") @Produces("application/json")
 public Person getPerson(@PathParam("personId") int personId) {
  Session session = HibernateUtil.getSessionFactory().getCurrentSession();
  Transaction tx = null;
  Person personClean;
  
  try{
   tx = session.beginTransaction();
   Person person = (Person) session.load(Person.class,personId);
   
   Mapper mapper = new DozerBeanMapper();
   personClean = mapper.map(person,Person.class);

   session.getTransaction().commit();

  } catch( Exception ex) {
   if( tx != null ) tx.rollback();
         throw new WebApplicationException(Response.Status.NOT_FOUND);
  }
  return personClean;
 }
 
 @GET @Produces("application/json")
 public PersonCollection get() {
  Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();
  List<person> result = session.createQuery("from Person").list();

  Mapper mapper = new DozerBeanMapper();
  PersonCollection persons = new PersonCollection();
        
  for( Person person : result ) {
    persons.add(mapper.map(person,Person.class));
  }
  session.getTransaction().commit();
        
  return persons;
 }
 
 @POST @Consumes("application/json")
 @Produces("application/json")
 public Person post(Person person) {
  Session session = HibernateUtil.getSessionFactory().getCurrentSession();
  session.beginTransaction();
  
  session.save(person);
  
  session.getTransaction().commit();
  return person;
 }

 @GET @Path("delete/{personId}/") @Produces("application/json")
 public Person deleteOne(@PathParam("personId") int personId) {
  Session session = HibernateUtil.getSessionFactory().getCurrentSession();
  session.beginTransaction();
  Transaction tx = null;
        Person personClean;
  
  try{
   tx = session.beginTransaction();
   Person person = (Person) session.load(Person.class,personId);
   session.delete(person);
   
   Mapper mapper = new DozerBeanMapper();
   personClean = mapper.map(person,Person.class);

   session.getTransaction().commit();

  } catch( Exception ex) {
   if( tx != null ) tx.rollback();
         throw new WebApplicationException(Response.Status.NOT_FOUND);
  }

  return personClean;
 }
}

Open Questions:
  • I'm not sure where the Hibernate session should be closed. I don't think this can be done at the end of each handler method.
  • How to use JTA for transaction management instead of direct JDBC transaction management?
  • Most of the Hibernate-specific code should go into a DAO, with most of it into a generic DAO. But the recommended by Hibernate documentation generic DAO is defined in terms of state-oriented data access API (makePersistent() and makeTransient() methods), which are less intuitive for me. What is the advantage of using them and how to use them from CRUD operations?

1 comment:

Anirudh Bhatnagar said...

You could have used jersey-json directly to map JSON/XML to your POJOs.
See this :
http://jersey.java.net/nonav/documentation/latest/json.html