实际应用中有这么个需求:根据用户所属的租户,来切换到租户对应的数据库(一个租户一套数据库,数据表都一样,存放的schema不同而已)
下面是配置的代码
1.动态数据源的切换,使用spring提供的AbstractRoutingDataSource接口,实现determineCurrentLookupKey()方法
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static String getCurrentLookupKey() {
return (String) contextHolder.get();
}
public static void setCurrentLookupKey(String currentLookupKey) {
contextHolder.set(currentLookupKey);
}
@Override
protected Object determineCurrentLookupKey() {
return getCurrentLookupKey();
}
}
2.在applicationContext.xml中为每个租户写一个对应的数据源
<bean id="baseDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.urla}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="aDataSource" parent="baseDataSource">
<property name="url" value="${jdbc.urla}" />
</bean>
<bean id="bDataSource" parent="baseDataSource">
<property name="url" value="${jdbc.urlb}" />
</bean>
<bean id="dataSource" class="com.baosight.iframework.common.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="a" value-ref="aDataSource" />
<entry key="b" value-ref="bDataSource" />
</map>
</property>
<property name="defaultTargetDataSource" ref="aDataSource" />
</bean>
配置中使用了属性继承,因为唯一变动的只有url连接,dataSource具体使用哪个是根据传来的key来决定,下面我们看下如何传key
3.调用数据访问层前,都先传dataSource的key
public class DynamicDataSourceInterceptor {
public void setdataSource(JoinPoint jp) {
HttpSession s = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest().getSession();
String t = (String) s.getAttribute("tenant");
if (t.equals("tenant1")) {
DynamicDataSource.setCurrentLookupKey("a");
}
if (t.equals("tenant2")) {
DynamicDataSource.setCurrentLookupKey("b");
}
}
}
上述的代码就是指定key的过程,因为用户的信息保存在session中,所以使用了session,下面的工作就是把上述的代码织入到service层的所有方法
<aop:config>
<aop:aspect id="dsAspect" ref="dsInterceptor">
<aop:pointcut id="businessService"
expression="execution(* com.jacky.service.*.*(..))" />
<aop:before pointcut-ref="businessService" method="setdataSource"/>
</aop:aspect>
</aop:config>
<bean id="dsInterceptor" class="com.jacky.DynamicDataSourceInterceptor" />
aop的作用就是每次调用service的方法前都先切换数据库
这样就完成了。我刚开始把aop切面放到controller层没有效果,要放到service层才行。