Hibernate Resource Management with Callbacks

Hibernate is a popular ORM library that uses abstractions of SQL transactions and other DB concepts. Like anything that deals with resources that must be cleaned up (network sockets, file handles, DB connections, transactions, etc.), ensuring that these resources are cleaned up correctly can get pretty verbose. For details on how to structure resource cleanup code in general, see David M. Lloyd’s article on the subject.

As our use of Hibernate grew beyond a few simple DB interactions, the amount of mostly-duplicated boilerplate code became more and more irritating, so we created some helpers to cut down on the duplication. I suspect other people probably have the same concern, so I’ll show how we were able to simplify our Hibernate interactions (as well as reducing error-prone duplicate code).

Hibernate Basics

The Hibernate class—technically an interface—that typically starts a Hibernate “conversation” is SessionFactory. Most people probably start off with a simple HibernateUtil class just like the one described in the Hibernate tutorial. This is simply a way to easily access a single SessionFactory instance.

Here’s how you could use this to create an object in the database.

// Assume User is a persistent class (e.g. mapped with JPA or
// Hibernate annotations or a User.hbm.xml file)
User user = new User();
Integer id;
 
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
id = session.save(user);
tx.commit();
session.close();

This doesn’t do robust error handling, though. In fact, HibernateException can be thrown from every single one of these method calls, though you wouldn’t know it if you put this code in a Java editor — HibernateException extends RuntimeException, so it is unchecked. If tx.commit() threw HibernateException, the session would never be closed. This can cause memory leaks. Here’s a better version of the code with more error handling.

final Session session = sessionFactory.openSession();
 
try {
    final Transaction tx = session.beginTransaction();
    try {
        id = session.save(user);
        tx.commit();
        session.close();
    } finally {
        if (!tx.wasCommitted()) {
            try {
                tx.rollback();
            } catch (HibernateException e) {
                // log
            }
        }
    }
} finally {
    if (session.isOpen()) {
        try {
            session.close();
        } catch (HibernateException e) {
            // log
        }
    }
}

That’s an awful lot of code just to create a row in the DB! However, we can do better. First, move the contents of the finally blocks into their own methods. They can be static methods as they have no state, but you’ll want to have a logger available to record when resources can’t be closed. Here, I use SLF4J‘s Logger interface in a static logger field.

public static void tolerantClose(Session session) {
    if (session.isOpen()) {
        try {
            session.close();
        } catch (HibernateException e) {
            logger.warn("An error occurred while closing the session.", e);
        }
    }
}
 
public static void tolerantDispose(Transaction tx) {
    // we're not in XA/JTA, so wasCommitted should be reliable. See the javadocs.
    if (!tx.wasCommitted()) {
        try {
            tx.rollback();
        } catch (HibernateException e) {
            logger.warn("Failed to rollback", e);
        }
    }
}

Focusing on the real work

Using methods to close sessions and transactions will help, but it’s still quite verbose overall. Pretty much everything that’s done with Hibernate is done in the context of a Session and a Transaction, so what if we hide all the setup and teardown of Session and Transaction and focus on just the work that needs to be done? First, an interface to represent the work to do:

public interface HibernateCallback {
    T execute(Session session, Transaction tx) throws DaoException;
}

I’m declaring the method to throw DaoException, a simple subclass of Exception. This is because for the way we use Hibernate it makes more sense to have Hibernate interactions throw checked exceptions than to throw unchecked exceptions, but if you like Hibernate’s HibernateException, feel free to remove uses of DaoException.

Now, code to run a HibernateCallback:

public  T runCallback(final HibernateCallback callback) throws DaoException {
    T result;
 
    try {
        final Session session = this.sessionFactory.openSession();
 
        try {
            final Transaction tx = session.beginTransaction();
            try {
                result = callback.execute(session, tx);
                tx.commit();
                session.close();
            } finally {
                DbResourceCloser.tolerantDispose(tx);
            }
        } finally {
            DbResourceCloser.tolerantClose(session);
        }
    } catch (HibernateException e) {
        throw new DaoException("Could not execute hibernate callback", e);
    }
 
    return result;
}

