Archive for July 16th, 2006

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 {
}

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