Simplify the DAO with Spring and Java Generics – 用Spring和Java泛型简化DAO

最后修改: 2015年 12月 8日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

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.

本文将重点讨论简化DAO层,为系统中的所有实体使用一个单一的、生成的数据访问对象,这将导致优雅的数据访问,没有不必要的杂乱或冗长的语言。

We’ll build on the Abstract DAO class we saw in our previous article on Spring and Hibernate, and add generics support.

我们将在我们之前关于Spring和Hibernate的文章中看到的抽象DAO类的基础上,添加泛型支持。

2. The Hibernate and JPA DAOs

2.Hibernate和JPA DAO

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 than one. Most likely, there is a one to one relation between the DAOs and the entities in the system.

大多数生产代码库都有某种DAO层。通常,实现的范围从没有抽象基类的多个类到某种生成的类。然而,有一点是一致的–总是有一个以上的。最有可能的是,DAO和系统中的实体之间存在着一对一的关系。

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. We can implement this such that no functionality is lost by taking full advantage of the type safety provided by Java Generics.

这些多个实现通常可以被一个单一的参数化DAO所取代。我们可以通过充分利用Java泛型所提供的类型安全来实现这一点,从而不会丢失任何功能。

We’ll show two implementations of this concept next, one for a Hibernate centric persistence layer and the other focusing on JPA. These implementations are by no means complete, but we can easily add more additional data access methods are included.

接下来我们将展示这个概念的两个实现,一个是以Hibernate为中心的持久层,另一个是以JPA为中心的持久层。这些实现绝不是完整的,但我们可以很容易地添加更多的额外数据访问方法。

2.1. The Abstract Hibernate DAO

2.1.抽象的Hibernate DAO

Let’s take a quick look at the AbstractHibernateDao class:

让我们快速浏览一下AbstractHibernateDao类。

public abstract class AbstractHibernateDao<T extends Serializable> {
    private Class<T> clazz;

    @Autowired
    protected SessionFactory sessionFactory;

    public void setClazz(final Class<T> clazzToSet) {
        clazz = Preconditions.checkNotNull(clazzToSet);
    }

    public T findOne(final long id) {
        return (T) getCurrentSession().get(clazz, id);
    }

    public List<T> findAll() {
        return getCurrentSession().createQuery("from " + clazz.getName()).list();
    }

    public T create(final T entity) {
        Preconditions.checkNotNull(entity);
        getCurrentSession().saveOrUpdate(entity);
        return entity;
    }

    public T update(final T entity) {
        Preconditions.checkNotNull(entity);
        return (T) getCurrentSession().merge(entity);
    }

    public void delete(final T entity) {
        Preconditions.checkNotNull(entity);
        getCurrentSession().delete(entity);
    }

    public void deleteById(final long entityId) {
        final T entity = findOne(entityId);
        Preconditions.checkState(entity != null);
        delete(entity);
    }

    protected Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }
}

This is an abstract class with several data access methods, that uses the SessionFactory for manipulating entities.

这是一个具有若干数据访问方法的抽象类,它使用SessionFactory来操作实体。

We are using Google Guava’s Preconditions here to make sure that a method or a constructor is invoked with a valid parameter value. If Preconditions fails, a tailored exception is thrown.

我们在这里使用Google Guava的preconditions来确保一个方法或构造函数被调用时有一个有效的参数值。如果预设条件失败,就会抛出一个定制的异常。

2.2. The Generic Hibernate DAO

2.2.通用的Hibernate DAO

Now that we have the abstract DAO class, we can extend it just once. The generic DAO implementation will become the only implementation we need:

现在我们有了抽象的DAO类,我们可以只扩展它一次。通用DAO的实现将成为我们唯一需要的实现

@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 parameterized, allowing the client to choose the correct parameter on a case by case basis. This will mean that the clients get all the benefits of type safety without needing to create multiple artifacts for each entity.

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

Secondly, notice the prototype scope of this generic DAO implementation. Using this scope means that the Spring container will create a new instance of the DAO each time it’s requested (including on autowiring). That will allow a service to use multiple DAOs with different parameters for different entities, as needed.

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

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.

这个作用域之所以如此重要,是由于Spring在容器中初始化Bean的方式。没有作用域的通用 DAO 将意味着使用 默认的单子作用域,这将导致容器中只有 DAO 的一个实例。这对于任何一种更复杂的情况来说,显然是有很大限制的。

The IGenericDao is simply an interface for all the DAO methods so that we can inject the implementation we need:

IGenericDao只是所有DAO方法的一个接口,这样我们就可以注入我们需要的实现。

public interface IGenericDao<T extends Serializable> {
    void setClazz(Class< T > clazzToSet);

    T findOne(final long id);

    List<T> findAll();

    T 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

2.3.抽象的JPA DAO

The AbstractJpaDao is very similar to the AbstractHibernateDao:

AbstractJpaDaoAbstractHibernateDao非常相似:

public abstract class AbstractJpaDAO<T extends Serializable> {
    private Class<T> clazz;

    @PersistenceContext(unitName = "entityManagerFactory")
    private EntityManager entityManager;

    public final void setClazz(final Class<T> clazzToSet) {
        this.clazz = clazzToSet;
    }

    public T findOne(final long id) {
        return entityManager.find(clazz, id);
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        return entityManager.createQuery("from " + clazz.getName()).getResultList();
    }

    public T create(final T entity) {
        entityManager.persist(entity);
        return entity;
    }

    public T update(final T entity) {
        return entityManager.merge(entity);
    }

    public void delete(final T entity) {
        entityManager.remove(entity);
    }

    public void deleteById(final long entityId) {
        final T entity = findOne(entityId);
        delete(entity);
    }
}

Similar to the Hibernate DAO implementation, we’re using the Java Persistence API directly, without relying on the now deprecated Spring JpaTemplate.

与Hibernate DAO的实现类似,我们直接使用Java Persistence API,而不依赖现在已经废弃的Spring JpaTemplate

2.4. The Generic JPA DAO

2.4.通用的JPA DAO

Similar to the Hibernate implementation, the JPA Data Access Object is straightforward as well:

与Hibernate的实现类似,JPA的数据访问对象也是直接的。

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

3. Injecting This DAO

3.注入这个DAO

We now have a single DAO interface we can inject. We also need to specify the Class:

我们现在有一个可以注入的DAO接口。我们还需要指定类:

@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 instance 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.

Spring 使用setter injection对新的DAO实例进行自动赋值,这样就可以用Class对象定制实现。在这一点上,DAO是完全参数化的,可以被服务使用。

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.

当然,还有其他方法可以为DAO指定类–通过反射,或者甚至在XML中。我更倾向于这种更简单的解决方案,因为与使用反射相比,可读性和透明度都有所提高。

4. Conclusion

4.结论

This article discussed the simplification of the Data Access Layer by providing a single, reusable implementation of a generic DAO. We showed the implementation in both a Hibernate and a JPA based environment. The result is a streamlined persistence layer, with no unnecessary clutter.

这篇文章讨论了通过提供一个单一的、可重复使用的通用DAO的实现来简化数据访问层的问题。我们展示了在基于Hibernate和JPA环境下的实现。其结果是一个精简的持久层,没有不必要的杂乱。

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.

关于使用基于Java的配置和项目的基本Maven pom设置Spring上下文的逐步介绍,请参阅这篇文章

Finally, the code for this article can be found in the GitHub project.

最后,本文的代码可以在GitHub项目中找到。