Wednesday, July 10, 2013

JSF2, Spring and Hibernate/JPA integration

In a previous article we saw how to integrate Spring with JSF2  and how to define custom JSF2 scopes in Spring. In this part, we will add support of JPA (through Hibernate) to our application.
As you should know Hibernate is an open source Java persistence framework that implements JPA.  If you are not familiar with Hibernate and JPA please refer to documentation for more informations.
In this article , we will create a simple data model consisting of two tables: CV and USER_TABLE. For simplicity user_table will refer to cv table via a foreign key.
Before we start, please add following dependencies to your POM file:



 org.hibernate
 hibernate-core
 4.3.0.Beta2


 org.hibernate
 hibernate-entitymanager
 4.3.0.Beta2

 

 antlr
 antlr
 20030911

1) Tables creation

Notice that I am using MySQL here.
Table CV
CREATE TABLE `cv` (
 `cv_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
 `objective` TEXT NULL,
 `content_url` VARCHAR(255) NULL DEFAULT NULL,
 `title` VARCHAR(255) NOT NULL,
 `document_name` VARCHAR(255) NOT NULL,
 PRIMARY KEY (`cv_id`)
)
ENGINE=InnoDB;
Table USER_TABLE
CREATE TABLE `user_table` (
 `user_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
 `firstname` VARCHAR(50) NULL DEFAULT NULL,
 `lastname` VARCHAR(50) NULL DEFAULT NULL,
 `email` VARCHAR(255) NULL DEFAULT NULL,
 `login` VARCHAR(30) NOT NULL,
 `password` VARCHAR(30) NOT NULL,
 `address` VARCHAR(100) NULL DEFAULT NULL,
 `role` VARCHAR(30) NULL DEFAULT 'USER',
 `cv_id` BIGINT(20) NULL DEFAULT NULL,
 PRIMARY KEY (`user_id`),
 INDEX `FK_20732bf2152840f1b10160c7a1f` (`cv_id`),
 CONSTRAINT `FK_20732bf2152840f1b10160c7a1f` FOREIGN KEY (`cv_id`) REFERENCES `cv` (`cv_id`)
)
ENGINE=InnoDB;

2) Domain classes

After creating tables, we should create corresponding classes to be mapped via JPA:


package com.raissi.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="cv")
public class Resume implements Serializable{
 private static final long serialVersionUID = -6450539497238528693L;
 
 @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cv_id")
 private Long id;
 @Column(name = "title")
 private String title;
 @Column(name = "objective", nullable = false)
 private String description;
 @Column(name = "content_url", nullable = false)
 private String contentUrl;
 @Column(name = "document_name", nullable = false)
 private String documentName;
 
 public Resume() {
  super();
 }
        //Getters and setters...
}
package com.raissi.domain;

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

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.raissi.domain.Resume;

@Entity
@Table(name="user_table")
public class User implements Serializable{

    private static final long serialVersionUID = 3571343460175211199L;
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //Generator user_seq
    @Column(name = "user_id")
    private Long userId;
    @Column(name = "login", nullable = false)
    private String login;
    @Column(name = "password", nullable = false)
    private String password;
    @Column(name = "firstname", nullable = false)
    private String firstName;
    @Column(name = "lastname", nullable = false)
    private String lastName;
    @Column(name = "address")
    private String address;
    @Column(name = "email", nullable = false)
    private String email;
    @Column(name = "role", nullable = false)
    private String role = "USER";
 
    @ManyToOne( cascade = {CascadeType.MERGE})
    @JoinColumn(name="cv_id")
    private Resume resume;
 
    public User() {
 super();
    }
 
    //Getters and setters here...
}
Now that we have our our tables and corresponding tables in place, let's create a Data Access Objects layer.

3) DAO layer

In this layer, I will use for each persistent object, a DAO interface and an implementation for the it. This is the recommended way, especially when working with IoC design pattern. 
package com.raissi.dao;

public interface BaseDao {
    public void save(Object o);
    public void update(Object o);
    public void delete(Object o);
}
package com.raissi.dao;

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

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

@Repository
public class BaseDaoImpl implements BaseDao{
 
    protected EntityManager entityManager;

    @Override
    public void save(Object o) {
 entityManager.persist(o);
    }

    @Override
    public void update(Object o) {
        entityManager.merge(o);
    }

    @Override
    public void delete(Object o) {
 entityManager.remove(o);
    }
 
    @PersistenceContext
    void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
}
Here we defined a base CRUD operations class. It will be inherited by all other DAO classes.
package com.raissi.dao;

import com.raissi.domain.Resume;

public interface ResumeDao extends BaseDao{
 public Resume getResumeByUser(Long userId);
}
package com.raissi.dao;

import javax.inject.Named;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import com.raissi.domain.Resume;

@Named("resumeDao")
public class ResumeDaoImpl  extends BaseDaoImpl implements ResumeDao{

 @Override
 public Resume getResumeByUser(Long userId) {
  try {
  Query query = entityManager
  .createQuery("from Resume res where res.id = " +
    "(select user.resume.id from User user where user.userId = :userId)").setParameter("userId", userId);
  return (Resume) query.getSingleResult();
  }catch (NoResultException ex) {
   return null;
  }
 }
}
package com.raissi.dao;

