Hibernate 2


What to me seems a long while back, I wrote an article on Hibernate. Spring was just beginning to take root, and everyone was raving about POJOs. I am studying Spring and Hibernate, and thought posting this bit of historical trivia would be a good starting point. Don’t use it as is, or use it at your own peril.

The ideal persistence mechanism

Ideally, objects would not need to be persisted if memory was non-volatile. Creating objects and establishing associations would suffice. Thus, a system would be a soup of objects, each having to deal with their own responsibilities, and not having to bother about external persistence.

O/R mapping

The closest we get to our ideal persistence is by using an O/R mapping framework such as Hibernate. Typically, external persistence is done by saving object state to relational databases. Thus, after we create an object, we have to persist it. The following steps are typically performed when a user interacts with a web based data entry use case:

  • Create new objects (transient) or query existing objects (persistent)
  • Begin a transaction
  • Change object properties and associations
  • Save objects to persistent storage
  • Commit the transaction

A simple persistence framework using Hibernate

In our architecture, development is driven by use cases. The flow of events of one or more use cases are orchestrated by a controller object. Each method of a controller object may initiate a new, or participate in an existing, database transaction. The controller saves and retrieves persistent objects using a Hibernate utility class, which in turn directly uses the Hibernate Session object.

import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Interceptor;
import net.sf.hibernate.LockMode;
import net.sf.hibernate.ObjectNotFoundException;
import net.sf.hibernate.ReplicationMode;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.metadata.ClassMetadata;
import net.sf.hibernate.type.Type;

/**
 * This utility class provides a simple framework for using Hibernate
 * by implementing some <a href="http://www.hibernate.org/40.html">well known</a>
 * design patterns or by being useful with some of these patterns:
 *  - Thread Local session
 *  - AspectJ Hibernate aspect
 *  - Root Persistent Class
 *
 * In conjunctions with CGLIB and the TransactionConfiguration class it allows
 * any class to be extended so that methods of it's instances are wrapped with
 * appropriate begin and commit transaction invocations. The transaction
 * requirement can specified in an external xml configuration file.
 *
 * @see net.sf.hibernate.Session
 * @see net.sf.hibernate.SessionFactory
 */
public final class HibernateUtil implements MethodInterceptor, Interceptor {
// methods in various code snippets below
}

Persistent Objects

Persistent objects inherit from a common root class called PersistentObject as shown below.

/**
 * Super-class of all basic / entity classes.
 */
public abstract class PersistentObject implements Serializable {
    // flag indicating that object is persistent / saved
    private boolean _saved = false; 

    /**
     * This method is called when the object is saved to persistent
     * storage.
     */
    public void onSave() {
        _saved = true;
    }

    /**
     * This method is called when the object is loaded from persistent
     * storage.
     */
    public void onLoad() {
        _saved = true;
    }

    /**
     * This method is used to discover the saved status of the object.
     *
     * @return True if the object is exists in persistent storage
     */
    public boolean isSaved() {
        return _saved;
    }
}

