Archive for the Category 'hibernate'

Hibernate anti-pattern #1 – hashCode/equals

Friday, December 14th, 2007

I’ve been doing a lot of Hibernate refactoring and performance tuning at work lately and have been spending time wrestling with some interesting anti-patterns that have crept into the code. One is our particularly bad hashCode/equals implementation which has some pretty frustrating implications1.

There’s a lengthy discussion here about various ways to implement hashCode and equals for Hibernate-managed entities. The post is great, and the discussion in the comments is also good.


The assumption is that the 1st-level cache (and also the 2nd-level cache if implemented) ensures that instances are unique. So our base entity class has a final equals method that uses the same logic as Object – two instances are equal if they’re the same (==). Likewise, hashCode is final and returns System.identityHashCode(this), the same value as if the method weren’t overridden (we have a base class between Object and our base entity class that necessitates explicitly reverting the behavior to that of Object):

@Override
public final boolean equals(final Object o) {
  return o == this;
}

@Override
public final int hashCode() {
  return System.identityHashCode(this);
}

This turns out to be overly simplistic and optimistic – it sure would be nice if that’s all it took, since there’s no equals/hashCode maintenance for new entity classes, no custom entity-specific logic to implement.

If you call Foo foo1 = session.get(Foo.class, 123) and Foo foo2 = session.get(Foo.class, 123), the 2nd call will return a reference to the cached 1st instance – there’s only 1 database hit. This is one of the primary benefits of using an ORM framework like Hibernate – there’s some overhead in its approach but optimizations like this will tend to far outweigh the costs as long as you’re using the framework appropriately.

So in this case, foo1 == foo2, and the equals method works fine.

But if you mix eagerly loaded entity instances and proxies, weird things happen. For example, suppose Bar has a lazy many-to-one reference to Foo (or a lazy-loaded collection of Foos); == will always be false even if the target entity of the proxy is the same as the non-proxy instance:

Foo foo = (Foo)session.get(Foo.class, 123);
Bar bar = (Bar)session.get(Bar.class, 321);

// always false since bar's Foo is a proxy
System.out.println(bar.getFoo() == foo);

// true for proper equals, always false for us
System.out.println(bar.getFoo().equals(foo));

The result is that data access implementation logic has bled up past the service tier and into the web tier; the web tier shouldn’t have to be aware of proxies but now has to be. If there’s any risk of comparing proxied and non-proxied instances, we have to use primary key equality to test instance equality instead of the much more natural equals.

What’s worse, we can’t mix persistent and new entity instances in collections that maintain uniqueness (Sets/Maps). A new instance can never be equal to a persistent instance even if every non-pk property value is the same, so we again have to deal with the data tier bleeding into other tiers and jump through hoops to get Set uniqueness and contains to work.


So here’s my proposed approach. Comparing two persistent instances using primary key is very efficient since primary keys are unique. This doesn’t help when comparing persistent and new instances though, and is inappropriate for usage in hashCode for the reasons discussed in the Hibernate post referenced above. Specifically, the hashCode will change from whatever you use before the instance is persisted to the primary key (or some value derived from it) afterwards. This will confuse hash-based collections since the instance will most likely be in the wrong hash bucket and cannot be found.

So equals compares primary keys if both are non-null. If either is null, then the class must define the fields that determine uniqueness and compare those.

hashCode always uses the same fields used in the equals comparison and never the primary key. If the entity is immutable or the fields that are used for the calculation are, then the hashCode could be cached to save the cost of unnecessary recalculation.

Most of this logic is in the base entity class, with abstract methods that force each entity class to define entity-specific behavior:

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.Hibernate;

/**
 * Abstract base class for entities.
 * @param <T> the type
 */
