【问题标题】:Multiple DAO Incomplete Implementations Design多个 DAO 不完全实现设计
【发布时间】:2012-12-04 14:31:22
【问题描述】:

我有 spring/hibernate 网络应用程序。我使用 Hibernate 来实现几乎所有的 DAO。但是,有时我需要使用基于 JDBC 的实现。对于每个 DAO,我都有一个接口,比如 ProductDao',并且我有一个 ProductDaoHibImpl 或 ProductDaoJdbcImpl 的实现。问题是,比如说,如果我有一个带有两种方法的 DAO,其中一种方法可以使用 Hibernate 实现,而另一种方法可以使用 jdbc 实现。什么是最好的类设计。

我想出了这些设计:

  1. 一个接口两个实现,在每个类中没有实现的方法中抛出运行时异常。 (即当调用hibernate类中实现的方法时,jdbc实现会抛出运行时异常)
  2. 将两个实现合并到一个类中
  3. 在两个类中实现所有方法

然而,

  • 设计1是反OO原则。
  • 设计 2 会搞砸 我的 DAO 实现的一致性,并且将使课程 本身可读性较差,并且会邀请其他开发人员继续添加 没有组织的方法。
  • 设计 3 添加了不必要的内容 工作,因为无论如何我都会使用更有效的实现。

对于多个 DAO 不完整的实现,有什么更好的设计?

例子:

public interface RxDao {

    public Rx getRxById(int rxId);

    public Map<String, List<Notification>> getAllRxNotificationsGroupedByFacility();
}

getRxById 使用 hibernate 实现是有意义的,因为您可以使用 hibernate ORM。

getAllRxNotificationsGroupedByFacility 另一方面,仅检索 Rx 列的子集,但获取更多数据,需要以某种方式分组,并最终发送到另一台服务器,因此使用实现它更有意义jdbc。

【问题讨论】:

  • 你能给我们举一个这两个方法签名的例子吗?
  • 这听起来像是可以使用 Projections 完成的事情,但这不是这个问题。
  • 对不起,如果我失去了重点,但你不能创建两个接口吗?一个用于 jdbc,另一个用于休眠,然后您使用选项 1-like 没有运行时异常
  • 设计1是反OO原则?我不同意,这是多数据访问层实现最合理的OO原则。它为在 spring 容器中配置/切换/单元测试数据访问层提供了最佳的可重用性和可扩展性。我认为这里的设计错误是您需要同时使用 Hibernate 和 JDBC 的原因。我无法想象一个 DAOImpl 类使用多种数据库评估技术,尤其是通过多种方法实现。除非您有非常特殊的要求,否则我将始终将其视为软件架构师级别的设计错误。

标签: java spring hibernate design-patterns dao


【解决方案1】:

方法 2 对我来说看起来不错,但是,我不太明白您为什么将其视为两个实现合并在一起。

创建一个 DAO 是很常见的,它对大多数方法使用高级 Hibernate 功能,但对于需要它的方法回退到较低级别。请注意,您可以使用 Hibernate API(本机查询、doWork(...) 等)进行低级访问,因此不会因为将 Hibernate 与 JDBC 混合而导致额外的复杂性。

类似这样的:

public class HibernateRxDao implements RxDao {
    ...

    public Rx getRxById(int rxId) {
        return sf.getCurrentSession().get(rxId, Rx.class);
    }

    public Map<String, List<Notification>> getAllRxNotificationsGroupedByFacility() {
        return toMap(sf.getCurrentSession().createNativeQuery(...). ... .list());
    }

    private Map<String, List<Notification>> toMap(List<Object[]> rows) { ... }
}