Hibernate Utility needs the isSaved() method to hint a hibernate session on whether an object is persistent. This is required in the special case where the child object has the primary key of the parent object and not a direct reference to it. This is how the Hibernate utility, which needs to implement the Hibernate Interceptor interface, makes use of this method.

    /**
     * If the specified object is an instance of PersistentObject
     * sets the state of the object to saved.
     * @see net.sf.hibernate.Interceptor#onLoad(java.lang.Object,
     * java.io.Serializable, java.lang.Object[], java.lang.String[],
     * net.sf.hibernate.type.Type[])
     */
    public boolean onLoad(Object object, Serializable id, Object[] state,
            String[] propertyNames, Type[] types)
            throws CallbackException {

        if (object instanceof PersistentObject) {
            ( (PersistentObject) object ).onLoad();
        }
        return false; // we did not modify state
    }

    /**
     * If the specified object is an instance of AuditedPersistentObject
     * sets the dateLastUpdated attribute.
     */
    private boolean setDateLastUpdated(Object object, Object[] state,
            String[] propertyNames) {
        if (object instanceof AuditedPersistentObject) {
            for (int i = 0; i < propertyNames.length; i++) {
                if ("dateLastUpdated".equals(propertyNames[i])) {
                    state[i] = new Date();
                    return true;
                }
            }
        }
        return false; // we did not modify state
    }

    /**
     * @see net.sf.hibernate.Interceptor#onFlushDirty(java.lang.Object,
     * java.io.Serializable, java.lang.Object[], java.lang.Object[],
     * java.lang.String[], net.sf.hibernate.type.Type[])
     */
    public boolean onFlushDirty(Object object, Serializable id,
            Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types)
            throws CallbackException {

        return setDateLastUpdated(object, currentState, propertyNames);
    }

    /**
     * @see net.sf.hibernate.Interceptor#onSave(java.lang.Object,
     * java.io.Serializable, java.lang.Object[], java.lang.String[],
     * net.sf.hibernate.type.Type[])
     */
    public boolean onSave(Object object, Serializable id, Object[] state,
            String[] propertyNames, Type[] types)
            throws CallbackException {

        boolean result = setDateLastUpdated(object, state, propertyNames);

        if (object instanceof PersistentObject) {
            ( (PersistentObject) object ).onSave();
        }

        return result;
    }

    /** (non-Javadoc)
     * @see net.sf.hibernate.Interceptor#onDelete(java.lang.Object,
     * java.io.Serializable, java.lang.Object[], java.lang.String[],
     * net.sf.hibernate.type.Type[])
     */
    public void onDelete(Object object, Serializable id, Object[] state,
            String[] propertyNames, Type[] types)
            throws CallbackException {
    }

    /** (non-Javadoc)
     * @see net.sf.hibernate.Interceptor#preFlush(java.util.Iterator)
     */
    public void preFlush(Iterator objects) throws CallbackException {

    }

    /** (non-Javadoc)
     * @see net.sf.hibernate.Interceptor#postFlush(java.util.Iterator)
     */
    public void postFlush(Iterator objects) throws CallbackException {

    }

    /**
     * If the object is a PersistentObject returns its saved status.
     * @return true if the PersistentObject already exists in the database.
     * @see net.sf.hibernate.Interceptor#isUnsaved(java.lang.Object)
     */
    public Boolean isUnsaved(Object object) {
        if (object instanceof PersistentObject) {
            return new Boolean( !( (PersistentObject) object ).isSaved() );
        } else {
            return null; // let Hibernate assume default behavior
        }
    }

    /**
     * @see net.sf.hibernate.Interceptor#findDirty(java.lang.Object,
     * java.io.Serializable, java.lang.Object[], java.lang.Object[],
     * java.lang.String[], net.sf.hibernate.type.Type[])
     */
    public int[] findDirty(Object object, Serializable id,
            Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) {
        return null; // let Hibernate assume default behavior
    }

    /**
     * @see net.sf.hibernate.Interceptor#instantiate(java.lang.Class,
     * java.io.Serializable)
     */
    public Object instantiate(Class classObj, Serializable id)
            throws CallbackException {
        Object object = null; // let Hibernate assume default behavior
        return object;
    }

Persistent objects that have composite keys must also override the equals and hashCode methods. The equals method must return true if the corresponding primary key attributes of two objects of the same class pass the equals test.

Intercepting objects about to be saved

The onSave method shown in the previous code listing also forms the basis for making changes to objects before they are saved. In our case we need to update the time the object was lasted updated. We have done that by creating a base class called AuditedPersistentObject, for all objects that need this new attribute. The implementation of this class is shown below. In the onSave method of Hibernate Utility we detect whether the object is an instance of this class and set the dateLastUpdated attribute.

/**
 * This class and it's sub-classes represent persistent objects with
 * audit information such as the date/hour the object was last updated.
 */
public abstract class AuditedPersistentObject extends PersistentObject {
    protected Date dateLastUpdated = null;

    /**
     * Recover the date/hour of last update.
     * @return Date/Hour
     */
    public Date getDateLastUpdated() {
        return dateLastUpdated;
    }

    /**
     * Set the date/hour of last update.
     * @param dateLastUpdated Date/hour
     */
    public void setDateLastUpdated(Date dateLastUpdated) {
        this.dateLastUpdated = dateLastUpdated;
    }
}

Declarative transactions

