DAO 设计模式

2022-09-04 08:20:01

假设我们有几个实体想要使用 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...)对它们进行分组,并将所有内容都放在一个类下?

提前致谢。


答案 1

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

一个更典型的模式是有一个接口(也经常调用)和一个,等等。这些基类将包含查找/获取/读取、保存/存储/持久化、更新/修改和删除/删除/清除等方法。BaseDAOGenericDAOJPABaseDAOJDBCBaseDAO

特定的 DAO 接口(如 then)继承自 和具体实现(如 extends from )。UserDAOBaseDAOJPAUserDAOJPABaseDAO

界面可能如下所示: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 实现放在另一个包中。然后,只需在生成中仅包含一个实现包。


答案 2

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

另外:通常,使用者的数量(在你的例子中:一些业务逻辑处理用户和地址)通常高于DAO的数量,DI框架无论如何都会为你解耦大部分东西。假设:50 个业务 Bean,两个接口和每个接口的两个实现(总共 4 个):即使是基本的 DI 也会照顾 50 个。使用分组将使您剩余的休息时间减半。


推荐