【讨论】:

    【解决方案2】:

    如果您仍然喜欢将事物分开,您可以将您的 DAO 接口一分为二(比如说JdbcProductOperationsJpaProductOperations)。您的 ProductDAO 接口不再声明任何方法,而是从这两个接口继承。然后,您可以添加一个 ProductDAO-Implementation,它接受一个 JdbcProductOperations- 和一个 JpaProductOperations 实例,并相应地委托调用。

    【讨论】:

    • 我不知道这是否可以理解...如果我应该添加一个示例,请告诉我。
    • 我理解你的解决方案,但是JdbcImplJpaImpl 不会是Dao 接口的实例,所以我需要一直使用JdbcProductOperationsJpaProductOperations 接口,这使Dao 接口(正如您所建议的将从它们两者继承)无用。
    • 重点是 JdbcProductOperations 和 JpaProductOperations 是“实现细节”。这些不应由客户端代码直接使用。客户端代码仍应仅依赖于 ProductDao-Interface。没错,JdbcImpl 和 JpaImpl 没有实现 Dao 接口,但 ProductDaoImpl 实现了(相应地委托给 JdbcImpl 和 JpaImpl)。
    • 顺便说一句,ProductDaoImpl 可以使用 JDK 代理动态自动生成。您只需要传入 Dao-Interface 和“部分”实现这些接口的类。在构建时,您的代理工厂将确定在哪个类上声明了哪些方法,并根据需要委托调用。如果您想查看此示例代码,请告诉我。
    【解决方案3】:

    除了我的其他答案之外,这里还有一个可能的解决方案。您使用“服务质量”对实现类上的方法进行注释,并创建代理以自动委托给具有最高“服务质量”值的实现:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.HashMap;
    import java.util.Map;
    
    
    public class DaoProxyDemo {
    
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        @interface QualityOfService {
            int value();
        }
    
        interface ProductDao {
            String operation1();
            String operation2();
        }
    
        static class JpaProductDao implements ProductDao {
    
            @QualityOfService(1)
            public String operation1() { return "hello from jpa"; }
    
            @QualityOfService(0)
            public String operation2() { throw new UnsupportedOperationException(); }
    
        }
    
        static class JdbcProductDao implements ProductDao {
    
            @QualityOfService(0)
            public String operation1() { throw new UnsupportedOperationException(); }
    
            @QualityOfService(1)
            public String operation2() { return "hello from jdbc"; }
    
        }    
    
        static class QosAwareProxyFactory {
    
            public static <T> T createProxy(Class<T> interfaceType, T... implementations) {
    
                class Binding {
                    private final int qos;
                    private final T impl;
    
                    public Binding(T impl, int qos) {
                        this.impl = impl;
                        this.qos = qos;
                    }
                }
    
                final Map<Method, Binding> dispatchMap = new HashMap<Method, Binding>();
                try {
                    for (Method method : interfaceType.getDeclaredMethods()) {
    
                        for (T impl : implementations) {
    
                            Method implMethod = impl.getClass().getMethod(method.getName(), 
                                    method.getParameterTypes());
    
                            QualityOfService qos = implMethod.getAnnotation(QualityOfService.class);
    
                            int qosValue = qos == null ? 0 : qos.value();
    
                            Binding bestSoFar = dispatchMap.get(method);
    
                            if (bestSoFar == null || bestSoFar.qos < qosValue) {
                                dispatchMap.put(method, new Binding(impl, qos.value()));
                            }
    
                        }
    
                    }
                }
                catch (NoSuchMethodException e) {
                    throw new AssertionError("can never happen");
                }
    
                Object proxy = Proxy.newProxyInstance(QosAwareProxyFactory.class.getClassLoader(), 
                        new Class<?>[] {interfaceType}, new InvocationHandler() {
    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
    
                        T impl = dispatchMap.get(method).impl;
                        return method.invoke(impl);
                    }
                });
    
                return interfaceType.cast(proxy);
    
            }
        }
    
        public static void main(String[] args) {
    
            ProductDao proxy = QosAwareProxyFactory.createProxy(
                    ProductDao.class, new JpaProductDao(), new JdbcProductDao());
    
            System.out.println(proxy.operation1());
            System.out.println(proxy.operation2());
    
        }
    
    }
    

    如果你运行 main 方法,它会打印:

    来自jpa的你好

    来自 jdbc 的你好

    您不喜欢需要实现无论如何都不支持的操作这一事实,您可以考虑拆分接口(正如我在另一篇文章中建议的那样)。

    【讨论】:

      【解决方案4】:

      我很好奇为什么需要使用两种不同的模型 - Hibernate 和 JDBC。

      我目前正在处理一些类似的事情。我的应用程序将有两个部署,一个使用我自己托管的 mySQL,另一个使用另一个公司将为不同的最终用户组托管的 microsoft SQL 服务器。

      为了处理 SQL 语法的差异,我选择了jOOQ 作为抽象层,经过一点学习曲线后,我发现它很容易使用。我可以在 Dao 对象中设置 SQL 方言(或使用 Servlet 初始化参数),这将是我需要在两个不同的数据库部署之间更改的唯一行。

      【讨论】:

      • 感谢您的回答,但问题不在于语法不同,而且我只需要一个实现(每个方法),所以情况不太相似。
      【解决方案5】:

      我觉得奇怪的是,您“在某些情况下”需要提供一个简单的 JDBC DAO 实现,您应该能够使用 Hibernate 或更一般地使用 JPA 来覆盖所有用例。

      另一个需要探索的想法:重用 Generic DAO 并纯粹重用它,而不是遵循一个 DAO-per-entity 的常见反模式,当您添加一个接口和一个每次你有一个新的数据模型实体时实现,例如添加Person 提示您添加IPersonDao 接口和PersonDao 实现,这显然是可扩展性和可维护性的高代价。您可以通过使用通用 DAO 来完全避免这种情况,例如假设您有一个新的 Person 数据模型实体,那么您可以简单地重用现有的通用 DAO:

       Person myGiovanni = new Person("Giovanni");  
       IGenericDao<Long, Person> myPersonDao = HibernateDaoFactory.getInstance().createDao(Person.class);
       myPersonDao.create(myGiovanni);
      

      this page 底部有更多示例。请务必查看通用 DAO 的“按示例查找”功能,该功能可以通过将命名查询映射到接口元素的 Spring 通用 DAO 实现进一步扩展。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-04-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-29
        • 1970-01-01
        相关资源
        最近更新 更多