Here is a way to handle this without having to make your DAOs aware that they use multiple datasources. Basically you create an AOP proxy which determines from the arguments which datasource to use, and you use a TargetSource to do the actual lookup. Here is an abstract class which is actually both objects:
Code:
public abstract class AbstractMapBasedTargetSourceInterceptor implements MethodInterceptor, TargetSource, InitializingBean {
private Map targetMapping;
private final ThreadLocal targetInThread = new ThreadLocal();
private Class targetClass;
public void setTargetMapping(Map targetMapping) {
this.targetMapping = targetMapping;
}
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object key = getKey(methodInvocation);
if (key == null) {
throw new IllegalStateException("Key value cannot be null");
}
targetInThread.set(lookupTarget(key));
try {
return methodInvocation.proceed();
} finally {
targetInThread.set(null);
}
}
protected abstract Object getKey(MethodInvocation methodInvocation);
public void afterPropertiesSet() throws Exception {
if (targetMapping == null) {
throw new IllegalStateException("targetMapping is required");
}
if (targetClass == null) {
throw new IllegalStateException("targetClass is required");
}
}
public Class getTargetClass() {
return targetClass;
}
public void setTargetClass(Class targetClass) {
this.targetClass = targetClass;
}
public boolean isStatic() {
return false;
}
public Object getTarget() throws Exception {
return targetInThread.get();
}
private Object lookupTarget(Object key) {
if (key == null) {
throw new IllegalStateException("No key for look up");
}
Object value = targetMapping.get(key);
if (value.getClass() != getTargetClass()) {
throw new IllegalStateException("Mapped object isn't appropriate class. Expecting: " + value.getClass().getName() + ". Actual: " + getTargetClass().getName());
}
return value;
}
public void releaseTarget(Object target) throws Exception {
// no-op
}
}
To use it you override it. In this example it always assumes every method uses the first argument as the datasource lookup:
Code:
public class DataSourceTargetSourceInterceptor extends AbstractMapBasedTargetSourceInterceptor {
/**
* Return first argument as the key
* @param methodInvocation
* @return
*/
protected Object getKey(MethodInvocation methodInvocation) {
return methodInvocation.getArguments()[0];
}
}
And you need two proxies:
- The dataSource as a targetSource. This will call getTarget around each dataSource method to get the current dataSource
- The object which determines the dataSource (most likely the DAO). This will look at the method call, looks up and sets the datasource as a ThreadLocal
The seqence of events is:
- DAO proxy is invoked. From the method arguments sets the datasource as a ThreadLocal
- DAO calls DataSource.getConnection() which uses the key to find the correct datasource
Here is roughtly how it would be wired up. This is different from most targetSource examples because what influences the target is at a different call level then where the target is used. In object pooling the very act of getting the target is what triggers the pool. Here its the DAO call which determines which dataSource is going to be used.
Code:
<bean id="targetMapping" class="DataSourceLookup">
<description>Creates a map of datasources</description>
</bean>
<bean id="dataSourceTarget" class="DataSourceTargetSourceInterceptor">
<property name="targetClass" value="javax.sql.DataSource"/>
<property name="targetMapping" ref="targetMapping"/>
</bean>
<bean id="myDataSource" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="javax.sql.DataSource"/>
<property name="targetSource" ref="dataSourceTarget"/>
</bean>
<bean id="myDaoTarget" class="myDaoImpl">
<property name="dataSource" ref="myDataSource"/>
</bean>
<bean id="myDao" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="myDaoTarget"/>
<property name="interceptorNames"><idref local="dataSourceTarget"/></property>
Although this is a lot of wiring and a little tricky your DAO can be written like any other DAO. It takes a datasource and invokes the datasource normally. You put the logic for determining which datasource to use outside of the DAO itself. Yet you can use your DAO as a collaborator to your business object. I had solved this problem differently using a DAO factory that required my business object to know about the DAO factory. Time to revisit my code. Thanks for the inspiration