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

相關文章
相關標籤/搜索