spring多數據源的配置-以及原理

spring多數據源的配置

創建一個類 繼承 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
重寫方法 determineCurrentLookupKey

package com.sky.lp.util;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class ExtendsAbstractRoutingDataSource extends AbstractRoutingDataSource{
    //使用ThreadLocal 保證線程安全
    private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
    public static void setDataSourceKey(String dataSource) {
        dataSourceKey.set(dataSource);
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return dataSourceKey.get();
    }
}

數據源配置

<!-- 定義數據源 使用 druid 包 -->
     <bean id ="dataSource1" class= "com.alibaba.druid.pool.DruidDataSource" init-method= "init" destroy-method ="close">
            <property name ="name" value="druidOne" />
            <property name ="url" value= "jdbc:mysql://localhost:3306/littledemo" />
            <property name ="username" value="root" />
            <property name ="password" value= "laixu785^@#"></property >
            <property name ="driverClassName" value= "com.mysql.jdbc.Driver"></property >
            <property name ="initialSize" value="2" />
            <property name ="maxActive" value="10" />
            <property name ="minIdle" value="5" />
            <property name ="validationQuery" value= "SELECT COUNT(*) FROM DUAL" />
            <property name ="testWhileIdle" value="true" />
            <property name ="timeBetweenEvictionRunsMillis" value= "5000" />
     </bean >

     <!-- 定義數據源 使用 druid 包 -->
     <bean id ="dataSource2" class= "com.alibaba.druid.pool.DruidDataSource" init-method= "init" destroy-method ="close">
            <property name ="name" value="druidTwo" />
            <property name ="url" value= "jdbc:mysql://localhost:3306/doubleo" />
            <property name ="username" value="root" />
            <property name ="password" value= "laixu785^@#"></property >
            <property name ="driverClassName" value= "com.mysql.jdbc.Driver"></property >
            <property name ="initialSize" value="2" />
            <property name ="maxActive" value="10" />
            <property name ="minIdle" value="5" />
            <property name ="validationQuery" value= "SELECT COUNT(*) FROM DUAL" />
            <property name ="testWhileIdle" value="true" />
            <property name ="timeBetweenEvictionRunsMillis" value= "5000" />
     </bean >

<!-- 多數據源 -->
     <bean id ="extendsAbstractRoutingDataSource" class= "com.sky.lp.util.ExtendsAbstractRoutingDataSource" >
<!-- 默認數據源 -->
        <property name ="defaultTargetDataSource" ref= "dataSource2"/>
<!--    不用的數據源對應不用的key值 -->
        <property name ="targetDataSources">
            <map >
                <entry key ="dataSource1" value-ref= "dataSource1"/>
                <entry key ="dataSource2" value-ref= "dataSource2"/>
            </map >
        </property >
     </bean >

spring jdbcTemplate 數據源的使用,
<bean id= "jdbcTemplate" class= "org.springframework.jdbc.core.JdbcTemplate" >
    <property name ="dataSource" ref= "extendsAbstractRoutingDataSource" />
</bean >

注意:
spring集成 ibatis 或者是 hibernate 或者其他的 ORM 框架,使用的數據源都是,extendsAbstractRoutingDataSource。

測試代碼

public static void main(String[] args) {
ClassPathXmlApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-servlet.xml");

        ExtendsAbstractRoutingDataSource myDataSource = appCtx.getBean("extendsAbstractRoutingDataSource", ExtendsAbstractRoutingDataSource.class);
        myDataSource.setDataSourceKey("dataSource1");

        JdbcTemplate jdbc = appCtx.getBean("jdbcTemplate", JdbcTemplate.class);
        List<?> list = jdbc.queryForList("select * from user");
        System.out.println(list);
}

spring多數據源工作原理(即多個數據源到底應該選擇哪一下)

原理分析(這裏以 spring 的 JdbcTemplate 爲例子)
JdbcTemplate 中執行sql語句,是從 dataSource 中獲取 數據庫 Connection,通過Connection 執行sql 語句。
那麼Connection是怎麼獲取到的呢,是通過
org.springframework.util.Assert.DataSourceUtils.getConnection(getDataSource()) 獲取Connection的。

方法 getDataSource() 源碼

public DataSource getDataSource() {
    return dataSource;
}

返回的dataSource,即配置 jdbcTemplate bean 的屬性dataSource<property name ="dataSource" ref= "extendsAbstractRoutingDataSource" />
看一下org.springframework.util.Assert.DataSourceUtils.getConnection(getDataSource()) 的源碼

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
            try {
                 return doGetConnection(dataSource);
           } catch (SQLException ex) {
                 throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
           }
}

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
            Assert.notNull(dataSource, "No DataSource specified");
            ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
            if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction()))

                conHolder.requested();
            if (!conHolder.hasConnection())

                 logger.debug("Fetching resumed JDBC Connection from DataSource");
           conHolder.setConnection(dataSource.getConnection());

            return conHolder.getConnection();

            logger.debug("Fetching JDBC Connection from DataSource");
           Connection con = dataSource.getConnection();
            if (TransactionSynchronizationManager.isSynchronizationActive())

                 logger.debug("Registering transaction synchronization for JDBC Connection");
            ConnectionHolder holderToUse = conHolder;
            if (holderToUse == null)
                holderToUse = new ConnectionHolder(con);
            else
                holderToUse.setConnection(con);
           holderToUse.requested();
           TransactionSynchronizationManager.registerSynchronization( new ConnectionSynchronization(holderToUse, dataSource));
           holderToUse.setSynchronizedWithTransaction( true);
            if (holderToUse != conHolder)
                 TransactionSynchronizationManager.bindResource(dataSource, holderToUse);

            return con;
}

解讀上面的代碼,最終的目的就是要從dataSource中獲取數據庫的Connection。而獲取數據庫的Connection必須調用dataSource的getConnection。
(即 javax.sql.DataSource的接口規定 getConnection() 方法 )。
所以,可以在從dataSource獲取Connection的過程中做手腳。這就是
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 做的工作。
AbstractRoutingDataSource 繼承 org.springframework.jdbc.datasource.AbstractDataSource,AbstractDataSource 實現 javax.sql.DataSource 接口。

下面看一下 AbstractRoutingDataSource 的方法 getConnection 的源碼

public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
}

protected DataSource determineTargetDataSource() {
    Assert.notNull(resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = (DataSource)resolvedDataSources.get(lookupKey);
    if(dataSource == null && (lenientFallback || lookupKey == null))
        dataSource = resolvedDefaultDataSource;

    if(dataSource == null)
        throw new IllegalStateException((new StringBuilder()).append("Cannot determine target DataSource for lookup key [").append(lookupKey).append("]").toString());

     return dataSource;
}

注意 源碼中的方法 determineCurrentLookupKey(),我們自己定義的類 ExtendsAbstractRoutingDataSource 重寫了 AbstractRoutingDataSource 中的 determineCurrentLookupKey 方法。

到這裏大家應該明白了,spring的動態數據庫切換是怎麼實現的了。至於其他的地方,大家有不明白的就看看源碼。

發佈了67 篇原創文章 · 獲贊 12 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章