需求:系統中要實現切換數據庫(業務數據庫和his數據庫)
網上很多資料上有提到AbstractRoutingDataSource,大致是這么說的
在Spring 2.0.1中引入了AbstractRoutingDataSource, 該類充當了DataSource的路由中介, 能有在運行時, 根據某種key值來動態切換到真正的DataSource上。
Spring動態配置多數據源,即在大型應用中對數據進行切分,並且采用多個數據庫實例進行管理,這樣可以有效提高系統的水平伸縮性。而這樣的方案就會不同於常見的單一數據實例的方案,這就要程序在運行時根據當時的請求及系統狀態來動態的決定將數據存儲在哪個數據庫實例中,以及從哪個數據庫提取數據。
Spring對於多數據源,以數據庫表為參照,大體上可以分成兩大類情況:
一是,表級上的跨數據庫。即,對於不同的數據庫卻有相同的表(表名和表結構完全相同)。
二是,非表級上的跨數據庫。即,多個數據源不存在相同的表。
Spring2.x的版本中采用Proxy模式,就是我們在方案中實現一個虛擬的數據源,並且用它來封裝數據源選擇邏輯,這樣就可以有效地將數據源選擇邏輯從Client中分離出來。Client提供選擇所需的上下文(因為這是Client所知道的),由虛擬的DataSource根據Client提供的上下文來實現數據源的選擇。
具體的實現就是,虛擬的DataSource僅需繼承AbstractRoutingDataSource實現determineCurrentLookupKey()在其中封裝數據源的選擇邏輯
一、原理
首先看下AbstractRoutingDataSource類結構,繼承了AbstractDataSource:
public abstract class AbstractRoutingDataSource extends org.springframework.jdbc.datasource.AbstractDataSource implements org.springframework.beans.factory.InitializingBean
既然是AbstractDataSource,當然就是javax.sql.DataSource的子類,於是我們自然地回去看它的getConnection方法:
public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); }
原來關鍵就在determineTargetDataSource()里:
1 protected DataSource determineTargetDataSource() { 2 Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); 3 Object lookupKey = determineCurrentLookupKey(); 4 DataSource dataSource = this.resolvedDataSources.get(lookupKey); 5 if (dataSource == null && (this.lenientFallback || lookupKey == null)) { 6 dataSource = this.resolvedDefaultDataSource; 7 } 8 if (dataSource == null) { 9 throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); 10 } 11 return dataSource; 12 }
這里用到了我們需要進行實現的抽象方法determineCurrentLookupKey(),該方法返回需要使用的DataSource的key值,然后根據這個key從resolvedDataSources這個map里取出對應的DataSource,如果找不到,則用默認的resolvedDefaultDataSource。
回過頭看AbstractDataSource的afterPropertiesSet方法:
1 public void afterPropertiesSet() { 2 if (this.targetDataSources == null) { 3 throw new IllegalArgumentException("Property 'targetDataSources' is required"); 4 } 5 this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); 6 for (Map.Entry entry : this.targetDataSources.entrySet()) { 7 Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); 8 DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); 9 this.resolvedDataSources.put(lookupKey, dataSource); 10 } 11 if (this.defaultTargetDataSource != null) { 12 this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); 13 } 14 }
配置數據源實例:
<bean id="onlineDynamicDataSource" class="com.xx.stat.base.dynamic.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="xx" value-ref="dataSourceXX"/> <entry key="yy" value-ref="dataSourceYY"/> </map> </property> <property name="defaultTargetDataSource" ref="dataSource"/> </bean>
觀察上面的配置文件,發現我們配置的是targetDataSources和defaultTargetDataSource
二、Spring配置多數據源的方式和具體使用過程
1、數據源的名稱常量類
1 public enum DatabaseTypeEnum { 2 DB_DLHMC("dlhmc", "dlhmc數據庫,默認的數據庫"),DB_HIS("his", "HIS數據庫"); 3 private String value; 4 private String desc; 5 6 private DatabaseTypeEnum(String value, String description) { 7 this.value = value; 8 this.desc = description; 9 } 10 11 public String getValue() { 12 return value; 13 } 14 15 public String getDesc() { 16 return desc; 17 } 18 19 @Override 20 public String toString() { 21 22 return "{" + value + ":" + desc + "}"; 23 } 24 25 public static DatabaseTypeEnum from(String value) { 26 for (DatabaseTypeEnum item : values()) { 27 if (item.getValue() == value) { 28 return item; 29 } 30 } 31 throw new IllegalArgumentException(String.format( 32 "非法的輸入參數 '%s' ! 必須是%s中的其中一個。", value, Arrays.asList(values()) 33 .toString())); 34 } 35 36 }
2、建立一個獲得和設置上下文環境的類,主要負責改變上下文數據源的名稱
1 public class DatabaseContextHolder { 2 private static ThreadLocal<String> contextHolder=new ThreadLocal<String>(); 3 public static void setDbType(String dbType){ 4 contextHolder.set(dbType); 5 } 6 public static String getDbType(){ 7 return contextHolder.get(); 8 } 9 10 public static void clearDbType(){ 11 contextHolder.remove(); 12 } 13 14 }
3、建立動態數據源類,注意,這個類必須繼承AbstractRoutingDataSource,且實現方法 determineCurrentLookupKey,該方法返回一個Object,一般是返回字符串
1 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 2 3 public class DynamicDataSource extends AbstractRoutingDataSource{ 4 @Override 5 protected Object determineCurrentLookupKey() { 6 return DatabaseContextHolder.getDbType(); 7 } 8 9 }
4、編寫spring的配置文件配置多個數據源
1 <!-- 數據源配置 --> 2 <bean id="defaultDS" class="com.alibaba.druid.pool.DruidDataSource" p:driverClassName="${jdbc.driver}" p:url="${jdbc.jdbcUrl}" p:username="${jdbc.username}" p:password="${jdbc.password}" 3 p:initialSize="${jdbc.initialSize}" p:maxActive="${jdbc.maxActive}" p:testOnBorrow="${jdbc.testOnBorrow:false}" destroy-method="close"> 4 </bean> 5 <bean id="hisDS" class="com.alibaba.druid.pool.DruidDataSource" p:driverClassName="${his.jdbc.driver}" p:url="${his.jdbc.jdbcUrl}" p:username="${his.jdbc.username}" p:password="${his.jdbc.password}" 6 p:initialSize="${his.jdbc.initialSize}" p:maxActive="${his.jdbc.maxActive}" p:testOnBorrow="${his.jdbc.testOnBorrow:false}" destroy-method="close"> 7 </bean> 8 <bean id="dataSource" class="com.supconit.util.datasource.DynamicDataSource"> 9 <property name="targetDataSources"> 10 <map key-type="java.lang.String"> 11 <entry key="dlhmc" value-ref="defaultDS" /> 12 <entry key="his" value-ref="hisDS" /> 13 <!-- entry key="2" value-ref="ds2" / --> 14 </map> 15 </property> 16 <property name="defaultTargetDataSource" ref="defaultDS" /> 17 </bean>
5、使用
@Override public List<VBedPatientNew> selectNursinglevel() { DatabaseContextHolder.setDbType(DatabaseTypeEnum.DB_HIS.getValue()); List<VBedPatientNew> result=selectList("selectNursinglevel"); DatabaseContextHolder.clearDbType(); return result; }