Reputation: 2721
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:
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
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
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
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
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
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