Pages

Saturday, 17 November 2012

Hibernate as Persistence Provider and ORM Solution – Part II


Continues from Part I

Configuring Hibernate Framework

The instance of org.hibernate.cfg.Configuration is used to manage Hibernate configuration that represents an entire set of mappings of an application’s Java types to an SQL database. These mappings are compiled from various XML mapping files or from Java 5 Annotations. The org.hibernate.cfg.Configuration is used to build an immutable org.hibernate.SessionFactory.
Hibernate provides following types of configurations
  1. hibernate.cfg.xml – A standard XML file which contains hibernate configuration and which resides in root of application’s CLASSPATH
  2. hibernate.properties – A Java compliant property file which holds key value pair for different hibernate configuration strings.
Programmatic configuration – This is the manual approach. The configuration can be defined in Java classes.

It is noticeable here that if both hibernate.cfg.xml and hibernate.properties files are provided simultaneously in an application then hibernate.cfg.xml gets precedence over hibernate.properties.


Pros and Cons of Hibernate Framework


Pros

  • Reduces development effort for persisting data from business tier.
  • With help of object relational mapping the database operations on tables can be performed in an object oriented style.
  • It allows caching of persistent objects that can be used for the performance improvement.
  • It is database vendor neutral.
  • Developer is not required to know SQL statements.

Cons

  • For highly data intensive applications, hibernate does not perform up to the mark due to memory issues.
  • Hibernate is suitable only if the database schema is well designed and not very complex.
  • Testing and debugging memory leaks might take a lot of time because the framework does not provide the control to write an optimized query.
  • Sometimes hibernate may generate a vendor specific query in the background that is performance wise very poor.
  • Needs high level of expertise for handling the performance tuning issues.


Using SessionFactory, Session and Transaction objects in an application


If there is single data source to which application needs to connect then only one SessionFactory object is enough for the entire application. However if an application is supposed to be connected to multiple data sources then in that case the number of SessionFactory objects in the application should be equal to the number of data sources. In most of the cases it is the best practice to use single hibernate session for a single request. If we tend to use same session for multiple requests then it means more number of persistent objects will reside in the first level cache which is a memory leak and thus not desired.

Whenever a Servlet receives any request a new thread is assigned to that request and if we follow the session-per-request approach then we will always have a unique session for each thread created against each request.

Now the question is how we can ensure that for each thread there is always a unique session? Simply use ThreadLocal variables to handle Hibernate sessions and that is it. It is noticeable that although a session can span over multiple transactions yet on a given point of time there can not be more than one active transactions as in Hibernate there is no concept of nested transactions. Therefore we can also use ThreadLocal variables to handle Hibernate transactions. Please take a look at the following utility class that does provide this functionality.
import java.sql.SQLException;
import java.util.logging.Logger;
import org.hibernate.Transaction;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
 
public final class HibernateUtil {

   private static final Logger logger  = Logger.getLogger(HibernateUtil.class.getSimpleName());
   private static final ThreadLocal<Session> threadSession =
      new ThreadLocal<Session>();
   private static final ThreadLocal<Transaction> threadTransaction =
      new ThreadLocal<Transaction>();
   private final static SessionFactory sessionFactory;

   static {
      sessionFactory = buildSessionFactory();
   }
    
 
   private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new AnnotationConfiguration()
              .configure()
                    .buildSessionFactory();
        } catch (Throwable ex) {
            logger.warning("Initial SessionFactory creation failed.");
            throw new ExceptionInInitializerError(ex);
        }
   }

   public static Session getCurrentSession() {
      Session s = threadSession.get();
      try {
         if (s == null || !s.isOpen()) {            
            logger.info("Opening new Session for this thread.");            
            s = sessionFactory.openSession();
            threadSession.set(s);
         }
         else {            
               logger.info("Using current session in this thread.");            
         }
      } catch (HibernateException ex) {
         throw new IllegalStateException("unable to open hibernate session",ex);
      }

      return s;
   }

   public static WrappedSession getWrappedSession() {
      return new WrappedSession(getCurrentSession());
      
   }

   public static void closeSession() {
      try {
         final Session s = threadSession.get();
         if (s != null && s.isOpen()) {
            logger.info("Closing Session of this thread.");
            s.close();
         }
      }
      catch (HibernateException ex) {
         logger.warning("Failed to close session of this thread.");
      }
      finally {
         threadSession.set(null);
      }
   }

   public static void beginTransaction() {
      Transaction tx = threadTransaction.get();
      try {
         if (tx != null && !tx.isActive()) {
            tx = null;
            threadTransaction.set(null);
         }
         if (tx == null) {
            
            logger.info("Starting new database transaction in this thread.");       
            tx = getCurrentSession().beginTransaction();
            threadTransaction.set(tx);
         }
         else {
            
            logger.info("Using current database transaction in this thread.");            
         }
      }
      catch (HibernateException ex) {
         throw new IllegalStateException("Failed to start new database transaction in this thread.");
      }
      finally {
         if (threadSession.get() == null || !threadSession.get().isOpen()) {
            getCurrentSession();
         }
      }
   }

   public static void commitTransaction() {
      final Transaction tx = threadTransaction.get();
      try {
         if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack())
         {                            
            logger.info("Flushing session and committing transaction of this thread.");
            Session s = getCurrentSession();
            s.flush();
            tx.commit();
         }
      }
      catch (HibernateException ex) {
         rollbackTransaction();
         logger.warning("Failed to flush session and commit transaction of this thread.");
      }
      finally {
         threadTransaction.set(null);
         closeSession();
      }
   }

   public static void rollbackTransaction() {
      final Transaction tx = threadTransaction.get();
      try {
         if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) {
            logger.info("Trying to rollback database transaction of this thread.");
            tx.rollback();
         }
      }
      catch (HibernateException ex) {
         logger.warning("Failed to rollback database transaction of this thread.");
      }
      finally {
         threadTransaction.set(null);
         closeSession();
      }
   }
}