【问题标题】:DAO design patternDAO 设计模式
【发布时间】:2012-04-14 20:01:29
【问题描述】:

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

class JdbcUserDao implements UserDao{
//...
}

class JdbcAddressDao implements AddressDao{
//...
}

因此,如果我希望能够将持久性实现从 JDBC 切换到 JPA(例如),反之亦然,我需要 JPAUserDao 和 JPAAddressDao...这意味着如果我有 20 个实体,并决定切换实现(使用 DI 容器),我必须在代码中使用 JPA 切换每个 Jdbc 实现。

现在可能是我误解了 DAO 的工作原理,但是……如果我有

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

然后我将所有 JDBC 实现放在一个类中,切换实现将是小菜一碟。此外,DaoImpl 计数等于 Dao 接口的数量。为什么不按实现(jdbc、JTA、JPA...)对它们进行分组,并将所有内容归于一个类?

提前致谢。

【问题讨论】:

  • 不将应用程序编码成一个大的main() 方法的原因相同:关注点分离。 (顺便说一句,没有人阻止您编写包含通用代码的抽象 JdbcDaoBase 并在您的 Dao 中扩展它)
  • 为什么替换 1 个类中的 500 个方法比替换 100 个类更容易?

标签: java design-patterns dao


【解决方案1】:

让一个类实现整个应用程序中的每个 DAO 接口将是一个相当糟糕的设计。

更典型的模式是有一个BaseDAO 接口(也通常称为GenericDAO)并有一个JPABaseDAOJDBCBaseDAO 等。这些基类将包含诸如查找/获取/读取、保存/存储/持久化、更新/修改和删除/移除/清除。

特定的 DAO 接口,如 UserDAO,然后继承自 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();
    }
}

在实践中,基类通常可以透明地做一些其他事情,例如检查实体是否实现了某种Auditable接口,并自动设置修改它的日期和用户等。

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

【讨论】:

  • 太好了,非常感谢。涉及多个表的 CRUD 操作呢?例如,我是否会执行一个 select 语句来获取一个对象并使用它来调用另一个 DAO impl 的 CRUD,或者可能创建某种外星混合 DAO?顺便提一句。你对我帮助很大,非常感谢。
  • CRUD 或涉及多个实体/表的任何操作通常由聚合多个 DAO 的服务处理。在 EJB 中,即使您调用多个 DAO(它会传播),您也会自动处于相同的持久性上下文中。另一种可能性是,如果实体是关联的(用户拥有房屋),您只需要一个用户的 DAO,JPA 将自动从您的用户对象中获取/保存/更新房屋。
  • 不错不错。这种方法是我在各种项目中采用的方法。事实证明,它运行良好且稳定。我在这里详细描述一下:codeblock.engio.net/?p=180
  • 是否需要在 JPAUserDAO 中重写 EntityManager 而不是使用 JPABaseDAO?
【解决方案2】:

依赖注入的全部意义在于使实现之间的切换更容易,并将用户与提供者分离。因此,所有 DI 框架都提供了某种方式来“分组”多个实现(这里是您的 JDBC 组和您的 JPA 组)并将它们切换到一个地方。

另外:通常消费者的数量(在你的情况下:一些对用户和地址工作的业务逻辑)通常高于 DAO 的数量,DI 框架无论如何都会为你解耦大部分东西。假设:50 个业务 bean、两个接口和每个接口的两个实现(总共 4 个):即使是基本的 DI 也会处理这 50 个。使用分组将剩下的剩余部分减半。

【讨论】:

  • 您能解释一下“也”部分吗?非常感谢。
  • "即使是基本的 DI 也会照顾 50 个。使用分组将使你剩下的休息减半。" - 我不明白这一点。
  • @user1304844:之前我做了一个典型的例子:你有很多业务 bean(例如 50 个)。每个业务 bean 通常引用几个 DAO 来委派持久性工作。在这个地方,DI 是第一个大帮手:在一个中心位置管理 50 个或更多依赖项 - DI-config。因此,您最初关心的问题 - 切换 20 个 DAO 实现 - 使用 DI 更容易,因为这 20 个更改将在一个位置 - 在 DI 配置文件中(与没有 DI 的 50 个位置相比)。最重要的是(即让事情变得更容易)DI 框架通常提供额外的分组内容。
【解决方案3】:

绝对有可能以一种广泛与技术无关的方式实现 DAO 模式,这样切换持久性技术甚至混合多种技术变得可行。本文介绍了一种实现方案,包括github上的源码。

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

【讨论】:

    猜你喜欢
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 1970-01-01
    • 2015-12-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多