There are several new things in this method:

  • The session factory is referenced as a field. I recommend creating a class that wraps a SessionFactory and exposes runCallback and other methods without exposing the SessionFactory itself. You may even be able to have HibernateUtil (or equivalent) only expose this wrapper and never expose SessionFactory at all.
  • The method is generic and has its own T generic parameter. The wrapper class itself need not be generic (and should not be). An example of how this method is used (below) should make this clear.
  • DbResourceCloser is simply a class containing the methods described above.

This is how the wrapper and callback can be used together.

SessionFactoryWrapper wrapper = new SessionFactoryWrapper(sessionFactory);
 
final User user = new User();
HibernateCallback callback = new HibernateCallback() {
    @Override
    public Integer execute(Session session, Transaction tx) {
        return (Integer) session.save(user);
    }
};
 
Integer id = wrapper.runCallback(callback);

That has a much better ratio of work done to code written. (Also, nothing in that code is specific to User; you could use it to save any persistent class. You may wish to put a method that does just that on your version of SessionFactoryWrapper, but note that changes that happen once a Hibernate Session has been closed will not be automatically tracked by Hibernate. This is fine if you have already set up all the data in the persistent class before you save it.) Now that we have this core abstraction done, a lot of other things become simpler. What if you want to use a Work object to do some raw JDBC commands? We can easily add that to the wrapper class:

public void runWork(final Work workCallback) throws DaoException {
    HibernateCallback hbCallback = new HibernateCallback() {
        @Override
        public Void execute(Session session, Transaction tx) {
            session.doWork(workCallback);
            return null;
        }
    };
 
    this.runCallback(hbCallback);
}

Now that it’s easy to do raw JDBC operations, let’s further illustrate the convenience of callbacks by making a way to simply operate on every result returned by a prepared statement. First, the callback interface:

public interface PreparedStatementCallback {
    String getQueryString();
 
    void configurePreparedStatement(PreparedStatement stmt) throws SQLException;
 
    void processRow(ResultSet resultSet) throws SQLException;
}

And the method that uses the callback:

public void runPreparedStatementCallback(
        final PreparedStatementCallback preparedStatementCallback)
        throws DaoException {
 
    final Work workCallback = new Work() {
        @Override
        public void execute(Connection connection) throws SQLException {
            String query = preparedStatementCallback.getQueryString();
            final PreparedStatement stmt = connection.prepareCall(query);
            try {
                preparedStatementCallback.configurePreparedStatement(stmt);
 
                final ResultSet res = stmt.executeQuery();
                try {
                    connection.commit();
 
                    while (res.next()) {
                        preparedStatementCallback.processRow(res);
                    }
                } finally {
                    res.close();
                }
            } finally {
                stmt.close();
            }
        }
    };
 
    this.runWork(workCallback);
}

Now it’s easy to do simple operations with prepared statements:

PreparedStatementCallback callback = new PreparedStatementCallback() {
    @Override
    public String getQueryString() {
        return "SELECT u.id FROM user u WHERE u.id > ?";
    }
 
    @Override
    public void configurePreparedStatement(PreparedStatement stmt) throws SQLException {
        stmt.setInt(1, 471);
    }
 
    @Override
    public void processRow(ResultSet resultSet) throws SQLException {
        int userId = (resultSet.getInt("id"));
        System.out.println("Found a user id greater than 471: " + userId);
    }
}
 
wrapper.runPreparedStatementCallback(callback);

This is a Hibernate tutorial, after all, so how about another callback that’s the Hibernate-level equivalent of the prepared statement callback? This one is for easily performing read-only operations on the persistent entities returned from using a Criteria. This is the callback interface:

public interface DaoCriteriaReadOnlyCallback {
    Criteria getCriteria(StatelessSession session);
 
    T cast(Object o);
 
    void delegate(T dao);
}

The cast() method is simply so that delegate() need not deal with casting the Objects returned by Hibernate to the appropriate persistent class. (You could also do it with clever usage of Class#cast().) The usage example below should make it clear how this is used, but first we need the method that runs the callback.

public  void runCriteriaCallback(DaoCriteriaReadOnlyCallback callback) throws DaoException {
    try {
        // Read only session
        final StatelessSession statelessSession = sessionFactory.openStatelessSession();
        try {
            final Transaction tx = statelessSession.beginTransaction();
            try {
                Criteria crit = callback.getCriteria(statelessSession);
                final ScrollableResults cursor = crit.scroll(ScrollMode.FORWARD_ONLY);
 
                try {
                    while (cursor.next()) {
                        callback.delegate(callback.cast(cursor.get(0)));
                    }
 
                    cursor.close();
                    tx.commit();
                    // StatelessSession#close is not idempotent, called only in finally block
                } finally {
                    DbResourceCloser.tolerantClose(cursor);
                }
            } finally {
                DbResourceCloser.tolerantDispose(tx);
            }
        } finally {
            DbResourceCloser.tolerantClose(statelessSession);
        }
    } catch (HibernateException e) {
        throw new DaoException("Could not execute hibernate callback", e);
    }
}

Here’s how such a callback could be used.

DaoCriteriaReadOnlyCallback callback = new DaoCriteriaReadOnlyCallback() {
    @Override
    public Criteria getCriteria(StatelessSession session) {
        return session.createCriteria(User.class)
                .add(Restrictions.eq("status", UserStatus.ACTIVE));
    }
 
    @Override
    public User cast(Object o) {
        return (User) o;
    }
 
    @Override
    public void delegate(User user) {
        System.out.println("Got a user: " + user.getId());
    }
}
 
wrapper.runCriteriaCallback(callback);

Using these tools, it’s easy to create methods that quickly and safely perform CRUD operations, as well as to execute more sophisticated logic like the criteria-based callback. This is far from the only way to organize Hibernate code, though, so feel free to comment if you have suggestions or improvements.

  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Twitter
  • Google Bookmarks
  • DZone
  • HackerNews
  • LinkedIn
  • Reddit
  • Alice

    GREAT ARTICLE! thanks for that.

  • http://thoughtforge.net John Turner

    I mostly use spring template classes which perform the boilerplate you outline above. I assume you are not using Spring or did you make a conscious decision to take this approach as opposed to Spring hibernate templates?

    • Marshall Pierce

      John, thanks for bringing that up; I should have mentioned Spring’s hibernate support. I’m not using Spring IoC, though that isn’t necessarily a reason not to use Spring’s ORM library. There are a couple reasons why I didn’t use Spring ORM:
      – We’ve purposely kept our Hibernate usage pretty simple to avoid straying into the bad parts of ORM.
      – We don’t (yet) need things like Spring’s declarative transaction management.
      – It’s pretty simple to write yourself. :)
      – DataAccessException is unchecked which, though fine for many usages, doesn’t work well for our usage. This could, of course, be solved by wrapping with a checked exception as I’ve done here with HibernateException.

      Spring ORM is a reasonable option, though, and if JTA support or other fancy things one can accomplish with Spring are needed I would certainly recommend at least trying it out to see if it works for you.

  • Jeff G

    this is called the template pattern from the GoF patterns. I’ve used it for the last decade for all my DAO implementations, hibernate or not. It does remove a ton of boilerplate code, but junior developers tend to get lost in the template. Say they have an error in a query, their debugger will pull them through the methods of the base class. They’ll learn to use it over time, but their is a learning curve.

    JEE 5 and 6 make templating even easier with generics. My JEE version is like this

    @Local
    public interface DataAccessObject  {
     
    	@TransactionAttribute(TransactionAttributeType.REQUIRED)
    	public VO persist(VO vo);
     
    	@TransactionAttribute(TransactionAttributeType.NEVER )
    	public List findAll();
     
    	@TransactionAttribute(TransactionAttributeType.NEVER)
    	public VO findById( long id);
     
    }
     
     
    public abstract class DAOTemplate implements DataAccessObject {
    	private static final long serialVersionUID = 1L;
     
     
    	protected String SelectByIDQuery;
    	protected String SelectAllQuery;
    	protected String genericTypeName;
     
    	@PersistenceContext(name="FISH")
    	protected EntityManager em;
     
    	@SuppressWarnings("unchecked")
    	protected DAOTemplate(){
     
    		Class genericType = ((Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
    		//generic.
    		//build the queries
    		genericTypeName = genericType.getSimpleName();
    		//genericTypeName = genericTypeName.substring(genericTypeName.lastIndexOf(".") +1);
    		SelectAllQuery = "select o from " + genericTypeName + " as o";
    		SelectByIDQuery = SelectAllQuery + " where o.id = :ID";
     
    	}
     
    	public VO persist(VO vo) {
    		return em.merge(vo);
    	}
     
    	@SuppressWarnings("unchecked")
    	public List findAll() {
    		Query query = em.createQuery( SelectAllQuery  );
    		return query.getResultList();
    	}
     
    	@SuppressWarnings("unchecked")
    	public VO findById(long id) {
    		Query query = em.createQuery(SelectByIDQuery);
    		query.setParameter("ID",id);
    		return (VO)query.getSingleResult();
    	}
     
    	protected void finalize() throws Throwable {
    		em.close();
    	}
     
    }
     
     
     
    Implementations are like the following.
    @Local
    public interface PersonDAO extends DataAccessObject{
     
    	@TransactionAttribute(TransactionAttributeType.NEVER)
    	public T findByName(String firstName, String lastName);
     
    	@TransactionAttribute(TransactionAttributeType.REQUIRED)
    	public void delete(String emailAddress);
     
    	@TransactionAttribute(TransactionAttributeType.NEVER )
    	public T findByEmailAddress(String emailAddress);
     
    }
     
    @Stateless
    public class PersonDAOBean extends DAOTemplate implements PersonDAO {
    	private static final long serialVersionUID = 1L;
     
    	private String findByNameQuery;
    	private String findByEmailAddressQuery;
    	private String deleteQuery;
     
    	public PersonDAOBean() {
    		super();
    		findByNameQuery = "select o from " + genericTypeName + " as o where o.firstName=:FirstName and o.lastName=:LastName";
    		deleteQuery = "delete from " + genericTypeName + " as o where o.emailAddress=:EmailAddress";
    		findByEmailAddressQuery = "select o from " + genericTypeName + " as o where o.emailAddress=:EmailAddress";
    	}
     
     
    	@SuppressWarnings("unchecked")
    	@Override
    	public VO findByName(String firstName, String lastName) {
    		try{
    			Query query = em.createQuery(findByNameQuery);
    			query.setParameter("FirstName",firstName);
    			query.setParameter("LastName",lastName);
    			return (VO)query.getSingleResult();
    		} catch (NoResultException e ){
    			return null;
    		}
    	}
     
     
    	@SuppressWarnings("unchecked")
    	@Override
    	public VO findByEmailAddress(String emailAddress) {
    		try{
    			Query query = em.createQuery(findByEmailAddressQuery);
    			query.setParameter("EmailAddress",emailAddress);
    			return (VO)query.getSingleResult();
    		} catch (NoResultException e ){
    			return null;
    		}
    	}
     
    	@Override
    	public void delete(String emailAddress) {
     
    		Query query = em.createQuery(deleteQuery);
    		query.setParameter("EmailAddress",emailAddress);
    		query.executeUpdate();
    	}
     
    }