As mentioned previously, the methods in the controller can initiate transactions. To do this declaratively, we create every controller object using the following method of Hibernate Utility.

    /***
     * This method returns a CGLIB extended object of the
     * specified class. Each method call on the extended object
     * results in a callback to the intercept method.
     * @param c The class to extend.
     * @return A new extended instance of the specified class
     */
    public Object extendClass(Class c) {
        Object newObject = null;
        newObject = Enhancer.create(c, null, this);
        return newObject;
    }

Now, when any method of the controller is called, the intercept method of Hibernate Utility is called automatically. For this, Hibernate Utility must implement the net.sf.cglib.proxy.MethodInterceptor interface of CGLIB. The intercept method itself, and all the other methods it calls, and the attributes they use, are shown below.

    // Private attributes

    // Transaction configuration
    private TransactionConfiguration transactions;
    // Hibernate Session Factory
    private SessionFactory sessionFactory = null;
    // Number of transactions in progress
    private int transactionCount = 0;             

    // Private Thread Local attributes

    // Session local to current thread
    private ThreadLocal session = new ThreadLocal();
    // Active transaction local to current thread
    private ThreadLocal transaction = new ThreadLocal();
    // Depth of the active database transaction
    private ThreadLocal transactionDepth = new ThreadLocal(); 

    /**
     * Wraps the call of an intercepted method with begin and
     * commit transactions if it supports transactions.
     * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], net.sf.cglib.proxy.MethodProxy)
     */
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {

            boolean useTransaction;
            Object retValFromSuper = null;

            useTransaction = transactions.isTransaction(method);
            try {
                if (useTransaction) beginTransaction();

                retValFromSuper = proxy.invokeSuper(obj, args);

                if (useTransaction) commitTransaction();

            }catch (Throwable e) {
                if (useTransaction) rollbackTransaction();
                throw e;
            }
            return retValFromSuper;
    }

    /**
     * Initiates a new database transaction and associates the
     * Hibernate Transaction instance with the current thread only on the
     * first call to this method. Successive calls to this method will
     * not initiate any transactions and result in incrementing a
     * stack counter. This stack counter is later used to determine
     * the outer-most commit so that the transaction can be committed.
     *
     * @throws DataPersistenceException If the beginTransaction method
     * of hibernate returns a HibernateException.
     */
    public void beginTransaction() throws DataPersistenceException {
        Integer depth = (Integer) transactionDepth.get();
        if (depth == null) {
            // first call to begin transaction in this thread
            Session s = (Session) session.get();
            if (s == null) {
                currentSession();
            }
            s = (Session) session.get();
            try {
                Transaction t = (Transaction) s.beginTransaction();
                transaction.set(t);
                transactionDepth.set(new Integer(0));
                incrementTransactionCount();
            } catch (HibernateException e) {
                throw new DataPersistenceException(e);
            }
        } else {
            // increment depth
            transactionDepth.set(new Integer(depth.intValue() + 1));
        }
    }

    /**
     * Commits a database transaction and dissociates the
     * Hibernate Transaction instance associated with the
     * current thread only if this is the outer-most commit request.
     *
     * @throws DataPersistenceException If the commit method
     * of hibernate returns a HibernateException or there is
     * no existing transaction to be committed.
     */
    public void commitTransaction() throws DataPersistenceException {
        Integer depth = (Integer) transactionDepth.get();
        if (depth == null) {
            // no begin transaction issued
        } else {
            if (depth.intValue() == 0) {
                // reached the outer most call to begin transaction
                Transaction t = (Transaction) transaction.get();
                if (t == null) {
                    // no begin transaction issued or rolled back
                    throw new DataPersistenceException("Assertion Failed: A transaction should be present but was not found.", null);
                } else {
                    try {
                        t.commit();
                    } catch (HibernateException e) {
                        throw new DataPersistenceException(e);
                    } finally {
                        transaction.set(null);
                        transactionDepth.set(null);
                        decrementTransactionCount();
                    }
                }
            } else {
                // decrement depth, ignore commit request
                transactionDepth.set(new Integer(depth.intValue() - 1));
            }
        }
    }

    /**
     * Rolls back a database transaction and dissociates the
     * Hibernate Transaction instance associated with the
     * current thread.
     *
     * @throws DataPersistenceException If the rollback method
     * of hibernate returns a HibernateException or there is
     * no existing transaction to be rolled back.
     */
    public void rollbackTransaction() throws DataPersistenceException {
        Integer depth = (Integer) transactionDepth.get();
        if (depth == null) {
            // no begin transaction issued
        } else {
            Transaction t = (Transaction) transaction.get();
            if (t == null) {
                // no transaction
                throw new DataPersistenceException("Assertion Failed: A transaction should be present but was not found.", null);
            } else {
                try {
                    t.rollback();
                } catch (HibernateException e) {
                    throw new DataPersistenceException(e);
                } finally {
                    transaction.set(null);
                    decrementTransactionCount();
                    closeSession();
                }
            }
            // decrement depth
            transactionDepth.set(null);
        }
    }

    /**
     * Recovers a Hibernate session associated with the current thread.
     * Opens a new session if one doesn't already exist and associates
     * it to the thread. As long as you don't call the closeSession
     * or the rollbackTransaction methods the session remains
     * open and associated with the current thread.
     *
     * @return A Hibernate session
     *
     * @throws DataPersistenceException If the openSession method
     * of the Hibernate session factory fails.
     */
    public Session currentSession() throws DataPersistenceException {

        Session s = (Session) session.get();
        // Open a new Session, if this Thread has none yet
        if (s == null) {
            try {
                s = sessionFactory.openSession(this);
                session.set(s);
            } catch (HibernateException e) {
                e.printStackTrace();
                throw new DataPersistenceException(e);
            }
        }
        return s;
    }

    /**
     * Closes and dissociates the Hibernate session associated with
     * the current thread.
     * @throws DataPersistenceException If the close method of the
     * Hibernate session fails.
     */
    public void closeSession() throws DataPersistenceException {
        Integer depth = (Integer) transactionDepth.get();
        if (depth != null) {
            // We are within a transaction, ignore close request
        } else {
            try {
                Session s = (Session) session.get();
                session.set(null);
                if (s != null) {
                    s.close();
                }
            } catch (HibernateException e) {
                e.printStackTrace();
                throw new DataPersistenceException(e);
            }
        }
    }

