利用AbstractRoutingDataSource實現動態數據源切換

 在Spring 2.0.1中引入了AbstractRoutingDataSource, 該類充當了DataSource的路由中介, 能有在運行時, 根據某種key值來動態切換到真正的DataSource上。java

     Spring動態配置多數據源,即在大型應用中對數據進行切分,而且採用多個數據庫實例進行管理,這樣能夠有效提升系統的水平伸縮性。而這樣的方案就會不一樣於常見的單一數據實例的方案,這就要程序在運行時根據當時的請求及系統狀態來動態的決定將數據存儲在哪一個數據庫實例中,以及從哪一個數據庫提取數據。spring

 
Spring對於多數據源,以數據庫表爲參照,大致上能夠分紅兩大類狀況: 
一是,表級上的跨數據庫。即,對於不一樣的數據庫卻有相同的表(表名和表結構徹底相同)。 
二是,非表級上的跨數據庫。即,多個數據源不存在相同的表。 
Spring2.x的版本中採用Proxy模式,就是咱們在方案中實現一個虛擬的數據源,而且用它來封裝數據源選擇邏輯,這樣就能夠有效地將數據源選擇邏輯從Client中分離出來。Client提供選擇所需的上下文(由於這是Client所知道的),由虛擬的DataSource根據Client提供的上下文來實現數據源的選擇。 
具體的實現就是,虛擬的DataSource僅需繼承AbstractRoutingDataSource實現determineCurrentLookupKey()在其中封裝數據源的選擇邏輯。sql

1、原理數據庫

首先看下AbstractRoutingDataSource類結構,繼承了AbstractDataSourcesession

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

既然是AbstractDataSource,固然就是javax.sql.DataSource的子類,因而咱們天然地回去看它的getConnection方法:oracle

public Connection getConnection() throws SQLException {  
        return determineTargetDataSource().getConnection();  
    }  
  
    public Connection getConnection(String username, String password) throws SQLException {  
        return determineTargetDataSource().getConnection(username, password);  
    }

 原來關鍵就在determineTargetDataSource()裏:dom

/** 
     * Retrieve the current target DataSource. Determines the 
     * {@link #determineCurrentLookupKey() current lookup key}, performs 
     * a lookup in the {@link #setTargetDataSources targetDataSources} map, 
     * falls back to the specified 
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary. 
     * @see #determineCurrentLookupKey() 
     */  
    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;  
    }

這裏用到了咱們須要進行實現的抽象方法determineCurrentLookupKey(),該方法返回須要使用的DataSource的key值,而後根據這個key從resolvedDataSources這個map裏取出對應的DataSource,若是找不到,則用默認的resolvedDefaultDataSource。ide

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

2、Spring配置多數據源的方式和具體使用過程ui


一、數據源的名稱常量類this

 public enum DynamicDataSourceGlobal {
     ORCL,   
    ISC  
}

二、創建一個得到和設置上下文環境的類,主要負責改變上下文數據源的名稱

public class DynamicDataSourceHolder {
// 線程本地環境  
private static final ThreadLocal<DynamicDataSourceGlobal> contextHolder = new ThreadLocal<DynamicDataSourceGlobal>();  
  
// 設置數據源類型  
public static void setDataSourceType(DynamicDataSourceGlobal dataSourceType) {  
    Assert.notNull(dataSourceType, "DataSourceType cannot be null");  
    contextHolder.set(dataSourceType);  
}  
  
// 獲取數據源類型  
public static DynamicDataSourceGlobal getDataSourceType() {  
    return (DynamicDataSourceGlobal) contextHolder.get();  
}  
  
// 清除數據源類型  
public static void clearDataSourceType() {  
    contextHolder.remove();  
}

三、創建動態數據源類,注意,這個類必須繼承AbstractRoutingDataSource,且實現方法 determineCurrentLookupKey,該方法返回一個Object,通常是返回字符串

public class DynamicDataSource extends AbstractRoutingDataSource {
 @Override  
    protected Object determineCurrentLookupKey() {  
        return DynamicDataSourceHolder.getDataSourceType();  
    }  
  
}

四、編寫spring的配置文件配置多個數據源

     <!-- 數據源相同的內容 -->

<bean id="parentDataSource"  
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
        <property name="driverClass"  
            value="oracle.jdbc.pool.OracleConnectionPoolDataSource" />  
        <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl" />  
        <property name="user" value="isc_v10" />  
        <property name="password" value="isc" />  
    </bean>  
  
    <!-- 數據源 -->  
    <bean id="orclDataSource" parent="parentDataSource">  
        <property name="user" value="orcl" />  
        <property name="password" value="orcl" />  
    </bean>  
  
    <!-- 數據源 -->  
    <bean id="iscDataSource" parent="parentDataSource">  
        <property name="user" value="isc_v10" />  
        <property name="password" value="isc" />  
    </bean>  
  
    <!-- 編寫spring 配置文件的配置多數源映射關係 -->  
    <bean id="dataSource" class="com.wy.config.DynamicDataSource">  
        <property name="targetDataSources">  
            <map key-type="java.lang.String">  
                <entry key="ORCL" value-ref="orclDataSource"></entry>  
                <entry key="ISC" value-ref="iscDataSource"></entry>  
            </map>  
        </property>  
        <property name="defaultTargetDataSource" ref="orclDataSource">  
        </property>  
    </bean>  
  
    <bean id="sessionFactory"  
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">  
        <property name="dataSource" ref="dataSource" />  
</bean>

五、使用

   @Test

public void testSave() {  
    // hibernate建立實體  
    DynamicDataSourceHolder.setDataSourceType(DynamicDataSourceGlobal.ORCL);// 設置爲另外一個數據源  
    com.wy.domain.Test user = new com.wy.domain.Test();  
  
    user.setName("WY");  
    user.setAddress("BJ");  
  
    testDao.save(user);// 使用dao保存實體  
  
    DynamicDataSourceHolder.setDataSourceType(DynamicDataSourceGlobal.ISC);// 設置爲另外一個數據源  
  
    testDao.save(user);// 使用dao保存實體到另外一個庫中  
  
}
相關文章
相關標籤/搜索