概述:随着业务独立性强,数据量大的时候的,为了提升并发,可能会对表进行分库,分库后,以及读写分离的实现,每个数据库都须要配置一个数据源。在此,作一个备份~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