zoukankan      html  css  js  c++  java
  • 切换数据库+ThreadLocal+AbstractRoutingDataSource 一

    最近项目用的数据库要整合成一个,所以把多源数据库切换的写法要清除掉。所以以下记载了多远数据库切换的用法及个人对源码的理解。

    框架:Spring+mybatis+vertx,(多源数据库切换的用法不涉及vertx,所以,适用于ssh,sm,ssh...)。

    数据库:mysql

    两个关键的api:

    一:ThreadLocal,

    二:AbstractRoutingDataSource。

    我一直坚持先先学会使用,在去探究源码和原理。

    部分一(实现代码):

    以下为实现代码:

    DatabaseSource.xml:

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="1111111"/>
        <property name="password" value="1111111"/>
        <property name="connectionProperties" value="com.mysql.jdbc.Driver"/>
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="2"/>
        <property name="maxWait" value="60000"/>
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
        <property name="filters" value="stat"/>
    </bean>
    <bean id="manager" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" parent="dataSource">
        <property name="url" value="jdbc:mysql://localhost:33308/manager?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true"/>
    </bean>
    <bean id="lawSh" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" parent="dataSource">
        <property name="url" value="jdbc:mysql://localhost:33308/law_hz?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true"/>
    </bean>
    
    <bean id="multipleDataSource" class="com.rayeye.law.app.dao.base.MultipleDataSource">
        <property name="defaultTargetDataSource" ref="lawSh"/>
        <property name="targetDataSources">
            <map>
                <entry key="d_1" value-ref="manager"/>
                <entry key="d_0" value-ref="lawSh"/>
            </map>
        </property>
    </bean>
    <!-- 定义事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="multipleDataSource"/>
    </bean>
    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="100" />
    

      

    ...
    以上为数据库的设置,
    解说:
    dataSource为两个数据库共同的参数设置;
    manager和lawsh的url部分单独设置;
    multipleDataSource为共其他bean使用的数据库,封装了两个数据库连接,设置默认的数据库连接池:defaultTargetDataSource属性值:lawsh;


    部分二:
    动态切换部分:
    public class MultipleDataSource extends AbstractRoutingDataSource {
        public static Logger logger= LoggerFactory.getLogger(MultipleDataSource.class);
        private static final ThreadLocal<String> dataSourceKey =new ThreadLocal<String>();
    
    
        public static void setDataSourceKey(String dataSource) {
            logger.debug("数据源切换====="+dataSource);
            dataSourceKey.set(dataSource);
        }
        @Override
        protected Object determineCurrentLookupKey() {
            logger.debug("dataSource====key:"+dataSourceKey.get());
            return dataSourceKey.get();
        }
    
        public static void setDataSourceByBid(String db) {
            if(db.equals(Constant.DB1)){
                setDataSourceKey(Constant.DB1) ;
            }else{
                setDataSourceKey(Constant.DB0) ;
            }
        }
    }
    
    @Component
    @Aspect
    @Order(2)
    public class MultipleMailSourceAspectAdvice {
        Logger logger =Logger.getLogger(MultipleMailSourceAspectAdvice.class);
        @Around("execution(* com.rayeye.law.app.service.*.*(..))")
        public Object doAround(ProceedingJoinPoint jp) throws Throwable {
            logger.info("doAround ==================dataSourse  =========");
            MultipleDataSource.setDataSourceKey(Constant.DB0);
    
            return jp.proceed();
        }
    }
    
    
    @Component
    @Aspect
    @Order(3)
    public class MultipleManagerSourceAspectAdvice {
        Logger logger =Logger.getLogger(MultipleManagerSourceAspectAdvice.class);
        @Around("execution(* com.rayeye.law.app.service_manager.DingService.*(..))")
        public Object doAround(ProceedingJoinPoint jp) throws Throwable {
            logger.info("doAround 2 ==================dataSourse  =========");
            MultipleDataSource.setDataSourceKey(Constant.DB1);
            return jp.proceed();
        }
    }
    

      


    以上为代码部分,
    如此,则你的代码可实现指定java类中的方法使用某个数据库连接。

    以下为代码及源码解析部分:
    关键api一:AbstractRoutingDataSource:
    该类的完整路径:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    本来我想先写ThreadLocal,但刚写几行,觉着先写AbstractRoutingDataSource更容易理解,所以将AbstractRoutingDataSource往前排了。
    以下为源码部分:
    AbstractRoutingDataSource类可以理解为DataSource的路由中介,可以通过它来切换数据库。
    在前面的代码中,我重写了AbstractRoutingDataSource类的determineCurrentLookupKey方法,在该方法中切换了数据库。
    原因是因为AbstarctRoutingDatraSource以下一段代码(源码):
    @Override
    public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
    }
     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; }

      其中,determineCurrentLookupKey()就是前面代码中重写的方法,在我们重写的方法中指定了使用哪一个数据库,

    此处的变量resolvedDataSources(是一个map)里面就存储着我们在配置文件中设置的两个数据库和它们的key值;
    下一段源码显示了resolvedDataSources的来源:
    @Override
    public void afterPropertiesSet() {
       if (this.targetDataSources == null) {
          throw new IllegalArgumentException("Property 'targetDataSources' is required");
       }
       this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
       for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
          Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
          DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
          this.resolvedDataSources.put(lookupKey, dataSource);
       }
       if (this.defaultTargetDataSource != null) {
          this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
       }
    }
    其中的targetDataSources 就是我们在配置文件中

    <property name="targetDataSources">

    <map>
        <entry key="d_1" value-ref="manager"/>
    <entry key="d_0" value-ref="lawSh"/>
    </map>
    </property>
    它是一个map,里面存储了两个数据库连接池和对应的key;
    在for循环中,spring将数据库连接池和对应的key值放到了resolvedDataSources(一个Map)中;
    其中的两个方法的源码如下:
    protected Object resolveSpecifiedLookupKey(Object lookupKey) {
       return lookupKey;
    }
    protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
       if (dataSource instanceof DataSource) {
          return (DataSource) dataSource;
       }
       else if (dataSource instanceof String) {
          return this.dataSourceLookup.getDataSource((String) dataSource);
       }
       else {
          throw new IllegalArgumentException(
                "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
       }
    }
    至此,可以清楚重写方法determineCurrentLookupKey的目的和意义(切换数据库)。

    关键api二:ThreadLocal:
    该类的完整路径:java.lang.ThreadLocal<T>;
    该类的主要作用是:为每个线程创建独立的局部变量副本,线程之间的ThradLocal互不影响(不同线程使用的不同的数据库,互补影响,线程安全)。
    以下为源码部分:
    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
    }
    void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

      可以看到,所有的变量都存储在一个静态的hash map中(ThreadLocalMap),而这个变量却保存在thread中,也就是说,每个线程对ThreadLocalMap都持有一个引用(ThreadLocalMap初始化时再ThreadLocal中进行的)。

    至此,可以理解ThreadLocal这个类在切换数据库中的作用了(保存每个线程的数据库连接标志,以供使用和切换)。

    至此,数据库切换大部分讲完了,还剩下代码里的注解和databaseSource.xml中的  事务管理器的  order属性没讲,这部分涉及事务管理和切面编程,今天有点事,这个部分下次讲。

    为什么突然会想起来把数据库切换给写下来,是因为注意到spring和mybatis整合后的:

        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.rayeye.bill.api.dao"/>
        </bean>
    

      这个类的属性sqlSessionFactory,

    以下为源码:

      /**
       * Specifies which {@code SqlSessionFactory} to use in the case that there is
       * more than one in the spring context. Usually this is only needed when you
       * have more than one datasource.
       * <p>
       * @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead.
       *
       * @param sqlSessionFactory
       */
      @Deprecated
      public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
      }
    

      可以看见,当配置单个数据库连接的时候,这个属性可以使用,但配置多个数据库连接的时候,这个属性就并不适用了,而且这个类的部分属性好像已经被弃用了。

    以上。

    end。

    
    
    
    
  • 相关阅读:
    SGU 242. Student's Morning( 网络流 )
    8.23单词
    bzoj1083:繁忙的城市
    求连通分量
    bzoj2761:不重复数字
    bzoj1207:打鼹鼠
    bzoj2748:音量调节
    bzoj1050:旅行comf
    bzoj1996:合唱队
    羞耻羞耻羞耻!!!
  • 原文地址:https://www.cnblogs.com/zqsky/p/6142376.html
Copyright © 2011-2022 走看看