zoukankan      html  css  js  c++  java
  • AbstractRoutingDataSource动态切换数据源

     一:流程图解

     灵活动态的切换数据源,每次在执行一个Dao操作之前可以设置当前的数据源。

     二:实现原理

      

       1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)

        从AbstractRoutingDataSource的源码中

      我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:

     获取连接的方法中,重点是determineTargetDataSource()方法,看源码:

    这个类是实现多数据源的关键,他的作用就是动态切换数据源,实质:有多少个数据源就存多少个数据源在targetDataSources(是AbstractRoutingDataSource的一个map类型的属性,其中value为每个数据源,key表示每个数据源的名字)这个属性中,然后根据determineCurrentLookupKey()这个方法获取当前数据源在map中的key值,然后determineTargetDataSource()方法中动态获取当前数据源,如果当前数据源不存并且默认数据源也不存在就抛出异常。

     

    public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
        //多数据源map集合
        private Map<Object, Object> targetDataSources;
        //默认数据源
        private Object defaultTargetDataSource;
        //其实就是targetDataSources,后面的afterPropertiesSet()方法会将targetDataSources赋值给resolvedDataSources
        private Map<Object, DataSource> resolvedDataSources;
        private DataSource resolvedDefaultDataSource;
        public void setTargetDataSources(Map<Object, Object> targetDataSources) {
            this.targetDataSources = targetDataSources;
        }
        protected DataSource determineTargetDataSource() {
            Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            Object lookupKey = this.determineCurrentLookupKey();
            DataSource 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 + "]");
            } else {
                return dataSource;
            }
        }
        protected abstract Object determineCurrentLookupKey();
    }

    上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,

    resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。

     看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

    public class DynamicDataSource extends AbstractRoutingDataSource  {
    
        /**
         * override determineCurrentLookupKey
         * Description: 自动查找datasource
         */
        @Override
        protected Object determineCurrentLookupKey() {
            //从自定义的位置获取数据源标识
            return DynamicDataSourceHolder.getDataSource();
        }
        
    
    }

     DynamicDataSourceHolder这个类则是我们自己封装的对数据源进行操作的类:

    public class DynamicDataSourceHolder {
         /**
         * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
         */
        private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();
    
    //获取数据源
    public static String getDataSource() { return THREAD_DATA_SOURCE.get(); } //设置数据源 public static void setDataSource(String dataSource) { THREAD_DATA_SOURCE.set(dataSource); } //清空数据源 public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); } }

    那么setDataSource方法要在什么地方执行呢?

    我们可以应用Spring aop来设置,把配置的数据源类型都设置成注解标签,在Servive层中需要切换数据源的方法上,写上注解标签,调用相应的方法切换数据源

    @DataSource(name=DataSource.slave1)
    public List getProducts(){
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource{
        String name() default DataSource.master;
        
        public static String master="datasource1";
        
        public static String slave1="datasource2";
    
        publid static String slave2="datasource3";
    }

    三:配置文件

    项目中单独分离出application-database.xml,关于数据源配置的文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Spring 数据库相关配置 放在这里 -->
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
     
    <bean id = "dataSource1" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">   
            <property name="url" value="${db1.url}"/>
            <property name = "user" value = "${db1.user}"/>
            <property name = "password" value = "${db1.pwd}"/>
            <property name="autoReconnect" value="true"/>
            <property name="useUnicode"  value="true"/>
            <property name="characterEncoding" value="UTF-8"/>
        </bean>
     
        <bean id = "dataSource2" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
            <property name="url" value="${db2.url}"/>
            <property name = "user" value = "${db2.user}"/>
            <property name = "password" value = "${db2.pwd}"/>
            <property name="autoReconnect" value="true"/>
            <property name="useUnicode"  value="true"/>
            <property name="characterEncoding" value="UTF-8"/>
        </bean>
     
        <bean id = "dataSource3" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
            <property name="url" value="${db3.url}"/>
            <property name = "user" value = "${db3.user}"/>
            <property name = "password" value = "${db3.pwd}"/>
            <property name="autoReconnect" value="true"/>
            <property name="useUnicode"  value="true"/>
            <property name="characterEncoding" value="UTF-8"/>
        </bean>


    </beans>

    配置多数据源映射关系

     <!-- 配置多数据源映射关系 -->
        <bean id="dataSource" class="com.datasource.test.util.database.DynamicDataSource">
            <property name="targetDataSources">
                <map key-type="java.lang.String">
                    <entry key="dataSource1" value-ref="dataSource1"></entry>
                    <entry key="dataSource2" value-ref="dataSource2"></entry>
                    <entry key="dataSource3" value-ref="dataSource3"></entry>
                </map>
            </property>
        <!-- 默认目标数据源为你主库数据源 -->
            <property name="defaultTargetDataSource" ref="dataSource1"/>
        </bean>

    SessionFactory的配置还是照旧,使用以前的配置,只不过当前选择的数据源是datasource,也就是数据源选择的中间层MultipleDataSourceToChoose,因为当前的中间层中实现了DataSource这个接口,所以可以看做为DataSource的是实现类啦,所以配置不会出现问题。

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
            lazy-init="false">
            <property name="configLocation" value="classpath:caravaggio-mybatis-config.xml"></property>
            <property name="dataSource" ref="dataSource" />
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <property name="basePackage" value="com.zmeng.rinascimento.caravaggio.mapper" />
    </bean>

    使用AOP拦截特定的注解去动态的切换数据源

    @Order(1)如果有多个拦截器时,第一个执行

    @Aspect
    @Slf4j
    @Component
    @Order(1)
    public class DataSourceAop {
    
        //@within在类上进行设置
        //@annotation 在方法上进行设置
        @Pointcut("@within(com.zmeng.rinascimento.caravaggio.common.util.datasource.DataSource)||@annotation(com.zmeng.rinascimento.caravaggio.common.util.datasource.DataSource)")
        public void pointcut(){}
    
        @Before("pointcut()")
        public void doBefore(JoinPoint joinPoint)throws Exception{
           Class clazz = joinPoint.getTarget().getClass();
           String methodName = joinPoint.getSignature().getName();
            Class[] parameterTypes =  ((MethodSignature)joinPoint.getSignature()).getMethod().getParameterTypes();
            Method method  =  clazz.getMethod(methodName,parameterTypes);
            //获取方法上的注解
            DataSource dataSource = method.getAnnotation(DataSource.class);
            if(dataSource==null){
                //获取类上的注解
                dataSource = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
                if(dataSource==null){
                    return;
                }
            }
    
           //获取注解上的数据源信息
            String dataSourceKey = dataSource.value();
           if(dataSourceKey!=null){
               //给当前执行SQL操作设置特殊的数据源的信息
               DynamicDataSourceHolder.setDataSource(dataSourceKey);
               System.out.println(DynamicDataSourceHolder.getDataSource());
           }
           log.info("AOP动态切换数据源,className"+joinPoint.getTarget().getClass().getName()+"methodName"+method.getName()+";dataSourceKey:"+dataSourceKey==""?"默认数据源":dataSourceKey);
        }
    
        @After("pointcut()")
        public void after(JoinPoint joinPoint){
            //清理掉当前的数据源,让默认的数据不受影响
            System.out.println(DynamicDataSourceHolder.getDataSource());
            DynamicDataSourceHolder.clearDataSource();
        }
    }

    如果不使用aop进行拦截切换数据源,也可以在代码中进行手动的切换数据源

    DynamicDataSourceHolder.setDataSource("dataSource");

     

  • 相关阅读:
    R语言 主成分分析
    主成分分析(PCA)及其在R里的实现
    UML类图几种关系的总结
    微信Android客户端架构演进之路
    Android单元测试实践
    Android studio 快捷键(Mac)
    Android 启动模式及常用的Intent的Flag
    linux常用命令 (mac ),积少成多
    Android Studio IDE 简单学习和介绍
    轻量级分布式 RPC 框架
  • 原文地址:https://www.cnblogs.com/shareTechnologyl/p/11686549.html
Copyright © 2011-2022 走看看