1 实现原理:
master/slave数据库都对应不同的操作名称,执行Dao层方法时,判断方法名是否以给定的master操作名称开头(比如:add, delete, save, delete), 如果是,则用master DB,如果不是则用slave DB。
2 实现步骤:
利用Spring的AbstractRoutingDataSource解决多数据源的问题,使用AOP动态切换数据源。
a. 配置 master/slave等多个数据源
b. 写一个DynamicDataSource类继承AbstractRoutingDataSource,并实现etermineCurrentLookupKey
方法。配置一个动态数据源
c. 将dataSource与ThreadLocal 绑定,利用ThreadLocal解决线程安全问题
d. 利用AOP, 编写DataSourceAdvice类,实现根据方法名动态切换数据源的功能。其中使用<util:set>来配置master Db对应的操作名称。
3 代码实现及分析:
a.配置数据源:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="lililocations">
<value>classpath:jdbc.properties</value>
</property>
</bean>
<bean id="masterDataSource" ...> … </bean>
<bean id="slaveDataSource" ...> … </bean>
<bean id="dataSource" class="com.lili.a.ds.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="masterDataSource" />
<entry key="slave" value-ref="slaveDataSource" />
</map>
</property>
<property name="defaultTargetDataSource" ref="slaveDataSource" />
</bean>
b.DynamicDataSource类:
继承AbstractRoutingDataSource, 而AbstractRoutingDataSource是javax.sql.DataSource的子类 ,于是我们自然地去看他的getConnection 方法:
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
我发现原来奥妙就在determineTargetDataSource()里:
通过看源码,我发现determineTargetDataSource()用到了我们需要进行实现的抽象方法determineCurrentLookupKey(),该方法返回使用的DataSource的key值。根据key值从resolvedDataSources这个map 中获取对应的value值。如果找不到,则使用默认的resolvedDefaultDataSource。
resolvedDataSources是一个map,程序会将配置中的targetDataSources 这个Map中的key-value复制到resolvedDataSources。同样的会将 defaultTargetDataSource的DataSource值赋给 defaultTargetDataSource。
public class MyDynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSouce();
}
}
c. DynamicDataSourceHolder类:
public class DynamicDataSourceHolder {
private static final ThreadLocal<String> datasource = new ThreadLocal<String>();
public static void setDataSource(String dataSource) {
datasource.set(dataSource);
} public static String getDataSouce(){ String dataSource = datasource .get(); if (dataSource == null) { setMaster("master"); } returndatasource.get().toString(); }}
d.AOP
<bean id="dataSourceAdvice" class="com.lili.test.ds.DataSourceAdvice">
<property name="MasterKeys" ref="MasterKeys"></property>
</bean>
<util:set id="MasterKeys">
<value>delete</value>
<value>add</value>
<value>update</value>
<value>save</value>
</util:set>
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.lili.a.dao..*.*(..)) || execution(* com.lili.a.mybatis..*.*(..))" />
<aop:advisor pointcut-ref="pc" advice-ref="dataSourceAdvice" order="1" />
</aop:config>
public class DataSourceAdvice implements MethodBeforeAdvice{
private Set<String> MasterKeys = new HashSet<String>();
public void setMasterKeys(Set<String> MasterKeys) {
this.MasterKeys = MasterKeys;
}
@Override
public void before(Method method, Object[] arg, Object target) throws Throwable {
boolean isMaster = false;
String methodName = method.getName();
for(String accessMasterKey : MasterKeys) {
if (methodName.startsWith(accessMasterKey)) {
isMaster = true;
break;
}
}
DynamicDataSourceHolder.setDataSource("master");
} else {
DynamicDataSourceHolder.setDataSource("slave");
}
}
}