The TransactionConfiguration class, used in the intercept method above, parses an XML file that specifies which methods of any class need database transactions. The isTransaction methods of this class return true for methods that need database transactions. See the code listing below for implementation of these methods.

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Utility class used to represent transaction configuration.
 */
public class TransactionConfiguration extends DefaultHandler {
    private static String className;
    Map classes = new HashMap();
    private static TransactionConfiguration instance;

    public static TransactionConfiguration getInstance() {
        if (instance == null) {
            instance = new TransactionConfiguration();
        }
        return instance;
    }

    /**
     * The method returns true if the method of the specified class is
     * transactional.
     *
     * @param className
     *            Fully qualified name of the class
     * @param methodName
     *            Method name
     * @return true if the method is specified as transactional.
     */
    public boolean isTransaction(String className, String methodName) {
        return hasMethod(className, methodName);
    }

    /**
     * The method returns true if the given Method instance is transactional.
     *
     * @param method
     *            Method instance
     * @return true if the Method instance is specified as transactional.
     */
    public boolean isTransaction(Method method) {
        boolean val;
        val = isTransaction(method.getDeclaringClass().getName(), method
                .getName());
        if (val == false) {
            // let us check if super classes / interfaces are transactions
            for (int i = 0; i < method.getDeclaringClass().getInterfaces().length; i++) {
                val = isTransaction(
                        method.getDeclaringClass().getInterfaces()[i].getName(),
                        method.getName());
                if (val == true)
                    break;
            }
        }
        return val;
    }

Hibernate Utility encapsulates the most commonly used Hibernate Session and SessionFactory methods, so you don’t need to obtain a Session by calling the currentSession method and call the methods of the Session.

Declarative transactions using AspectJ

The following aspect can be used to extend desired methods to add declarative transactions.

public abstract aspect TransactionAspect {

    public abstract pointcut transactedMethods();

    Object around() throws SystemRuntimeException : transactedMethods() {
        boolean useTransaction = false;
        Object retValFromSuper = null;

        String methodName = thisJoinPoint.getSignature().getName();
        String className = thisJoinPoint.getSignature()
                .getDeclaringType().getName();

        // The TransactionConfiguration determines if a method
        // should be made transactional or not.
        useTransaction = TransactionConfiguration.getInstance()
                .isTransaction(className, methodName);

        if ( useTransaction ) {
            System.out.println("TransactionAspect: Method '"
                    + className + "." + methodName
                    + "' is transactional.");
            try {
                // begin the transaction.
                HibernateUtil.getInstance().beginTransaction();

                // "proceed()" allows the method to continue as it normally
                // would. The return value is saved.
                retValFromSuper = proceed();

                HibernateUtil.getInstance().commitTransaction();

                return retValFromSuper;
            } catch (RuntimeException e) {
                System.out.println("TransactionAspect: Method '"
                        + className + "." + methodName
                        + "' threw an exception.");
                HibernateUtil.getInstance().rollbackTransaction();
                throw e;
            }
        }

        // This means the method was not transactional.
        System.out.println("TransactionAspect: Method '"
                + className + "." + methodName
                + "' is NOT transactional.");

        // Return the result of the action method as normal.
        return proceed();
    }
}

A concrete aspect must extend the abstract aspect above and implement the abstract join point transactedMethods as follows.

public aspect AppTransactionAspect extends TransactionAspect {
    public pointcut transactedMethods() :
        (target(PersistentObject)
        || target(SomeOtherClass))
        && execution(public * *( .. ))
        && !execution(* PersistentObject.*( .. ))
        && !execution(public * find*( .. ))
        && !execution(public * get*( ))
        && !execution(public * set*( .. ));
}

The transactedMethods point cut implementation above executes the around advice implemented in the abstract aspect, for all methods of classes that are sub classes of SomeOtherClass and PersistentObject, except the following:

