Spring數(shù)據(jù)源動態(tài)切換
利用spring-jdbc包中的AbstractRoutingDataSource類愚臀,從名字就可以看出來這是個抽象類。
先來看看這個類的相關(guān)類圖
可以看到這個類實現(xiàn)了DataSource接口,這意味著其繼承類可以在任何需要DataSource的地方使用镐依。
DataSource接口中只定義了兩個方法
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
所以我們重點關(guān)注AbstractRoutingDataSource中如何實現(xiàn)這兩個方法甩挫。
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
繼續(xù)追蹤
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
這個方法被設(shè)計成protected,說明子類可以重寫炮障。該方法中調(diào)用了AbstractRoutingDataSource中唯一一個抽象方法
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
protected abstract Object determineCurrentLookupKey();
這個方法便是我們實現(xiàn)動態(tài)數(shù)據(jù)源切換的關(guān)鍵了目派。
determineTargetDataSource中可以看到最終使用的DataSource對象是從resolvedDataSources中獲取的。其定義如下:
private Map<Object, DataSource> resolvedDataSources;
那么這個map是什么時候填充的呢胁赢?
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
可以清晰的看到是用targetDataSources里面的數(shù)據(jù)填充的企蹭。
接下來就是最終的解決方案了。
- 定義數(shù)據(jù)源
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String datasource = ContextHolder.getCustomerType();
return datasource;
}
}
<bean id="dataSource" class="com.sogou.daohang.datamonitor.support.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="qidian" value-ref="test1" />
<entry key="iguess" value-ref="test2" />
</map>
</property>
<property name="defaultTargetDataSource" ref="default" />
</bean>
其中test1,test2以及default都是我們定義的DataSource bean。
- 動態(tài)數(shù)據(jù)源操作
public class ContextHolder {
public static final String DATA_SOURCE_TEST1 = "test1";
/**
* 猜你喜歡數(shù)據(jù)庫
*/
public static final String DATA_SOURCE_TEST2 = "test2";
/**
* 當(dāng)前系統(tǒng)的數(shù)據(jù)庫
*/
public static final String DATA_SOURCE_DEFAULT = "default";
// 利用ThreadLocal解決線程安全問題
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
- 定義切面
public class DataSourceInterceptor {
public void setDefaultDataSource() {
ContextHolder.setCustomerType(ContextHolder.DATA_SOURCE_DEFAULT);
}
public void setTest1DataSource() {
ContextHolder.setCustomerType(ContextHolder.DATA_SOURCE_TEST1);
}
public void setTest2DataSource() {
ContextHolder.setCustomerType(ContextHolder.DATA_SOURCE_TEST2);
}
}
<!-- 動態(tài)數(shù)據(jù)源切換aop 先與事務(wù)的aop -->
<bean id="dataSourceInterceptor" class="com.*.support.DataSourceInterceptor" />
<aop:config>
<aop:pointcut id="test1Pointcut" expression="execution(* com.*.dao.test1..*.*(..))" />
<aop:pointcut id="test2Pointcut" expression="execution(* com.*.dao.test2..*.*(..))" />
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:before method="setDataSourceTest1" pointcut-ref="test1Pointcut"/>
<aop:before method="setDataSourceTest2" pointcut-ref="test2Pointcut"/>
</aop:aspect>
</aop:config>
這樣在調(diào)用相應(yīng)的方法時就能自動切換數(shù)據(jù)源了练对,但是設(shè)置完畢數(shù)據(jù)源后并沒有清除遍蟋,這意味著需要手動清除∶荆可以加上aop after在每個方法調(diào)用后清除掉當(dāng)前的數(shù)據(jù)源虚青。