SpringMVC 利用AbstractRoutingDataSource實現動態數據源切換
本文轉載至:http://exceptioneye.iteye.com/blog/1698064
Spring動態配置多數據源,即在大型應用中對數據進行切分,並且采用多個數據庫實例進行管理,這樣可以有效提高系統的水平伸縮性。而這樣的方案就會不同於常見的單一數據實例的方案,這就要程序在運行時根據當時的請求及系統狀態來動態的決定將數據存儲在哪個數據庫實例中,以及從哪個數據庫提取數據。
Spring對於多數據源,以數據庫表為參照,大體上可以分成兩大類情況:
一是,表級上的跨數據庫。即,對於不同的數據庫卻有相同的表(表名和表結構完全相同)。
二是,非表級上的跨數據庫。即,多個數據源不存在相同的表。
Spring2.x的版本中采用Proxy模式,就是我們在方案中實現一個虛擬的數據源,並且用它來封裝數據源選擇邏輯,這樣就可以有效地將數據源選擇邏輯從Client中分離出來。Client提供選擇所需的上下文(因為這是Client所知道的),由虛擬的DataSource根據Client提供的上下文來實現數據源的選擇。
具體的實現就是,虛擬的DataSource僅需繼承AbstractRoutingDataSource實現determineCurrentLookupKey()在其中封裝數據源的選擇邏輯。
一、原理
首先看下AbstractRoutingDataSource類結構,繼承了AbstractDataSource
<span>public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean</span>
既然是AbstractDataSource,當然就是javax.sql.DataSource的子類,於是我們自然地回去看它的getConnection方法:
<span>public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); }</span>
原來關鍵就在determineTargetDataSource()里:
/** * 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。
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); } }
二、Spring配置多數據源的方式和具體使用過程
建立一個獲得和設置上下文環境的類,主要負責改變上下文數據源的名稱
public class DynamicDataSourceHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } public static String getDataSourceType() { return 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>
測試
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保存實體到另一個庫中 }