Generics example: the Data Access Object
One very useful application of generics is in the Data Access Object pattern, especially when using JPA or Hibernate. Using generics, this pattern can almost evolve into the 'One DAO to Rule Them All' pattern 
Let's start with the interface for such generic DAO. You can download the source code at the bottom of this page.
package be.bzbit.framework.domain.repository;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* Generic Repository, providing basic CRUD operations
*
* @author Jurgen Lust
* @author $LastChangedBy: jlust $
*
* @version $LastChangedRevision: 257 $
*
* @param <T> the entity type
* @param <ID> the primary key type
*/
public interface GenericRepository<T, ID extends Serializable> {
/**
* Get the Class of the entity.
*
* @return the class
*/
Class<T> getEntityClass();
/**
* Find an entity by its primary key
*
* @param id the primary key
* @return the entity
*/
T findById(final ID id);
/**
* Load all entities.
*
* @return the list of entities
*/
List<T> findAll();
/**
* Find entities based on an example.
*
* @param exampleInstance the example
* @return the list of entities
*/
List<T> findByExample(final T exampleInstance);
/**
* Find using a named query.
*
* @param queryName the name of the query
* @param params the query parameters
*
* @return the list of entities
*/
List<T> findByNamedQuery(
final String queryName,
Object... params
);
/**
* Find using a named query.
*
* @param queryName the name of the query
* @param params the query parameters
*
* @return the list of entities
*/
List<T> findByNamedQueryAndNamedParams(
final String queryName,
final Map<String, ?extends Object> params
);
/**
* Count all entities.
*
* @return the number of entities
*/
int countAll();
/**
* Count entities based on an example.
*
* @param exampleInstance the search criteria
* @return the number of entities
*/
int countByExample(final T exampleInstance);
/**
* save an entity. This can be either a INSERT or UPDATE in the database.
*
* @param entity the entity to save
*
* @return the saved entity
*/
T save(final T entity);
/**
* delete an entity from the database.
*
* @param entity the entity to delete
*/
void delete(final T entity);
}
And now the JPA implementation:
package be.bzbit.framework.domain.repository.jpa;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Projections;
import org.springframework.beans.factory.annotation.Required;
import be.bzbit.framework.domain.repository.GenericRepository;
/**
* JPA implementation of the GenericRepository. Note that this implementation
* also expects Hibernate as JPA implementation. That's because we like the
* Criteria API.
*
* @author Jurgen Lust
* @author $LastChangedBy: jlust $
*
* @version $LastChangedRevision: 257 $
*
* @param <T>
* The persistent type
* @param <ID>
* The primary key type
*/
public class GenericJpaRepository<T, ID extends Serializable> implements
GenericRepository<T, ID> {
private final Class<T> persistentClass;
private EntityManager entityManager;
@SuppressWarnings("unchecked")
public GenericJpaRepository() {
this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public GenericJpaRepository(final Class<T> persistentClass) {
super();
this.persistentClass = persistentClass;
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository#countAll()
*/
@Override
public int countAll() {
return countByCriteria();
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository#countByExample(java.lang.Object)
*/
@Override
public int countByExample(final T exampleInstance) {
Session session = (Session) getEntityManager().getDelegate();
Criteria crit = session.createCriteria(getEntityClass());
crit.setProjection(Projections.rowCount());
crit.add(Example.create(exampleInstance));
return (Integer) crit.list().get(0);
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository#findAll()
*/
@Override
public List<T> findAll() {
return findByCriteria();
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository#findByExample(java.lang.Object)
*/
@SuppressWarnings("unchecked")
@Override
public List<T> findByExample(final T exampleInstance) {
Session session = (Session) getEntityManager().getDelegate();
Criteria crit = session.createCriteria(getEntityClass());
final List<T> result = crit.list();
return result;
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository#findById(java.io.Serializable)
*/
@Override
public T findById(final ID id) {
final T result = getEntityManager().find(persistentClass, id);
return result;
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository
* #findByNamedQuery(java.lang.String, java.lang.Object[])
*/
@SuppressWarnings("unchecked")
@Override
public List<T> findByNamedQuery(final String name, Object... params) {
javax.persistence.Query query = getEntityManager().createNamedQuery(
name);
for (int i = 0; i < params.length; i++) {
query.setParameter(i + 1, params[i]);
}
final List<T> result = (List<T>) query.getResultList();
return result;
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository
* #findByNamedQueryAndNamedParams(java.lang.String, java.util.Map)
*/
@SuppressWarnings("unchecked")
@Override
public List<T> findByNamedQueryAndNamedParams(final String name,
final Map<String, ? extends Object> params) {
javax.persistence.Query query = getEntityManager().createNamedQuery(
name);
for (final Map.Entry<String, ? extends Object> param : params
.entrySet()) {
query.setParameter(param.getKey(), param.getValue());
}
final List<T> result = (List<T>) query.getResultList();
return result;
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository#getEntityClass()
*/
@Override
public Class<T> getEntityClass() {
return persistentClass;
}
/**
* set the JPA entity manager to use.
*
* @param entityManager
*/
@Required
@PersistenceContext
public void setEntityManager(final EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
/**
* Use this inside subclasses as a convenience method.
*/
protected List<T> findByCriteria(final Criterion... criterion) {
return findByCriteria(-1, -1, criterion);
}
/**
* Use this inside subclasses as a convenience method.
*/
@SuppressWarnings("unchecked")
protected List<T> findByCriteria(final int firstResult,
final int maxResults, final Criterion... criterion) {
Session session = (Session) getEntityManager().getDelegate();
Criteria crit = session.createCriteria(getEntityClass());
for (final Criterion c : criterion) {
crit.add(c);
}
if (firstResult > 0) {
crit.setFirstResult(firstResult);
}
if (maxResults > 0) {
crit.setMaxResults(maxResults);
}
final List<T> result = crit.list();
return result;
}
protected int countByCriteria(Criterion... criterion) {
Session session = (Session) getEntityManager().getDelegate();
Criteria crit = session.createCriteria(getEntityClass());
crit.setProjection(Projections.rowCount());
for (final Criterion c : criterion) {
crit.add(c);
}
return (Integer) crit.list().get(0);
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository#delete(java.lang.Object)
*/
@Override
public void delete(T entity) {
getEntityManager().remove(entity);
}
/**
* @see be.bzbit.framework.domain.repository.GenericRepository
* #save(java.lang.Object)
*/
@Override
public T save(T entity) {
final T savedEntity = getEntityManager().merge(entity);
return savedEntity;
}
}
Now say you need a DAO for classes of type Person, using a Long as primary key. The code for this becomes very simple.
Again, first the interface:
package org.bejug.dao;
import org.bejug.model.Person;
import be.bzbit.framework.domain.repository.GenericRepository;
public interface PersonRepository extends GenericRepository<Person, Long> {
}
And the JPA implementation:
package org.bejug.dao;
import org.bejug.model.Person;
import be.bzbit.framework.domain.repository.jpa.GenericJpaRepository;
public class PersonJpaRepository extends GenericJpaRepository<Person, Long>
implements PersonRepository {
}
Nice and short, isn't it? You can of course add specific
methods to your classes, but the basic CRUD operations are there, you no longer need to implement them for every model class in your project.