Simplify the DAO with Spring and Java Generics


1.概述

本文将重点介绍如何通过为系统中的所有实体使用单个,通用的数据访问对象来简化DAO层,这将导致优雅的数据访问,没有不必要的混乱或冗长。

2. Hibernate和JPA DAO

大多数生产代码库都有某种DAO层。通常,实现的范围从没有抽象基类的多个类到某种类的泛化类。但是,有一点是一致的 - 总有不止一个 - 最有可能的是,DAO与系统中的实体之间存在一对一的关系。

此外,根据所涉及的泛型的级别,实际的实现可以从高度重复的代码变为几乎为空,其中大部分逻辑分组在基本抽象类中。

这些多个实现通常可以由单个参数化DAO替代,因为通过充分利用Java Generics提供的类型安全性,不会丢失任何功能。

接下来介绍这个概念的两个实现,一个用于Hibernate中心持久层,另一个用于JPA。这些实现绝不是完整的 - 只包括一些数据访问方法,但可以更容易地使它们更加彻底。

2.1。抽象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();
   }
}

DAO直接使用Hibernate API,而不依赖于任何Spring模板(例如HibernateTemplate)。在Hibernate DAO教程中介绍了模板的使用以及在DAO中自动装配的SessionFactory的管理。

2.2。通用Hibernate DAO

现在抽象DAO已经完成,我们只需实现一次 - 通用DAO实现将成为唯一需要的实现:

@Repository
@Scope( BeanDefinition.SCOPE_PROTOTYPE )
public class GenericHibernateDao< T extends Serializable >
  extends AbstractHibernateDao< T > implements IGenericDao< T >{
   //
}

首先,请注意,泛型实现本身已参数化 - 允许客户端根据具体情况选择正确的参数。这意味着客户端可以获得类型安全的所有好处,而无需为每个实体创建多个工件。

其次,请注意此通用DAO实现的原型范围。使用此作用域意味着Spring容器将在每次请求时创建DAO的新实例(包括自动装配)。这将允许服务根据需要为不同的实体使用具有不同参数的多个DAO。

这个范围如此重要的原因是Spring在容器中初始化bean的方式。保留没有范围的通用DAO意味着使用默认的单例范围,这将导致生活在容器中的DAO的单个实例。对于任何更复杂的情况,这显然是主要限制因素。

该IGenericDao简直就是所有的DAO方法的接口,使我们可以在(或任何需要)注入我们与春天的实现:

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。本摘要 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 );
   }
}

与Hibernate DAO实现类似,此处直接使用Java Persistence API,同样不依赖于现已弃用的 Spring JpaTemplate。

2.4。Generic JPA DAO

与Hibernate实现类似,JPA数据访问对象也很简单:

@Repository
@Scope( BeanDefinition.SCOPE_PROTOTYPE )
public class GenericJpaDao< T extends Serializable >
 extends AbstractJpaDao< T > implements IGenericDao< T >{
   //
}

3.注入这个DAO

现在有一个由Spring注入的DAO ; 此外,类需要指定:

@Service
class FooService implements IFooService{

   IGenericDao< Foo > dao;

   @Autowired
   public void setDao( IGenericDao< Foo > daoToSet ){
      dao = daoToSet;
      dao.setClazz( Foo.class );
   }

   // ...
27*6
}

Spring 使用setter注入自动装配新的DAO实例,以便可以使用Class对象自定义实现。在此之后,DAO完全参数化并准备好供服务使用。

当然还有其他方法可以为DAO指定类 - 通过反射,甚至是XML。我倾向于采用这种更简单的解决方案,因为与使用反射相比,可读性和透明度得到了提高。