import java.util.List;
import com.raissi.domain.User;

public interface UserDao extends BaseDao{
 public User findUserByLoginOrEmail(String loginOrEmail);
}
package com.raissi.dao;

import java.util.List;

import javax.inject.Named;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import com.raissi.domain.User;

@Named("userDao")
public class UserDaoImpl extends BaseDaoImpl implements UserDao{
@Override
 public User findUserByLoginOrEmail(String loginOrEmail) {
  try {
   Query query = entityManager
     .createQuery("from User user where user.login=:login or user.email=:email")
     .setParameter("login", loginOrEmail).setParameter("email", loginOrEmail);
   return (User) query.getSingleResult();
  } catch (NoResultException ex) {
   return null;
  }
 }
}
Now that we have our DAO layer prepared (notice the entityManager injected in the BaseDaoImpl class), we need to inform Hibernate where to find our tables.

3) JPA configuration with Spring

If you followed my last article about Spring Security, you would have seen that we configured a datasource via Spring:







 
 
 
  
   
   
   
  
        

The last defined bean: entityManagerFactory is a factory that will build and return the entityManager we injected in our DAO classes via @PersistenceContext annotation.
Now you may notice that we are referring to a "cvtheque" value as a "persistenceUnitName" in the entityManagerFactory bean. In JPA, you must have a a file named "persistence.xml" under a folder named META-INF in you classpath (I created the folder META-INF under src/main/resources). This file must define the persistence unit:


    
      
         
         
         
      
   

In this persistence config, you may pass Hibernate parameters like show_sql. By now you should be able to use your DAO layer and communicate with you DB tables.

4) Transactions

Now, if you want to save a new User object into your database using this code in a DAO class, let's say a generic class responsible for basic CRUD operations of all objects:

public void save(Object o) {
     entityManager.persist(o);
}
And in service class, UserServiceImpl you call this method:
public void saveUser(User user){
 userDao.save(user);
}
Then when calling the method UserService#saveUser from a managed bean, you may notice that everything works fine, except one thing, data is not persisted into DB (user not saved). Well this is pretty normal. We don't have any transaction opened to commit data into DB. In fact, "No communication with the database can occur outside of a database transaction" (read more here). So what to do ?
Well, you need to open transaction before every data change aimed to be done on DB, do logic and then commit transaction.
Without Spring: if you are not using Spring (especially, Spring Transactions) or another Transaction management framework, then you have to manage your transactions manually, like this in your previously mentioned BaseDAO class:

public void save(Object o) {
 EntityTransaction tx = null;
 try {
     tx = entityManager.getTransaction();
     tx.begin();
     entityManager.persist(o);
     tx.commit();
 }
 catch (RuntimeException e) {
     if ( tx != null && tx.isActive() ) tx.rollback();
     throw e; // or display error message
 }
 finally {
  entityManager.close();
 }
}
As you can see this is a very much code to write just to call the entityManager.persist(o); instruction. Hopefully, Spring come with a framework to manage transactions for us using AOP annotations.
With Spring Transactions: to make a method transactional (i.e. be executed in a transaction), all we need is annotate that method with @Transactional.
Usually, you will want to make your service layer transactional. In fact, a transaction must go inline with the ACID properties (Atomic, Consistent, Isolated, Durable). And by this, if a service layer method,only requires a one call to the DB, than the transaction will contain only that DB access. However, if a single service method needs to execute two or more related operations, and among them there is a DB save operation. If you made only DB access method as transactional, then, when an error occurs while doing the service logic (or during the DB transaction) then only some logic will get executed. A good example for this situation is this service method:

public void bookArticle(User user, Article article, int quantity){
 articleDao.retrieveFromStock(article, quantity); //decrease the number of articles available in DB
 doPayment(user, quantity, article);//Make the payment
}
Now suppose that for a reason or another, the doPayment method throws an exception. If you didn't think about that, then you will get your DB messed up every time there is a payment problem. And you will find yourself obliged to handle these error by re-adding back the already decreased number of articles into DB.
And for this reason, you should make every service logic that is related by the ACID properties in the same transaction, like the above bookArticle method.
How to do it in Spring words
First thing to do is to add the transaction Spring framework, here is maven dependencies:


  org.springframework
  spring-tx
  ${org.springframework.version}

After that, you need to add Spring transactions configs to your applicationContext.xml file, here what needs to be added (I chose to write the beans root element here, just to show you the added namespace for transactions):


    
    
    
    
    
     
    

Now all you need is to annotate you methods with adequate transaction annotations, for example:
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void saveUser(User user){
 userDao.save(user);
}
@Transactional(readOnly=true)
public User findUserByLoginOrEmail(String loginOrEmail) {
 return userDao.findUserByLoginOrEmail(loginOrEmail);
}
Here we have two types of annotations: a method that requires a new transaction to be opened (via propagation=Propagation.REQUIRES_NEW) and another one, that is only for reading DB data (via readOnly=true).
For a complete list of Spring transactions propagation behaviors please read this section of the docs.

No comments:

Post a Comment