• 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();
    }
  • 相关阅读:
    Freemarker空值判断
    php集成开发环境IDE
    mysql卸载
    Apache网站根目录
    冒泡排序
    线程操作
    通过滚轮改变图片大小
    Timer计时器
    写异常日志
    异常处理
  • 原文地址:https://www.cnblogs.com/yyh081616/p/3608133.html
Copyright © 2020-2023  润新知