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");
}
}
}