@MappedSuperclass
public abstract class BaseEntity<T extends BaseEntity<T>>
       extends ValueObject {

  private int _version;

  /**
   * Default.
   */
  protected BaseEntity() {
    // default
  }

  // optimistic locking version, private since only Hibernate uses these
  @Version @Column(name = "oplock", nullable = false)
  @SuppressWarnings("unused")
  private int getVersion() {
    return _version;
  }
  @SuppressWarnings("unused")
  private void setVersion(final int version) {
    _version = version;
  }

  /**
   * Utility method for <code>equals()</code> methods.
   * @param o1  one object
   * @param o2  another object
   * @return <code>true</code> if they're both <code>null</code> or both equal
   */
  protected boolean areEqual(final Object o1, final Object o2) {
    if (o1 == null) {
      if (o2 != null) {
        return false;
      }
    }
    else if (!o1.equals(o2)) {
      return false;
    }

    return true;
  }

  /**
   * Utility method for <code>equals()</code> methods.
   * @param s1  one string
   * @param s2  another string
   * @param ignoreCase if <code>false</code> do case-sensitive comparison
   * @return <code>true</code> if they're both <code>null</code> or both equal
   */
  protected boolean areEqual(
      final String s1,
      final String s2,
      final boolean ignoreCase) {
    // for use in custom equals() methods

    if (s1 == null && s2 == null) {
      return true;
    }

    if (s1 == null || s2 == null) {
      return false;
    }

    return ignoreCase ? s1.equalsIgnoreCase(s2) : s1.equals(s2);
  }

  /**
   * Utility method for <code>equals()</code> methods.
   * @param d1  one date
   * @param d2  another date
   * @return <code>true</code> if they're both <code>null</code> or both equal
   */
  protected boolean areEqual(
      final Date d1,
      final Date d2) {
    // for use in custom equals() methods

    if (d1 == null && d2 == null) {
      return true;
    }

    if (d1 == null || d2 == null) {
      return false;
    }

    return d1.getTime() == d2.getTime();
  }

  /**
   * Utility method for <code>equals()</code> methods.
   * @param f1  one float
   * @param f2  another float
   * @return <code>true</code> if they're equal
   */
  protected boolean areEqual(
      final float f1,
      final float f2) {
    // for use in custom equals() methods

    return Float.floatToIntBits(f1) == Float.floatToIntBits(f2);
  }

  /**
   * Utility method for <code>hashCode()</code> methods.
   * @param values  the values to use in calculation
   * @return  the hash code value
   */
  protected int calculateHashCode(final Object... values) {
    HashCodeBuilder builder = new HashCodeBuilder();
    for (Object value : values) {
      builder.append(value);
    }
    return builder.toHashCode();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode() {
    return calculateHashCode(getHashCodeData());
  }

  /**
   * Get the data used to calculate hash code;
   * use getters not fields in case the instance is a proxy.
   * @return the data
   */
  @Transient
  protected abstract Object[] getHashCodeData();

  /**
   * Allows type-specific "this".
   * @return  this
   */
  @Transient
  protected abstract T getThis();

  /**
   * Get the primary key.
   * @return  the pk, or <code>null</code> if not set or if not supported
   */
  @Transient
  public abstract Serializable getPk();

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  @Override
  public final boolean equals(final Object other) {
    if (this == other) {
      return true;
    }

    if (other == null
        || // looks into the target class of a proxy if necessary
        !Hibernate.getClass(other).equals(Hibernate.getClass(this))) {
      return false;
    }

    // if pks are both set, compare
    if (getPk() != null) {
      Serializable otherPk = ((BaseEntity)other).getPk();
      if (otherPk != null) {
        return getPk().equals(otherPk);
      }
    }

    return dataEquals((T)other);
  }

  /**
   * Compare data only; null, class, and pk have been checked.
   * @param other the other instance
   * @return <code>true</code> if equal
   */
  protected abstract boolean dataEquals(T other);
}

A concrete subclass must implement getHashCodeData and dataEquals, for example using the username property for a User class:

/**
 * {@inheritDoc}
 */
@Override
protected boolean dataEquals(final User other) {
  return areEqual(getUsername(), other.getUsername(), true);
}

/**
 * {@inheritDoc}
 */
@Override
@Transient
protected Object[] getHashCodeData() {
  return new Object[] { getUsername() };
}

This has worked well so far. It’s consistent, even when an instance transitions from new to persistent and its primary key becomes non-null. The data used for hashCode calculation never changes, so that’s consistent. The data for equals does change, but it stays consistent – comparing two persistent entities or mixing persistent and new returns the correct result in all cases since non-pk data is used for comparison.

Note that equals is final but hashCode isn’t to allow caching.


My original implementation of this was way too simplistic, just using the Jakarta Commons Lang reflection methods that I described here. This approach is great for regular value objects and DTOs, but is way too expensive for Hibernate entities since every field is used, even collections and the primary key. Lazy-loaded collections will be retrieved from the database to calculate hashCode and equals, which is almost always inappropriate.

So I worked on a slightly smarter approach that skipped the primary key and collections but still used reflection for the rest of the fields. This is still too expensive, and breaks when using proxies since the fields will be null – the getter methods delegate to the data that’s in the proxy’s target instance.

Eventually I ended up with the current implementation, which has lasted for a while with no issues.




  1. Ironically, The Decidertm was asked recently why this choice was made and he couldn’t defend it, but was sure that there was a good reason for it and wasn’t willing to change anything even given the implications of the decision.[back]

Don’t change the parameter value in a Hibernate setter

Tuesday, December 04th, 2007

A while back a co-worker asked for some help with a Hibernate problem – he thought he’d found a serious bug. He was an experienced database architect but new to Hibernate, so he’d written some basic test code. It was trivial – just reading objects from various HQL queries and displaying them to the console. The weird thing was, after reading an object the ‘name’ property was always null (even though the column wasn’t – the sample data had values for every column) and weirder still after reading a row, the column became null.

I looked at his mapping files and his environment but everything seemed fine. Then I noticed that his setter method looked something like this:

public void setName(String naem) {
  this.name = name;
}

So it was a classic typo – the parameter was misspelled, so the setter was just setting the field to itself (Eclipse has a check for this and I find it’s best to flag it as an error). Since the state at flush time was different from the original values that Hibernate caches for dirty detection, Hibernate dutifully pushed the “change” to the database.

I was reminded of this today because of a similar bug that was introduced last week. One of the developers was seeing an unexpected optimistic locking exception in an entity that wasn’t being updated concurrently – in fact there are no public setters in the class.

Since the class is effectively immutable, I added the mutable='false' attribute to the class element in the mapping file, removed the non-public setters only called by Hibernate during instantiation and switched to field access. I also removed the optimistic locking <version> element since there’s no need to check for concurrent updates, and the bug went away.

I felt guilty checking that in though since it felt like fixing the symptom and not the real problem – just setting up someone else the next time something similar triggered the root bug. Luckily I realized the real issue a little while later.

We have a custom type for storing dates independent of timezone (it converts to GMT on insert and we adjust for the current time zone on read) and it was implemented using java.sql.Timestamp. Apparently this caused problems when comparing equality with java.util.Date, so the custom type’s nullSafeGet was changed to read the database timestamp and convert to a java.util.Date. As with the earlier example – an apparent change of the value (Timestamp‘s equals method isn’t symmetric with Date‘s) caused Hibernate to detect the change and push the update to the database. Two users were concurrently reading the object and Hibernate was pushing both changes to the database, hence the optimistic locking exception.

So I’ll keep the entity class immutable – it matches the actual usage – but will fix the custom type to do the right thing.

The first time I burned myself like this was with mapped collections. I had a mapped List on an entity and when I wrote the unit tests for the DAO tier, I mistakenly created test SQL scripts with index column values that started at 1 instead of 0. So I was seeing that the lists always had N+1 elements, and the first was always null. I spent way too long banging on this and finally put in a hack where I created a copy of the List in the setter (a new ArrayList), keeping only non-null elements.

This was a side project I was working on from home, and I ended up getting too busy and leaving the project. I recently started working on it again after being away for a year, and was able to fix a lot of dumb mistakes I’d made (this was my first time using Spring and Hibernate a real project).

The folks on the team mentioned that the SQL logs showed lots of deletes and inserts every time the entity was loaded from the database, resulting in significant slowness, so they wanted me to take a look. It didn’t take long to realize that it was due to replacing the managed PersistentList with a new list. Since it’s a new instance, Hibernate doesn’t detect specific changes so it deletes all of the old rows and inserts all the new, even in a case like this where there was no net change. In the production code, there were no nulls and in fact no actual changes, so no actions were necessary at all. This realization lead to the discovery of the unit test bug, and I was able to remove the hack and just keep the List that Hibernate passed in the setter.

Note to self: Don’t Use Stored Procs in Hibernate

Tuesday, September 11th, 2007

We fired our DBA/Data Architect a few months ago and haven’t found a replacement yet, so I’ve been tasked with writing some PL/SQL. I’m a little rusty with PL/SQL and haven’t used Hibernate’s stored proc/function support before, so it’s been an interesting few days.


The first indication that it might not be totally straightforward was that Hibernate has a very specific format for calling procedures. The function must return a cursor as its first OUT parameter. That’s no big deal, I’m creating these from scratch, so there’s no need to adapt legacy code. Also, you can’t return an array; you must specify the entity class to be built from the results.

But then it gets weirder. I want to return a cursor that will work as a ResultSet that doesn’t map to an existing Hibernate-managed class. I’m using subclassing and won’t know which concrete class to instantiate until I get the result(s) back, but Hibernate needs to be configured as a Named Query with “callable=’true'” and with the concrete result class specified. I don’t want to create four different call types (and can’t anyway since I don’t know the return type) so I need to create a dummy entity class mapped to a dummy table to get Hibernate to deserialize the rows, then I can use that to create the actual instances via a discriminator column that I set in the stored function. Ok, no biggie, I’ll play along.

So I get everything basically working (the code’s ugly and has no error handling, but I’ll get to that …) and write some unit tests. Although the calls work fine in SQL Developer and SQL*Plus, all the tests fail:
53984 DEBUG JDBCExceptionReporter - could not execute query [{ ? = call profile_attribute.lookup_profile_attribute(?, ?)
}]
java.sql.SQLException: ORA-00900: invalid SQL statement

Huh. Looks fine to me. So I run the tests in the debugger and deep in the Hibernate code I see that Hibernate is appending a comment to the beginning of the SQL and when I turn on SQL logging I see that the SQL is actually:

/* named native SQL query getAttributeValue */ { ? = call profile_attribute.lookup_profile_attribute(?, ?) }

I have the properties hibernate.format_sql set to true, hibernate.use_sql_comments set to true, and hibernate.show_sql set to false so I can turn on SQL logging when things get weird – the formatting and comments help make sense of the generated SQL.

When I run the SQL with the prefixed comment in SQL*Plus, I get that same error. I searched for other people having experienced the same thing but didn’t find anything. So I reluctantly turned off hibernate.use_sql_comments and things worked fine. It would be convenient if this were being done in the Dialect since I could then subclass and only generate comments for non-CallableStatement calls, but it’s in the core code.


So then I modified the DAO code to limit the number of results – simple enough to do, just call Query.setMaxResults(). Nope:

java.sql.SQLException: ORA-00903: invalid table name

Huh? If I remove the call to setMaxResults it works fine. I turn on SQL logging again and see that the autogenerated SQL to limit the result count for Oracle is


select * from ( { ? = call profile_attribute.lookup_hist_attr(?, ?) } ) where rownum <= ?

which of course is valid for regular SQL but garbage for a CallableStatement. I re-read the Hibernate documentation and it does say that "Stored procedure queries can't be paged with setFirstResult()/setMaxResults()" but you'd think that this would be enforced programmatically, rather than passing junk SQL down to the database and letting it fail there.

Bummer. So I remove the call to setMaxResults and add a TODO to fix this later, but when I go to run the tests in Ant before checking everything in they fail due to SQL comments. This is very weird and must be due to some misconfiguration in the test code, but even dumping the configuration settings during the test shows that all the properties are set to false.


At this point I've had enough, and realize that I'm working way too hard to get around Hibernate's limitations. I'm not playing the "Hibernate game" though - using HQL in a database-independent fashion and using ORM to manage objects conveniently. This is a very Oracle-specific piece of functionality, and so finally I realize that I should just use straight JDBC (or more accurately Spring's JdbcTemplate).

A couple of hours of refactoring later, all is well and although I needed to write more code for the JDBC approach than the Hibernate approach, it's more direct and less hackish.

Using Generics to Simplify Hibernate/Spring DAOs (update #2 for JPA)

Thursday, March 22nd, 2007

I’ve been working with JPA and Hibernate Annotations and needed to update my base DAO interface and implementation class from its native Hibernate implementation.

The interface is a little different from the one I’d been using so I’m including the new one:

package com.foo.bar.dao;

import java.io.Serializable;
import java.util.Collection;

import com.foo.bar.model.DatabaseObject;

/**
 * Base DAO.
 * @author Burt
 * @param <T> the entity type
 */
public interface BaseDAO<T extends DatabaseObject> {

  /**
   * Get the total number of instances.
   * @return  the count
   */
  long getTotalCount();

  /**
   * Find all.
   * @return  all instances
   */
  Collection<T> findAll();

  /**
   * Find an instance by primary key.
   * @param pk  the primary key
   * @return  the instance
   */
  T findByPrimaryKey(Serializable pk);

  /**
   * Insert a new instance.
   * @param t  the new instance
   * @return  the instance updated with its primary key
   */
  T create(T t);

  /**
   * Save changes to an existing instance.
   * @param t  the instance
   * @return  the updated & refreshed instance
   */
  T update(T t);

  /**
   * Delete an existing instance.
   * @param t  the instance
   */
  void delete(T t);
}

and the new base implementation class:

package com.foo.bar.dao.jpa;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Date;

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

import org.apache.log4j.Logger;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.foo.bar.annotation.DependencyInjection;
import com.foo.bar.dao.BaseDAO;
import com.foo.bar.model.DatabaseObject;

/**
 * Abstract base class for Hibernate DAOs.
 *
 * @author Burt
 * @param <T> the entity type
 */
@Transactional
@Repository
public abstract class BaseDaoImpl<T extends DatabaseObject>
       implements BaseDAO<T> {

  protected final Logger _log = Logger.getLogger(getClass());

  private Class _entityClass;

  private EntityManager _entityManager;

  /**
   * Inject the entity manager.
   * @param entityManager
   */
  @PersistenceContext
  @DependencyInjection
  public void setEntityManager(final EntityManager entityManager) {
    _entityManager = entityManager;
  }
  protected EntityManager getEntityManager() {
    return _entityManager;
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#getTotalCount()
   */
  public long getTotalCount() {
    return (Long)getEntityManager().createQuery(
      "SELECT COUNT(*) FROM " + getEntityClass().getSimpleName())
      .getSingleResult();
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  @SuppressWarnings("unchecked")
  public Collection<T> findAll() {
    return getEntityManager().createQuery(
        "FROM " + getEntityClass().getName())
        .getResultList();
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey(java.io.Serializable)
   */
  @SuppressWarnings("unchecked")
  public T findByPrimaryKey(final Serializable pk) {
    return (T)getEntityManager().find(getEntityClass(), pk);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#create(com.foo.bar.model.DatabaseObject)
   */
  public T create(final T t) {
    getEntityManager().persist(t);
    return t;
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#update(com.foo.bar.model.DatabaseObject)
   */
  public T update(final T t) {
    t.setModified(new Date());
    getEntityManager().merge(t);
    return findByPrimaryKey(t.getPk());
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#delete(com.foo.bar.model.DatabaseObject)
   */
  public void delete(final T t) {
    getEntityManager().remove(t);
  }

  protected synchronized Class getEntityClass() {

    if (_entityClass == null) {
      Type type = getClass().getGenericSuperclass();
      loop:
      while (true) {
        if (type instanceof ParameterizedType) {
          Type[] arguments =1) {
              _entityClass = (Class)argument;
              break loop;
            }
          }
        }
        type = ((Class)type).getGenericSuperclass();
        if (type == Object.class) {
          throw new RuntimeException(
              "Could not find a DatabaseObject subclass parameterized type");
        }
      }
    }
    return _entityClass;
  }
}
  1. ParameterizedType)type).getActualTypeArguments(); for (Type argument : arguments) { if (argument instanceof Class && DatabaseObject.class.isAssignableFrom(((Class)argument[back]

Autodiscovery of Hibernate Mapping Files in Spring

Wednesday, September 06th, 2006

I’m a big fan of autodiscovery – I dislike having to specify information that can be inferred programmatically. So along those lines, here’s a subclass of Spring’s LocalSessionFactoryBean (AutodiscoverSessionFactoryBean) that finds Hibernate hbm.xml mapping files in your classpath.
(more…)

Using Generics to Simplify Hibernate/Spring DAOs (update)

Sunday, July 16th, 2006

Tim noted in the comments of my previous post that it’s possible to use reflection to determine the parameterized type of the entity class. It’s a great enhancement and drops simple DAOs that only use the standard base DAO methods down to zero LOC (other than the class definition and imports). Cool.

The updated base DAO implementation class:

(see getEntityClass())

package com.foo.bar.dao.hibernate;

import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Expression;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.foo.bar.dao.BaseDAO;
import com.foo.bar.model.DatabaseObject;
import com.foo.bar.model.User;

/**
 * Abstract base class for Hibernate DAOs.
 *
 * @author Burt
 * @param <T> the entity type
 */
public abstract class BaseDaoImpl<T extends DatabaseObject>
       extends HibernateDaoSupport
       implements BaseDAO<T> {

  private Class _entityClass;

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  public Set<T> findAll() {
    return findAll(false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  @SuppressWarnings("unchecked")
  public Set<T> findAll(final boolean includeDeleted) {
    return (Set<T>)getHibernateTemplate().execute(new HibernateCallback() {
        public Object doInHibernate(final Session session) {
          Criteria criteria = session.createCriteria(getEntityClass());
          if (!includeDeleted) {
            criteria.add(Expression.isNull("deleted"));
          }
          return new HashSet<T>(criteria.list());
        }
      });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey(int)
   */
  public T findByPrimaryKey(final int pk) {
    return findByPrimaryKey(pk, false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey()
   */
  @SuppressWarnings("unchecked")
  public T findByPrimaryKey(final int pk, final boolean includeDeleted) {

    return (T)getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(final Session session) {
        T instance = (T)session.get(getEntityClass(), pk);
        if (instance != null && (includeDeleted || !instance.isDeleted())) {
          return initializeIfNotNull(instance);
        }
        return null;
      }
    });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByName()
   */
  public T findByName(final String name) {
    return findByName(name, false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByName()
   */
  @SuppressWarnings("unchecked")
  public T findByName(final String name, final boolean includeDeleted) {

    return (T)getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(final Session session) {
        Criteria criteria = session.createCriteria(getEntityClass());
        criteria.add(Expression.eq(getNameFieldName(), name));
        if (!includeDeleted) {
          criteria.add(Expression.isNull("deleted"));
        }
        return initializeIfNotNull(criteria.uniqueResult());
      }
    });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#create()
   */
  public T create(final T t) {
    getHibernateTemplate().saveOrUpdate(t);
    return t;
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#update()
   */
  public void update(final T t, final User updatedBy) {
    t.setModified(new Timestamp(System.currentTimeMillis()));
    t.setModifiedBy(updatedBy);
    getHibernateTemplate().update(t);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#delete()
   */
  public void delete(final T t, final User deletedBy) {
    getHibernateTemplate().delete(t);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#softDelete()
   */
  public void softDelete(final T t, final User deletedBy) {
    t.setDeleted(new Timestamp(System.currentTimeMillis()));
    update(t, deletedBy);
  }

  @SuppressWarnings("unchecked")
  private Object initializeIfNotNull(final Object instance) {
    if (instance != null) {
      initialize((T)instance);
    }
    return instance;
  }

  protected void initialize(@SuppressWarnings("unused") T instance) {
    // let subclass do special initialization
  }

  protected synchronized Class getEntityClass() {

    if (_entityClass == null) {
      Type type = getClass().getGenericSuperclass();
      loop:
      while (true) {
        if (type instanceof ParameterizedType) {
          Type[] arguments = ( (ParameterizedType)type).getActualTypeArguments();
          for (Type argument : arguments) {
            if (argument instanceof Class &&
                DatabaseObject.class.isAssignableFrom(((Class)argument))) {
              _entityClass = (Class)argument;
              break loop;
            }
          }
        }
        type = ((Class)type).getGenericSuperclass();
        if (type == Object.class) {
          throw new RuntimeException(
              "Could not find a DatabaseObject subclass parameterized type");
        }
      }
    }
    return _entityClass;
  }

  /**
   * Each entity has a 'name' but not necessarily
   * called 'name'.
   * @return  the name's field name
   */
  protected String getNameFieldName() {
    return "name";
  }
}

And the updated sample implementation class:

package com.foo.bar.dao.hibernate;

import com.foo.bar.dao.hibernate.BaseDaoImpl;
import com.foo.bar.dao.ErrorAnnotationDAO;
import com.foo.bar.model.ErrorAnnotation;

/**
 * Hibernate implementation of
 * <code>ErrorAnnotationDAO</code>.
 *
 * @author Burt
 */
public class ErrorAnnotationDaoImpl
       extends BaseDaoImpl<ErrorAnnotation>
       implements ErrorAnnotationDAO {
}

Using Generics to Simplify Hibernate/Spring DAOs

Saturday, July 15th, 2006

Database access always involves a significant amount of boilerplate code, especially for standard CRUD operations. Complex relationships and queries make things interesting, but the bulk of the database code involves reading individual instances (looked up by primary key, ‘name’, etc.), storing new instances, editing instances and deleting. I’ve become a big fan of the Hibernate approach to ORM, and Spring‘s helper classes significantly reduce the amount of coding required. But for each entity there’s still the same nearly identical code for each DAO implementation class, and for their interfaces too.

So I’ve developed a base interface and base implementation class that use Java5 generics to reduce the code for each DAO to almost nothing for DAOs that only perform the “standard” operations.

The DAOs manage DatabaseObjects which extend DataObject and contain fields and getters and setters for primary key, user who created, time created, user who last edited, time last edited, and the user and time that the instance was “soft” deleted, i.e. flagged as deleted to hide from the UI but still in the database.

The base interface:1

package com.foo.bar.dao;

import java.util.Set;

import com.foo.bar.model.DatabaseObject;
import com.foo.bar.model.User;

/**
 * Base DAO.
 * @author Burt
 * @param <T> the entity type
 */
public interface BaseDAO<T extends DatabaseObject> {

  /**
   * Find all.
   * @param includeDeleted
   * @return  all instances
   */
  Set<T> findAll(boolean includeDeleted);

  /**
   * Find all.
   * @return  all instances
   */
  Set<T> findAll();

  /**
   * Find an instance by primary key.
   * @param pk  the primary key
   * @param includeDeleted
   * @return  the instance
   */
  T findByPrimaryKey(int pk, boolean includeDeleted);

  /**
   * Find an instance by primary key.
   * @param pk  the primary key
   * @return  the instance
   */
  T findByPrimaryKey(int pk);

  /**
   * Find an instance by name.
   * @param name  the name
   * @param includeDeleted
   * @return  the instance
   */
  T findByName(String name, boolean includeDeleted);

  /**
   * Find an instance by name.
   * @param name  the name
   * @return  the instance
   */
  T findByName(String name);

  /**
   * Insert a new instance.
   * @param t  the new instance
   * @return  the instance updated with its primary key
   */
  T create(T t);

  /**
   * Save changes to an existing instance.
   * @param t  the instance
   * @param updatedBy
   */
  void update(T t, User updatedBy);

  /**
   * Delete an existing instance.
   * @param t  the instance
   * @param deletedBy
   */
  void delete(T t, User deletedBy);

  /**
   * Soft delete (hide) an existing instance.
   * @param t  the instance
   * @param deletedBy
   */
  void softDelete(T t, User deletedBy);
}

And the base implementation class:

package com.foo.bar.dao.hibernate;

import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Expression;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.foo.bar.dao.BaseDAO;
import com.foo.bar.model.DatabaseObject;
import com.foo.bar.model.User;

/**
 * Abstract base class for Hibernate DAOs.
 *
 * @author Burt
 * @param <T> the entity type
 */
public abstract class BaseDaoImpl<T extends DatabaseObject>
       extends HibernateDaoSupport
       implements BaseDAO<T> {

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  public Set<T> findAll() {
    return findAll(false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  @SuppressWarnings("unchecked")
  public Set<T> findAll(final boolean includeDeleted) {
    return (Set<T>)getHibernateTemplate().execute(new HibernateCallback() {
        public Object doInHibernate(final Session session) {
          Criteria criteria = session.createCriteria(getEntityClass());
          if (!includeDeleted) {
            criteria.add(Expression.isNull("deleted"));
          }
          return new HashSet<T>(criteria.list());
        }
      });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey(int)
   */
  public T findByPrimaryKey(final int pk) {
    return findByPrimaryKey(pk, false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey()
   */
  @SuppressWarnings("unchecked")
  public T findByPrimaryKey(final int pk, final boolean includeDeleted) {
    return (T)getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(final Session session) {
        T instance = (T)session.get(getEntityClass(), pk);
        if (instance != null && (includeDeleted || !instance.isDeleted())) {
          return initializeIfNotNull(instance);
        }
        return null;
      }
    });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByName()
   */
  public T findByName(final String name) {
    return findByName(name, false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByName()
   */
  @SuppressWarnings("unchecked")
  public T findByName(final String name, final boolean includeDeleted) {
    return (T)getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(final Session session) {
        Criteria criteria = session.createCriteria(getEntityClass());
        criteria.add(Expression.eq(getNameFieldName(), name));
        if (!includeDeleted) {
          criteria.add(Expression.isNull("deleted"));
        }
        return initializeIfNotNull(criteria.uniqueResult());
      }
    });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#create()
   */
  public T create(final T t) {
    getHibernateTemplate().saveOrUpdate(t);
    return t;
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#update()
   */
  public void update(final T t, final User updatedBy) {
    t.setModified(new Timestamp(System.currentTimeMillis()));
    t.setModifiedBy(updatedBy);
    getHibernateTemplate().update(t);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#delete()
   */
  public void delete(final T t, final User deletedBy) {
    getHibernateTemplate().delete(t);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#softDelete()
   */
  public void softDelete(final T t, final User deletedBy) {
    t.setDeleted(new Timestamp(System.currentTimeMillis()));
    update(t, deletedBy);
  }

  @SuppressWarnings("unchecked")
  private Object initializeIfNotNull(final Object instance) {
    if (instance != null) {
      initialize((T)instance);
    }
    return instance;
  }

  protected void initialize(@SuppressWarnings("unused") T instance) {
    // let subclass do special initialization
  }

  /**
   * Can't determine T.getclass() due to erasure,
   * so must declare the class redundantly.
   * @return  the entity class
   */
  protected abstract Class getEntityClass();

  /**
   * Each entity has a 'name' but not necessarily
   * called 'name'.
   * @return  the name's field name
   */
  protected String getNameFieldName() {
    return "name";
  }
}

And a sample DAO:

package com.foo.bar.dao;

import com.foo.bar.dao.BaseDAO;
import com.foo.bar.model.ErrorAnnotation;

/**
 * DAO for <code>ErrorAnnotation</code>.
 * @author Burt
 */
public interface ErrorAnnotationDAO extends BaseDAO<ErrorAnnotation> {
  // no other methods
}

And its implementation:

(uses only base interface methods, so the only required method is getEntityClass)

package com.foo.bar.dao.hibernate;

import com.foo.bar.dao.hibernate.BaseDaoImpl;
import com.foo.bar.dao.ErrorAnnotationDAO;
import com.foo.bar.model.ErrorAnnotation;

/**
 * Hibernate implementation of
 * <code>ErrorAnnotationDAO</code>.
 *
 * @author Burt
 */
public class ErrorAnnotationDaoImpl
       extends BaseDaoImpl<ErrorAnnotation>
       implements ErrorAnnotationDAO {

  @Override
  protected Class getEntityClass() {
    return ErrorAnnotation.class;
  }
}

Of course client usage is the same as if I’d copy/pasted the boilerplate code thanks to the generic types, e.g.:

ErrorAnnotationDAO dao = ...
ErrorAnnotation errorAnnotation = dao.findByPrimaryKey(12345); // no need to cast
...
Collection<ErrorAnnotation> allErrorAnnotations = dao.findAll(); // no need to cast
...
dao.create(new ErrorAnnotation(...));
...
ErrorAnnotation errorAnnotation = ...
errorAnnotation.setFoo('foo');
errorAnnotation.setBar('bar');
User user = ...
dao.update(errorAnnotation, user);

The one downside is the need to suppress unchecked warnings, but Hibernate guarantees that all instances are of the appropriate class so that’s just to get past the compiler.

  1. Note that the update and delete methods take a User parameter to record the user performing the action. create() doesn’t though because DatabaseObject have by convention a User parameter in their constructors.[back]

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.