  • All methods of the class PersistentObject itself.
  • All methods beginning with set.
  • All methods beginning with get.
  • All methods beginning with find.

Concurrency

While an object is being prepared for updating, the persistent representation of the object in the database may be changed by another transaction. Hibernate automatically adds version number checking to all UPDATE commands if the class mapping has a version element, as shown below.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"     "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="test.app">

    <class name="Customer" table="CUSTOMER">

        <id name="id">
            <column name="id" sql-type="integer" not-null="true"/>
            <generator class="native"/>
        </id>

        <version name="version" column="version" type="integer" />

        <property name="name">
            <column name="name" sql-type="varchar(20)" not-null="true"/>
        </property>

        <bag name="orders" table="ORDER" cascade="all">
            <key column="customer_id"/>
            <one-to-many class="Order"/>
        </bag>

    </class>

</hibernate-mapping>

The application is responsible for checking that the object information it has is not stale, before updating the object.

Closing Hibernate sessions

Closing hibernate sessions is a must to liberate JVM heap and avoid an OutOfMemoryError, and more importantly, close database connections. In web applications one useful strategy is to use a servlet filter to close a Hibernate session as shown below.

public class CloseDataSession implements Filter {

    private static ThreadLocal stackCounter = new ThreadLocal();
        // for closing sessions only when exiting from a client request

    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        try {
            increment();
            chain.doFilter(req, resp);
        } finally {
            if (decrement() <= 0) {
                HibernateUtil.getInstance().closeSession();
            }
        }
    }

    public void init(FilterConfig config) throws ServletException {
    }

    private int increment() {
        int count = 0;
        if (stackCounter.get() == null) {
            stackCounter.set(new Integer(++count));
        } else {
            count = ((Integer) stackCounter.get()).intValue();
            stackCounter.set(new Integer(++count));
        }
        return count;
    }

    private int decrement() {
        int count = 0;
        if (stackCounter.get() == null) {
            stackCounter.set(new Integer(count));
        } else {
            count = ((Integer) stackCounter.get()).intValue();
            stackCounter.set(new Integer(--count));
        }
        return count;
    }
}

The filter CloseDataSession is mapped to all server side resources that directly or indirectly access any persistent objects. This mapping is done in the web module descriptor (web.xml) of a J2EE web application. Some application servers execute a filter several times within the same web request, for example, every time when the request is forwarded. To close the Hibernate session only once, we use a simple filter execution counter associated with the current thread. Only when the counter goes to zero do we close the session.

For those adopting Hibernate today

I recommend using Hibernate with JPA, or Spring, and using annotations instead of manually coding configuration files.

Advertisements

One thought on “Hibernate 2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s