概述:隨着業務獨立性強,數據量大的時候的,爲了提升併發,可能會對錶進行分庫,分庫後,以及讀寫分離的實現,每個數據庫都須要配置一個數據源。在此,作一個備份~java
Spring但數據源的配置此處再也不贅述,多數據源的狀況也與此相似,下面會對配置作詳細的描述。既然是多數據源,必然會引起一個問題:若是在應用運行時,動態的選擇合適的數據源?spring
Spring 2.0.1引入了 AbstractRoutingDataSource 抽象類,實現根據 lookup key 從多個數據源中獲取目標數據源。源碼以下:最主要的方法:determineTargetDataSource() 決定使用哪個數據源,方法中調用了determineCurrentLookupKey()來獲取當前數據源的 lookup key,全部該抽象類的實現類都要實現這個方法。Spring也提供了一個它的實現類:IsolationLevelDataSourceRoutersql
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { private Map<Object, Object> targetDataSources; private Object defaultTargetDataSource; private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); private Map<Object, DataSource> resolvedDataSources; private DataSource resolvedDefaultDataSource; ....//此處省略部分代碼 /** * 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; } /** * 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(); }
IsolationLevelDataSourceRouter的實現以下:該實現類是根據當前事務的隔離級別選擇合適的目標數據源,隔離級別的name做爲key(TransactionDefinition接口中有具體的定義,此處不作詳細描述)數據庫
public class IsolationLevelDataSourceRouter extends AbstractRoutingDataSource { /** Constants instance for TransactionDefinition */ private static final Constants constants = new Constants(TransactionDefinition.class); /** * Supports Integer values for the isolation level constants * as well as isolation level names as defined on the * {@link org.springframework.transaction.TransactionDefinition TransactionDefinition interface}. */ @Override protected Object resolveSpecifiedLookupKey(Object lookupKey) { if (lookupKey instanceof Integer) { return lookupKey; } else if (lookupKey instanceof String) { String constantName = (String) lookupKey; if (!constantName.startsWith(DefaultTransactionDefinition.PREFIX_ISOLATION)) { throw new IllegalArgumentException("Only isolation constants allowed"); } return constants.asNumber(constantName); } else { throw new IllegalArgumentException( "Invalid lookup key - needs to be isolation level Integer or isolation level name String: " + lookupKey); } } @Override protected Object determineCurrentLookupKey() { return TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); } }
在具體的業務處理中須要本身實現AbstractRoutingDataSource,一般只須要實現determineCurrentLookupKey()方法便可:併發
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceHolder.getDataSource(); } }
其中,dataSource是經過 DataSouceHolder 來獲取的,代碼以下:app
public class DataSourceHolder { private static final ThreadLocal<DataSource> dataSources = new ThreadLocal<DataSource>(); public static void setDataSource(DataSource dataSource) { dataSources.set(dataSource); } public static DataSource getDataSource() { return dataSources.get(); } public static void clearDataSource() { dataSources.remove(); } }
注意:事務是線程級別的,不一樣線程之間互不影響,所以使用ThreadLocal來存放當前事務的DataSource。ide
接下來是多個數據源的配置文件:application-dao.xml,配置了三個數據源,parentDataSource爲三個數據源抽出來的公共部分,減小冗餘。高併發
<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" abstract="true"> <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> <property name="username" value="sa"/> </bean> <bean id="dataSource1" parent="parentDataSource"> <property name="url" value="jdbc:hsqldb:hsql://localhost:${db.port.gold}/blog"/> </bean> <bean id="dataSource2" parent="parentDataSource"> <property name="url" value="jdbc:hsqldb:hsql://localhost:${db.port.silver}/blog"/> </bean> <bean id="dataSource3" parent="parentDataSource"> <property name="url" value="jdbc:hsqldb:hsql://localhost:${db.port.bronze}/blog"/> </bean> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:/blog/datasource/db.properties"/> </bean>
而後,須要配置一個動態數據源,相似於目標數據源的代理~ 其中,DynamicDataSource爲前面提到的AbstractRoutingDataSource 的實現類,targetDataSources爲全部數據源的map,defaultTargetDataSource 能夠配置默認使用的數據源。this
<bean id="dataSource" class="blog.datasource.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="online" value-ref="dataSource1"/> <entry key="mirror" value-ref="dataSource2"/> <entry key="default" value-ref="dataSource3"/> </map> </property> <property name="defaultTargetDataSource" ref="dataSource3"/> </bean>
到此,Spring多數據源的配置已經完成,使用時也十分簡單是須要DataSouceHolder.setDataSource(dataSource) 便可(業務中只須要在controller層或者service經過註解的方式注入數據源,在線程級別切換數據庫)。url