spring dataSourceRouter自動切換數據源


  spring多數據源的切換,主要用到的是AbstractRoutingDataSource這個路由類,當我們的自定義的一個路由分發類繼承AbstractRoutingDataSource類后,重寫determineCurrentLookupKey()這個方法,重寫的內容就是我們的分發規則。那么spring在需要選擇數據源的時候,就會執行這個方法,然后根據我們的自定義的規則自動進行分發,從而實現多數據源的切換。

  步驟如下:

  1、配置多個數據源

 <!-- 
  <bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driverClass}" />
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
    <property name="user" value="${jdbc.user}" />
    <property name="password" value="${jdbc.password}" />
    <property name="initialPoolSize" value="${c3p0.initialPoolSize}" />
    <property name="minPoolSize" value="${c3p0.minPoolSize}" />
    <property name="maxPoolSize" value="${c3p0.maxPoolSize}" />
    <property name="maxStatements" value="${c3p0.maxStatements}" />
    <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}" />
    <property name="maxIdleTime" value="${c3p0.maxIdleTime}" />
    <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}" />
  </bean>
 -->

  <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.jdbcUrl}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> <property name="initialSize" value="${druid.initialSize}" /> <property name="minIdle" value="${druid.minIdle}" /> <property name="maxActive" value="${druid.maxActive}" /> <property name="maxWait" value="${druid.maxWait}" /> <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${druid.validationQuery}" /> <property name="testWhileIdle" value="${druid.testWhileIdle}" /> <property name="testOnBorrow" value="${druid.testOnBorrow}" /> <property name="testOnReturn" value="${druid.testOnReturn}" /> <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" /> <property name="filters" value="${druid.filters}" /> </bean>
<bean id="druidDataSource_1" parent="druidDataSource"> <property name="url" value="${jdbc.jdbcUrl1}" /> </bean>

  2、配置路由器,注意這個odd和even,他就是我們數據源的標識,在AbstractRoutingDataSource路由determineCurrentLookupKey()這個方法中返回哪個,就是用其對應的數據源。如返回"odd"就是用druidDataSource數據源。

<bean id="dataSourcreRouter" class="cn.yyh.router.NMRoutingDataSource">
      <property name="targetDataSources">
            <map>
                <entry key="odd" value-ref="druidDataSource" />
                <entry key="even" value-ref="druidDataSource_1" />
            </map>
      </property>
      <property name="defaultTargetDataSource" ref="druidDataSource" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSourcreRouter" /> </bean>

  3、編寫AbstractRoutingDataSource路由分發器,這個NMRoutingToken是什么叻?這個NMRoutingToken是一個令牌,他是存儲在ThreadLocal中的一個對象。而ThreadLocal其實就是當前線程內的一個map對象。

public class NMRoutingDataSource extends AbstractRoutingDataSource {

    protected Object determineCurrentLookupKey() {
         NMRoutingToken token = NMRoutingToken.getCurrentToken();
        if (token != null) {
            String dataSourceName = token.getDataSourceName();
            // 解除令牌
            NMRoutingToken.unbindToken();
            return dataSourceName;
        }
        return null;
    }

}

  4、可以看到,其實NMRoutingToken就是在當前線程中的ThreadLocal存儲一個字符串,前面我們就說過determineCurrentLookupKey()返回什么,spring就自動選擇其對應的數據源。那么如果我們new NMRoutingToken("odd"),他就會綁定"odd"到ThreadLocal中,然后在determineCurrentLookupKey()取出來返回就可以了。

public class NMRoutingToken {

    private static ThreadLocal<NMRoutingToken> token = new ThreadLocal<NMRoutingToken>();
    private String dataSourceName;
    
    public String getDataSourceName() {
        return dataSourceName;
    }

    public NMRoutingToken(String dataSourceName) {
        super();
        this.dataSourceName = dataSourceName;
        bindToken(this);
    }

    /**
     * 綁定令牌對象到當前線程
     */
    private static void bindToken(NMRoutingToken t) {
        token.set(t);
    }

    /**
     * 解除與當前線程綁定的令牌
     */
    public static void unbindToken() {
        token.remove();
    }

    /**
     * 取得與當前線程綁定的令牌
     */
    public static NMRoutingToken getCurrentToken() {
        return token.get();
    }

}

  5、測試

public class NMDataSourceTest extends BaseTest {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private IUserService userService;

    @Test
    /**
     * 未開啟事務
     */
    public void testMapper() {
        /**
         * 不配置任何數據庫,使用默認數據庫
         */
        int count1 = userMapper.getCount();
        System.out.println(count1);

        /**
         * 配置任何數據庫odd
         */
        NMRoutingToken token = new NMRoutingToken("odd");
        count1 = userMapper.getCount();
        System.out.println(count1);

        /**
         * 配置任何數據庫even
         */
        token = new NMRoutingToken("even");
        count1 = userMapper.getCount();
        System.out.println(count1);

        /**
         * 亂配將使用默認數據庫
         */
        token = new NMRoutingToken("asdf");
        count1 = userMapper.getCount();
        System.out.println(count1);
    }

    @Test
    /**
     * 開啟事務
     * 注意,由於tomcat容器不支持跨庫事務,所以在選擇數據源,開啟事務后就會掛起,
     * 就算再次綁定令牌,但它不會再執行NMRoutingDataSource的determineCurrentLookupKey()方法了,
     * 所以后續選擇數據源是無效的,默認還是第一次選擇的數據源
     */
    public void testService() {
        userService.getNMCount();
    }
}
@Transactional(readOnly = true)
    public void getNMCount() {
        int count1 = -1;
        /**
         * 配置數據庫even
         */
        new NMRoutingToken("even");
        count1 = userMapper.getCount();
        System.out.println(count1);
        
        /**
         * 配置數據庫odd無效,默認還是使用even數據源
         */
        new NMRoutingToken("odd");
        count1 = userMapper.getCount();
        System.out.println(count1);
    }

實例下載:示例下載

注:此處的令牌里面存儲的是一個字符串,此為最簡單的分發規則,我們自己手動選擇,如果需要根據對象的某些屬性進行自動選擇,那么可以在令牌中存儲對應的對象,然后在路由中取出對應的屬性,如id,然后根據我們的規則,動態返回,這樣就不需要我們自己指定了。

注意:tomcat等容器不支持跨庫事務,所以只能針對單個service進行事務控制。
不能在同一個開啟事務的service中同時操作多個數據庫。
如:
@Tranctional
AService(){
    methodA(){
        dataSourceA.insert();
        dataSourceB.update();
    }
}
AService(){
    @Tranctional
    methodA(){
        dataSourceA.insert();
        dataSourceB.update();
    }
}
此類寫法(在類或方法上開啟事務,同時操作多個數據庫)都是不允許的,原理請見src/test/java/ResourceTest.java


只能跟別進行事務控制:
Service(){
    aService.insert();
    bService.update();
}
AService{
    @Tranctional
    insert();
}
BService{
    @Tranctional
    update();
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM