Archive for the Category 'spring'

A Minimal Grails JMS Integration

Thursday, August 28th, 2008

We’re implementing some asynchronous processing at work and naturally JMS seems like the right choice. We looked at the JMS plugin, and it is helpful but you need to configure a JMS provider yourself. It’d certainly be more helpful if it configured a provider for you, e.g. ActiveMQ, while making it easy to configure another if you want.

We were having some classloader issues with ActiveMQ, so I configured a new empty app just to get that working, without the clutter of all of our code, the other various plugins, etc. I thought I had everything working – at least the app started – but although I didn’t get errors sending test messages, I wasn’t seeing any output in the console from the listener I’d configured.

It turns out it was a Groovy issue – I was trying to send a text message but ended up sending a GString, which was sent as an ObjectMessage, so when I called “message.getText()” in onMessage() it failed, and the Spring wrapper class just logs the exception so I didn’t see anything.

So anyway, I thought I’d post the app I created to help others get started. It’s intentionally very basic – it uses an in-memory instance of ActiveMQ (so there’s no extra step of starting an external server) and configures a single queue, plus a single listener and a simple Controller method to send test messages. It doesn’t use the JMS plugin, but Spring’s excellent JmsTemplate makes sending message trivial. Adding other queues, listeners, and senders would be simple by using the existing configuration as a guide.

Download the sample app here. Run “grails run-app” and navigate to http://localhost:8080/jms/jms and you should see a message in the console after the receiver logs the receipt of the message that the JmsController sends.

Using Spring MVC Controllers in Grails

Thursday, March 27th, 2008

Update 03/22/2010: As of version 1.2 Grails has support for Spring MVC controllers; check out the 1.2 release notes

Groovy is slower than Java and sometimes dramatically slower. Realistically, this has little impact on a web application since response time is affected more by the database and network latency, so as long as the slowdown isn’t too dramatic, the benefits of Groovy and Grails far outweigh these concerns. And Grails is still way faster than Rails šŸ™‚

But having said that, I was wondering how to use a regular Java Spring MVC controller and JSP instead of a Grails controller and a GSP (both of which use Groovy). Turns out it’s pretty easy:

  • Register the traditional Spring dispatcher servlet in web.xml (you’ll need to have run grails install-templates). In this example the name (SpringMVC) isn’t important, use whatever you want, and I’ve chosen to map *.action URLs to this controller and let Grails handle the rest:
<servlet>
   <servlet-name>SpringMVC</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
   <servlet-name>SpringMVC</servlet-name>
   <url-pattern>*.action</url-pattern>
</servlet-mapping>
  • Generate web-app/WEB-INF/SpringMVC-servlet.xml:
<?xml version='1.0' encoding='UTF-8'?>

<beans xmlns='http://www.springframework.org/schema/beans'
   xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
   xmlns:p='http://www.springframework.org/schema/p'
   xmlns:lang='http://www.springframework.org/schema/lang'
   xsi:schemaLocation='
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
      http://www.springframework.org/schema/lang
      http://www.springframework.org/schema/lang/spring-lang-2.5.xsd'>

   <bean id='mvcHandlerMapping'
      class='org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
      p:order='1'>
      <property name='interceptors'>
         <list>
            <ref bean='openSessionInViewInterceptor' />
            <ref bean='localeChangeInterceptor' />
         </list>
      </property>
   </bean>

   <bean id='mvcViewResolver'
      class='org.springframework.web.servlet.view.UrlBasedViewResolver'
      p:viewClass='org.springframework.web.servlet.view.InternalResourceView'
      p:order='1'
      p:prefix='/WEB-INF/jsp/'
      p:suffix='.jsp'
   />

   <bean name='baseSimpleController' abstract='true' p:cacheSeconds='0'/>

   <bean name='jspController'
      class='com.foo.spring.controller.JspController'
      parent='baseSimpleController'
      abstract='true'
   />

   <!-- actions -->

   <bean name='/test.action'
      class='com.foo.spring.controller.TestController'
      parent='baseSimpleController'
      p:successView='test'
   />

   <bean name='/other.action' parent='jspController' p:successView='other' />

</beans>

And that’s it. Some notes:

  • the handler mapping uses the id mvcHandlerMapping since Grails will create one using the standard name of handlerMapping
  • since handler mappings are auto-discovered by default, you need to set the order attribute to something lower than the Grails mapping’s (which uses the default value of Integer.MAX_VALUE) so this mapping is accessed first
  • the HandlerInterceptors that are configured for the Grails mapping (OpenSessionInView, LocaleChange) won’t be automatically available to this mapping, but it’s simple to borrow them since they’re registered as beans; you can also add other custom interceptors to the list
  • I’ve created an optional abstract parent controller bean (baseSimpleController) for simple controllers (single-page, i.e. not form or wizard controllers)
  • I’ve also created a simple controller that just shows a JSP ā€“ this is useful for pages that don’t have any controller logic:
    package com.foo.spring.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.AbstractController;
    
    public class JspController extends AbstractController {
    
       private String _successView;
    
       @Override
       protected ModelAndView handleRequestInternal(
             final HttpServletRequest request,
             final HttpServletResponse response) {
    
          return new ModelAndView(_successView);
       }
    
       public void setSuccessView(final String view) {
          _successView = view;
       }
    }
    

I’ve mapped two sample URLs ā€“ /test.action, which uses a controller, and /other.action, which uses JspController to just show other.jsp.

Note that it is possible to use JSPs with Grails; Grails looks for a GSP using the specified name, but if it doesn’t find one it looks for a JSP (under /WEB-INF/grails-app/views/) and uses that if it exists. So another option is to use Grails controllers and JSP.

Big caveat: I haven’t used this in production yet ā€“ I’m just prototyping so I’ll have this available in the future just in case.

WTF?

Thursday, November 29th, 2007

This is an actual email conversation from work, with the names changed to keep me from getting fired šŸ™‚
“Bob” is my manager, and “Ray” is his:

From: Burt Beckwith
Sent: Tuesday, November 20, 2007 11:26 AM
To: Bob
Cc: Ray
Subject: The Spring Experience

Might be a good idea to send one or more developers to this ā€“ Spring 2.5 was just released and the speaker list and session descriptions look great.

The conference costs ~$1500, and it looks like travel costs would be around $1000 for flight, hotel, and car (assuming 1 person) with food/gas/etc extra.

http://www.thespringexperience.com/conference/
hollywood/2007/12/index.html

Burt

A quick reply:

From: Bob
Sent: Tuesday, November 20, 2007 11:28 AM
To: Burt Beckwith
Cc: Ray
Subject: RE: The Spring Experience

Thanks Burt. I will discuss with Ray.

and then nothing for over a week. I assumed for various reasons that the answer would be no, but the conference is getting close, so I thought I’d check and see:

From: Burt Beckwith
To: Bob
Cc: Ray
Sent: Thu Nov 29 17:44:46 2007
Subject: RE: The Spring Experience

Iā€™m assuming from the deafening silence that nobody will be attending from the company?

Burt

and the unbelievable reply:

We are sending Bob

Sent by BlackBerry

Ray

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.