Isaac
Isaac

Reputation: 2721

Multiple DAO Incomplete Implementations Design

I have spring/hibernate web app. I use Hibernate to implement almost all of my DAOs. However, occasionally I need to use a JDBC based implementation. For each DAO I have an interface, say ProductDao', and I have an implementation for it ProductDaoHibImpl, or ProductDaoJdbcImpl. The problem is that, say, if I have a DAO with two methods, where one method makes sense to be implemented using Hibernate and the other makes sense to be implemented using jdbc. What's the best class design.

I came up with these designs:

  1. one interface two implementations, with a runtime exception thrown in methods not implemented in each class. (i.e. jdbc implmentation would throw runtime exception when a method implemented in the hibernate class is called)
  2. merge both implementations in one class
  3. implement all methods in both classes

However,

What's a better design for multiple DAO incomplete implementations?

Example:

public interface RxDao {

    public Rx getRxById(int rxId);

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

getRxById makes sense to be implemented using hibernate because you can use hibernate ORM.

getAllRxNotificationsGroupedByFacility on the other hand retrieves only a subset of Rx columns but fetches a lot more data, that needs to be grouped a certain way, and eventually be sent over to another server, so it makes more sense implement it using jdbc.

Upvotes: 0

Views: 2473

Answers (5)

mbelow
mbelow

Reputation: 1123

In addition to my other answer, here is a possible solution to this. You annotate the methods on your implementation classes with a "quality of service", and create proxies that automatically delegate to your implementation with the highest "quality of service" value:

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());

    }

}

If you run the main method, it prints:

hello from jpa

hello from jdbc

You dislike the fact that you need to implement the operations that are not supported anyway, you could consider splitting the interfaces up (as I suggested in my other post).

Upvotes: 2

SkyWalker
SkyWalker

Reputation: 14309

I find it strange that you need to provide a plain JDBC DAO implementation "in some cases", you should be able to cover all your uses-cases with Hibernate or more generally using JPA.

Another idea to explore: reuse a Generic DAO and purely reuse it, rather than following the common antipattern of one-DAO-per-entity which is what happens when you add an interface and an implementation everytime you have a new datamodel entity e.g. adding Person prompts you to add IPersonDao interface and PersonDao implementation, this is clearly a high toll in extensibility and maintainability. You may avoid this entirely by using a Generic DAO e.g. say you have a new Person datamodel entity then you simple reuse an existing generic DAO:

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

More examples at the bottom of this page. Make sure to checkout the "find-by-example" feature of the Generic DAO that can be further extended by the Spring Generic DAO implementation that maps named query to interface elements.

Upvotes: 0

mbelow
mbelow

Reputation: 1123

If you still prefer to keep things separated, you could split your DAO-interfaces up in two (lets say JdbcProductOperations and JpaProductOperations). Your ProductDAO-interfaces does no longer declare any method, but inherits from both of these interfaces. You could then add a ProductDAO-Implementation that takes a JdbcProductOperations- and a JpaProductOperations instance, and delegates the calls accordingly.

Upvotes: 1

axtavt
axtavt

Reputation: 242686

Approach 2 looks quite good for me, however, I don't quite understand why do you see it as two implementation merged together.

It's pretty common to create a DAO that uses high-level Hibernate functionality for most of the methods, but falls back to lower level for methods that require it. Note that you can use Hibernate API (native queries, doWork(...), etc) for low-level access, therefore there should be no additional complexity caused by mixing Hibernate with JDBC.

Something like this:

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) { ... }
}

Upvotes: 2

Thorn
Thorn

Reputation: 4057

I'm curious as to why you need to use two different models - Hibernate and JDBC.

I am currently dealing with something somewhat similar. My application will have two deployments, one using mySQL which I will host myself and the other using microsoft SQL server which another company will host for a different set of end users.

In order to deal with the differences in SQL syntax I chose jOOQ as an abstraction layer and after a little learning curve, I'm finding it very easy to work with. I can just set the SQL dialect in a Dao object (or using a Servlet init parameter) and this will be the only line I'll need to change between the two different database deployments.

Upvotes: 0

Related Questions