DAO设计模式

13

假设我们有几个实体需要使用DAO对象进行持久化。因此,我们实现了正确的接口,以便我们最终得到:

class JdbcUserDao implements UserDao{
//...
}

class JdbcAddressDao implements AddressDao{
//...
}
所以,如果我想要能够在JDBC和JPA之间切换持久化实现(例如),我需要有JPAUserDao和JPAAddressDao...这意味着如果我有20个实体,并决定在代码中切换实现(使用DI容器),那么我就必须将每个Jdbc实现都切换为JPA。
现在可能是我误解了DAO的工作方式,但是... 如果我只有

class JdbcDaoImpl implements UserDao,AddressDao{
//...
}

我将所有JDBC实现放在一个类中,切换实现会很容易。同时,DaoImpl的数量等于Dao接口的数量。为什么不按照实现的组别(jdbc, JTA, JPA...)对它们进行分组,并将所有内容放在一个类中呢?

提前感谢。


4
同样的原因,为什么你不把应用程序全部编写在一个大的“main()”方法中:分离关注点。(顺便说一下,没有人阻止你编写一个包含公共代码的抽象“JdbcDaoBase”,并在你的“Dao”中继承它。) - rsp
为什么在一个类中替换500个方法比在100个类中替换更容易? - Jens Schauder
3个回答

22
拥有单个类实现整个应用程序中的每个DAO接口会是一个相当糟糕的设计。
更典型的模式是拥有一个BaseDAO接口(也经常称为GenericDAO),并且拥有JPABaseDAO、JDBCBaseDAO等。这些基类将包含诸如find/get/read、save/store/persist、update/modify和delete/remove/purge之类的方法。
像UserDAO这样的特定DAO接口然后继承自BaseDAO,而像JPAUserDAO这样的具体实现则从JPABaseDAO扩展。
一个BaseDAO接口可能看起来像这样:
public interface BaseDAO <T> {      
    T getByID(Long ID);
    T save(T type);
    T update(T type);
    void delete(T type);
}

一个 UserDAO 接口:

public interface UserDAO extends BaseDAO<User> {
    List<User> getAllAuthorized();
}

一个实现该接口的JPABaseDAO的简单示例:

@Stateless
public class JPABaseDAO<T> implements BaseDAO<T> {

    @PersistenceContext
    private EntityManager entityManager;

    private final Class<T> entityType;

    @SuppressWarnings("unchecked")
    public JPABaseDAO() {
        this.entityType = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
    }

    @Override
    public T getByID(Long ID) {
        return entityManager.find(entityType, ID);
    }

    @Override  
    public T save(T type) {
        return entityManager.persist(type);        
    }

    @Override  
    public T update(T type) {        
        return entityManager.merge(type);
    }

    @Override
    public void delete(T type) {
        entityManager.remove(entityManager.contains(type) ? type : entityManager.merge(type));
    }

}

以下是一些继承自UserDAO的示例实现:

@Stateless
public class JPAUserDAO extends JPABaseDAO<User> implements UserDAO {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<User> getAllAuthorized() {
        return entityManager.createNamedQuery("User.getAllAuthorized", User.class)
                            .getResultList();
    }
}

实际上,基类通常可以透明地执行一些其他操作,例如检查实体是否实现了某种可审计接口,并自动设置修改日期和用户等。

使用EJB实现DAO时,更改实现的一种策略是将所有JDBC实现放在一个包中,将所有JPA实现放在另一个包中。然后只在您的构建中包含一个实现包。


非常好,非常感谢。那么涉及多个表的CRUD操作呢?例如,我是否需要执行一个select语句来获取一个对象,并使用它来调用另一个DAO实现的CRUD,或者创建某种外星混合DAO?顺便说一下,你对我非常有帮助,非常感激。 - Mercurial
1
CRUD或涉及多个实体/表的任何操作通常由聚合多个DAO的服务处理。在EJB中,即使调用多个DAO(它会传播),您也将自动处于相同的持久性上下文中。另一种可能性是,如果实体相关联(用户有一个房子),则只需要为用户创建一个DAO,JPA将自动从您的用户对象中获取/保存/更新房屋。 - Arjan Tijms
1
不错,这种方法我在多个项目中都采用过。证明它非常有效和稳定。我在这里详细描述了它:http://codeblock.engio.net/?p=180 - bennidi
在JPAUserDAO中覆盖EntityManager而不是使用JPABaseDAO是必要的吗? - Akshay Naik

1
依赖注入的整个目的是使切换实现更容易,并将用户与提供者解耦。因此,所有 DI 框架都提供了一些方法来“分组”多个实现(例如您的 JDBC 组和 JPA 组)并在一个地方切换它们。
另外:通常消费者数量(例如在您的案例中:一些处理用户和地址的业务逻辑)通常比 DAO 数量高,因此 DI 框架将为您解耦大部分内容。假设:50 个业务 bean,每个接口有两个实现(总共 4 个):即使是基本的 DI 也会为这 50 个 bean 解决问题。使用分组将为您减少剩余部分的一半。

请问您能解释一下“Also”部分吗?非常感谢。 - Mercurial
我不理解这句话的意思:"即使是基本的 DI 也会为您处理50。使用分组将为您减少剩余的一半。" - Mercurial
@user1304844:之前我举了一个典型的例子:你有很多业务bean(例如50个)。每个业务bean通常引用几个DAO来委托持久化工作。在这种情况下,依赖注入(DI)是第一个重要的助手:它可以在一个中心位置(DI配置文件)管理50个或更多的依赖关系。因此,你最初的担忧——切换20个DAO实现——使用DI会更容易,因为这20个变化只需要在DI配置文件中进行修改(而不是在50个地方)。除此之外(即使让事情更加简单),DI框架通常还提供额外的分组功能。 - A.H.

0

肯定有可能以广泛技术中立的方式实现DAO模式,使得切换持久化技术甚至混合多种技术变得可行。本文介绍了一种包括在Github上的源代码的实现方案。

http://codeblock.engio.net/?p=180


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接