Simplifying the Data Access Layer with Spring and Java Generics
1. Overview
This article will focus on simplifying the DAO layer by using a single, generified Data Access Object for all entities in the system, which will result in elegant data access, with no unnecessary clutter or verbosity.
2. The Hibernate and JPA DAOs
Most production codebases have some kind of DAO layer. Usually the implementation ranges from multiple classes with no abstract base class to some kind of generified class. However, one thing is consistent – there is always more then one – most likely, there is a one to one relation between the DAOs and the entities in the system.
Also, depending on the level of generics involved, the actual implementations can vary from heavily duplicated code to almost empty, with the bulk of the logic grouped in a base abstract class.
These multiple implementations can usually be replaced by a single parametrized DAO used in such no functionality is lost by taking full advantage of the type safety provided by Java Generics.
Two implementations of this concept are presented next, one for a Hibernate centric persistence layer and the other focusing on JPA. These implementation are by no means complete – only some data access methods are included, but they can be easily be made more thorough.
2.1. The Abstract Hibernate DAO
public abstract class AbstractHibernateDao< T extends Serializable > { private Class< T > clazz; @Autowired SessionFactory sessionFactory; public final void setClazz( Class< T > clazzToSet ){ this.clazz = clazzToSet; } public T findOne( long id ){ return (T) getCurrentSession().get( clazz, id ); } public List< T > findAll(){ return getCurrentSession().createQuery( "from " + clazz.getName() ).list(); } public void create( T entity ){ getCurrentSession().persist( entity ); } public void update( T entity ){ getCurrentSession().merge( entity ); } public void delete( T entity ){ getCurrentSession().delete( entity ); } public void deleteById( long entityId ){ T entity = findOne( entityId ); delete( entity ); } protected final Session getCurrentSession(){ return sessionFactory.getCurrentSession(); } }
The DAO uses the Hibernate API directly, without relying on any Spring templates (such as HibernateTemplate). Using of templates, as well as management of the SessionFactory which is autowired in the DAO were covered in the Hibernate DAO tutorial.
2.2. The Generic Hibernate DAO
Now that the abstract DAO is done, we can implement it just once – the generic DAO implementation will become the only implementation needed:
@Repository @Scope( BeanDefinition.SCOPE_PROTOTYPE ) public class GenericHibernateDao< T extends Serializable > extends AbstractHibernateDao< T > implements IGenericDao< T >{ // }
First, note that the generic implementation is itself parametrized – allowing the client to choose the correct parameter in a case by case basis. This will mean that the clients gets all the benefits of type safety without needing to create multiple artifacts for each entity.
Second, notice the prototype scope of these generic DAO implementation. Using this scope means that the Spring container will create a new instance of the DAO each time it is requested (including on autowiring). That will allow a service to use multiple DAOs with different parameters for different entities, as needed.
The reason this scope is so important is due to the way Spring initializes beans in the container. Leaving the generic DAO without a scope would mean using the default singleton scope, which would lead to a single instance of the DAO living in the container. That would obviously be majorly restrictive for any kind of more complex scenario.
The IGenericDao is simply an interface for all the DAO methods, so that we can inject our implementation with Spring in (or in whatever is needed):
public interface IGenericDao<T extends Serializable> { T findOne(final long id); List<T> findAll(); void create(final T entity); T update(final T entity); void delete(final T entity); void deleteById(final long entityId); }
2.3. The Abstract JPA DAO
public abstract class AbstractJpaDao< T extends Serializable > { private Class< T > clazz; @PersistenceContext EntityManager entityManager; public void setClazz( Class< T > clazzToSet ){ this.clazz = clazzToSet; } public T findOne( Long id ){ return entityManager.find( clazz, id ); } public List< T > findAll(){ return entityManager.createQuery( "from " + clazz.getName() ) .getResultList(); } public void save( T entity ){ entityManager.persist( entity ); } public void update( T entity ){ entityManager.merge( entity ); } public void delete( T entity ){ entityManager.remove( entity ); } public void deleteById( Long entityId ){ T entity = getById( entityId ); delete( entity ); } }
Similar to the Hibernate DAO implementation, the Java Persistence API is used here directly, again not relying on the now deprecated Spring JpaTemplate.
2.4. The Generic JPA DAO
Similar to the the Hibernate implementation, the JPA Data Access Object is straighforward as well:
@Repository @Scope( BeanDefinition.SCOPE_PROTOTYPE ) public class GenericJpaDao< T extends Serializable > extends AbstractJpaDao< T > implements IGenericDao< T >{ // }
3. Injecting this DAO
There is now a single DAO to be injected by Spring; also, the Class needs to be specified:
@Service class FooService implements IFooService{ IGenericDao< Foo > dao; @Autowired public void setDao( IGenericDao< Foo > daoToSet ){ dao = daoToSet; dao.setClazz( Foo.class ); } // ... }
Spring autowires the new DAO insteince using setter injection so that the implementation can be customized with the Class object. After this point, the DAO is fully parametrized and ready to be used by the service.
There are of course other ways that the class can be specified for the DAO – via reflection, or even in XML. My preference is towards this simpler solution because of the improved readability and transparency compared to using reflection.
4. Conclusion
This article discussed the simplification of the Data Access Layer by providing a single, reusable implementation of a generic DAO. This implementation was presented in both a Hibernate and a JPA based environment. The result is a streamlined persistence layer, with no unnecessary clutter.
For a step by step introduction about setting up the Spring context using Java based configuration and the basic Maven pom for the project, see this article.