Spring 多数据源动态路由实现原理

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

相关文章
相关标签/搜索