Problem

Most likely, each DAO class implements basic CRUD operations. Since these are the same for all possible entities, it makes perfect sense to have one generic DAO class which implements common CRUDs and extend it when necessary adding anything which is required by a specific entity instance.

Simple generic DAO should implement the following interface:

SimpleDao.java

package com.application.sample;

import java.io.Serializable;

public interface SimpleDao <PK extends Serializable, E extends AbstractEntity <PK>> {
  public E create(E entity);
  public E read(PK id);
  public E update(E entity);
  public void delete(PK id);
}

When implementing generic DAO using Hibernate as JPA provider we are facing the problem. Hibernate load and get operations require information about object being retrieved. Either object class, class name or the object instance has to be provided before actual read operation can be executed. None of these information is accessible before generic DAO is extended and instantiated. I can think of two possible solutions, an obvious one – asking implementation to deliver required information, and nice one – using java.lang.reflection.ParameterizedType.

Lets assume, I have an abstract entity, which defines fields common to all entities in my application:

AbstractEntity .java

package com.application.sample;

import java.io.Serializable;

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

@MappedSuperclass
public abstract class AbstractEntity <PK extends Serializable> implements Serializable {
  private static final long serialVersionUID = -6991226731574845156L;

  public AbstractEntity () {
  }

  private PK primaryKey;
  private Integer version;

  @Transient
  public PK getPrimaryKey () {
    return primaryKey;
  }

  @Version
  @Column(name="version", nullable=false)
  public Integer getVersion() {
    return version;
  }

  public void setPrimaryKey (PK primaryKey) {
    this.primaryKey = primaryKey;
  }

  public void setVersion(Integer version) {
    this.version = version;
  }

}

And Employee entity:

Employee.java

package com.application.sample;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.Index;

@Entity
@Table(name = "EMPLOYEES")
@org.hibernate.annotations.Table(
  indexes = {@Index(name="lastNameIdx", columnNames={"last_name"} )},
  appliesTo = "EMPLOYEES"
)
public class Employee extends AbstractEntity <Integer> {
  private static final long serialVersionUID = -1693502528557287614L;

  private String firstName = null;
  private String lastName = null;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return getPrimaryKey();
  }

  @Column(name = "first_name")
  public String getFirstName() {
    return firstName;
  }

  @Column(name = "last_name")
  public String getLastName() {
    return lastName;
  }

  public void setEmployeeId(Integer employeeId) {
    setPrimaryKey(employeeId);
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

}

For an Employee entity I want to have a possibility to find employees by their last name, so I add the required method the specification:

EmployeeDao.java

package com.application.sample;

import java.util.List;

public interface EmployeeDao extends SimpleDao<Integer, Employee> {

  public List<Employee> readEmployeeByLastName(String lastName);

}


Solution one – pass the buck.

Generic DAO implementation:

AbstractPassTheBuckDao.java

package com.application.sample;

import java.io.Serializable;

import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class AbstractPassTheBuckDao<PK extends Serializable, E extends AbstractEntity <PK>>
       extends HibernateDaoSupport implements SimpleDao <PK, E>   {

  public AbstractPassTheBuckDao() {
  }

  protected AbstractPassTheBuckDao(SessionFactory sessionFactory) {
    this();
    this.setSessionFactory(sessionFactory);
  }

  // Ask the implementation for entity class
  abstract protected Class<E> getPersistentClass ();

  public E create(E entity) {
    this.getHibernateTemplate().save(entity);
    return entity;
  }

  @SuppressWarnings("unchecked")
  public E read(PK id) {
    E entity = (E) this.getHibernateTemplate().get(getPersistentClass(), id);
    return entity;
  }

  public E update(E entity) {
    this.getHibernateTemplate().merge(entity);
    return entity;
  } 

  public void delete(PK id) {
    this.getHibernateTemplate().delete(read(id));
  }

}

Employee DAO implementation:

EmployeePassTheBuckDao.java

package com.application.sample;

import java.util.List;

public class EmployeePassTheBuckDao
       extends AbstractPassTheBuckDao <Integer, Employee> implements EmployeeDao {

  // Deliver entity class
  protected Class<Employee> getPersistentClass() {
    return Employee.class;
  }

  @SuppressWarnings("unchecked")
  public List<Employee> readEmployeeByLastName(String lastName) {
    String queryString = "from Employee where lastName = :lastName";
    String paramName = "lastName";
    return (List<Employee>) this.getHibernateTemplate().findByNamedParam(queryString, paramName, lastName);
  }

}


Solution two – do it yourself.

Generic DAO implementation:

AbstractDoItYourselfDao.java

package com.application.sample;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class AbstractDoItYourselfDao<PK extends Serializable, E extends AbstractEntity <PK>>
       extends HibernateDaoSupport implements SimpleDao <PK, E>   {

  private Class<E> persistentClass;

  @SuppressWarnings("unchecked")
  public AbstractDoItYourselfDao() {
    // retrieve entity class
    ParameterizedType paramType = null;
    Type type = getClass().getGenericSuperclass();
    if (type instanceof ParameterizedType) {
      paramType = (ParameterizedType) type;
    } else {
      paramType = (ParameterizedType) getClass().getSuperclass().getGenericSuperclass();
    }

    if (paramType.getActualTypeArguments().length == 2) {
      if (paramType.getActualTypeArguments()[1] instanceof TypeVariable) {
        throw new IllegalArgumentException("Could not persistent entity class using reflection");
      } else {
        persistentClass = (Class<E>) paramType.getActualTypeArguments()[1];
      }
    } else {
      persistentClass = (Class<E>) paramType.getActualTypeArguments()[0];
    }

  }

  protected AbstractDoItYourselfDao(SessionFactory sessionFactory) {
    this();
    this.setSessionFactory(sessionFactory);
  }

  // deliver entity class
  protected Class<E> getPersistentClass () {
    return persistentClass;
  }

  public E create(E entity) {
    this.getHibernateTemplate().save(entity);
    return entity;
  }

  @SuppressWarnings("unchecked")
  public E read(PK id) {
    E entity = (E) this.getHibernateTemplate().get(getPersistentClass(), id);
    return entity;
  }

  public E update(E entity) {
    this.getHibernateTemplate().merge(entity);
    return entity;
  } 

  public void delete(PK id) {
    this.getHibernateTemplate().delete(read(id));
  }

}

Employee DAO implementation:

EmployeeDoItYourselfDao .java

package com.application.sample;

import java.util.List;

public class EmployeeDoItYourselfDao
       extends AbstractDoItYourselfDao <Integer, Employee> implements EmployeeDao {

  @SuppressWarnings("unchecked")
  public List<Employee> readEmployeeByLastName(String lastName) {
    String queryString = "from Employee where lastName = :lastName";
    String paramName = "lastName";
    return (List<Employee>) this.getHibernateTemplate().findByNamedParam(queryString, paramName, lastName);
  }

}

As seen above, the EmployeeDao implementation implements only Employee related operations and there is no need to deliver Employee class to generic DAO superclass.