zoukankan      html  css  js  c++  java
  • 【AbstractRoutingDataSource】分析

    抽象类AbstractRoutingDataSource,通过扩展这个类实现根据不同的请求切换数据源。

    AbstractRoutingDataSource继承AbstractDataSource,如果声明一个类DynamicDataSource继承AbstractRoutingDataSource后,DynamicDataSource本身就相当于一种数据源。所以AbstractRoutingDataSource必然有getConnection()方法获取数据库连接。如下:

    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }
    
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }
    

    AbstractRoutingDataSourcegetConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。接着看determineTargetDataSource()方法:

    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()方法获取一个key,

    通过key从resolvedDataSources中获取数据源DataSource对象。determineCurrentLookupKey()是个抽象方法,

    需要继承AbstractRoutingDataSource的类实现;而resolvedDataSources是一个Map<Object, DataSource>,里面应该保存当前所有可切换的数据源。

    AbstractRoutingDataSource动态数据源切换

    操作数据一般都是在DAO层进行处理,可以选择直接使用JDBC进行编程(http://blog.csdn.net/yanzi1225627/article/details/26950615/)

    或者是使用多个DataSource 然后创建多个SessionFactory,在使用Dao层的时候通过不同的SessionFactory进行处理,不过这样的入侵性比较明显,一般的情况下我们都是使用继承HibernateSupportDao进行封装了的处理,如果多个SessionFactory这样处理就是比较的麻烦了,修改的地方估计也是蛮多的

    最后一个,也就是使用AbstractRoutingDataSource的实现类通过AOP或者手动处理实现动态的使用我们的数据源,这样的入侵性较低,非常好的满足使用的需求。比如我们希望对于读写分离或者其他的数据同步的业务场景

    下面看看图片 


     

    单数据源的场景(一般的Web项目工程这样配置进行处理,就已经比较能够满足我们的业务需求)

    多数据源多SessionFactory这样的场景,估计作为刚刚开始想象想处理在使用框架的情况下处理业务,配置多个SessionFactory,然后在Dao层中对于特定的请求,通过特定的SessionFactory即可处理实现这样的业务需求,不过这样的处理带来了很多的不便之处,所有很多情况下我们宁愿直接使用封装的JDBC编程,或者使用Mybatis处理这样的业务场景

    使用AbstractRoutingDataSource 的实现类,进行灵活的切换,可以通过AOP或者手动编程设置当前的DataSource,不用修改我们编写的对于继承HibernateSupportDao的实现类的修改,这样的编写方式比较好,至于其中的实现原理,让我细细到来。我们想看看如何去应用,实现原理慢慢的说!

    编写AbstractRoutingDataSource的实现类,HandlerDataSource就是提供给我们动态选择数据源的数据的信息,我们这里编写一个根据当前线程来选择数据源,然后通过AOP拦截特定的注解,设置当前的数据源信息,也可以手动的设置当前的数据源,在编程的类中。

    package com.common.utils.manydatasource;

    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

    /**

    * descrption: 多数据源的选择

    * authohr: wangji

    * date: 2017-08-21 10:32

    */

    public class MultipleDataSourceToChoose extends AbstractRoutingDataSource {

        /**

        * @desction: 根据Key获取数据源的信息,上层抽象函数的钩子

        * @author: wangji

        * @date: 2017/8/21

        * @param:

        * @return:

        */

        @Override

        protected Object determineCurrentLookupKey() {

            return HandlerDataSource.getDataSource();

        }

    }

    设置动态选择的Datasource,这里的Set方法可以留给AOP调用,或者留给我们的具体的Dao层或者Service层中手动调用,在执行SQL语句之前。

    package com.common.utils.manydatasource;

    /**

    * descrption: 根据当前线程来选择具体的数据源

    * authohr: wangji

    * date: 2017-08-21 10:36

    */

    public class HandlerDataSource {

        private static ThreadLocal<String> handlerThredLocal = new ThreadLocal<String>();

        /**

        * @desction: 提供给AOP去设置当前的线程的数据源的信息

        * @author: wangji

        * @date: 2017/8/21

        * @param: [datasource]

        * @return: void

        */

        public static void putDataSource(String datasource) {

            handlerThredLocal.set(datasource);

        }

        /**

        * @desction: 提供给AbstractRoutingDataSource的实现类,通过key选择数据源

        * @author: wangji

        * @date: 2017/8/21

        * @param: []

        * @return: java.lang.String

        */

        public static String getDataSource() {

            return handlerThredLocal.get();

        }

        /**

        * @desction: 使用默认的数据源

        */

        public static void clear() {

            handlerThredLocal.remove();

        }

    }

    设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上,dataSource是当前数据源的一个别名用于标识我们的数据源的信息。

    package com.common.utils.manydatasource;

    import java.lang.annotation.*;

    /**

    * @description: 创建拦截设置数据源的注解

    * Created by wangji on 2017/8/21.

    */

    @Target({ElementType.METHOD,ElementType.TYPE})

    @Retention(RetentionPolicy.RUNTIME)

    @Documented

    public @interface DynamicSwitchDataSource {

        String dataSource() default "";

    }

    AOP拦截类的实现,通过拦截上面的注解,在其执行之前处理设置当前执行SQL的数据源的信息,HandlerDataSource.putDataSource(….),这里的数据源信息从我们设置的注解上面获取信息,如果没有设置就是用默认的数据源的信息。

    package com.common.utils.manydatasource;

    import lombok.extern.slf4j.Slf4j;

    import org.aspectj.lang.JoinPoint;

    import org.aspectj.lang.annotation.After;

    import org.aspectj.lang.annotation.Aspect;

    import org.aspectj.lang.annotation.Before;

    import org.aspectj.lang.annotation.Pointcut;

    import org.aspectj.lang.reflect.MethodSignature;

    import org.springframework.core.annotation.Order;

    import org.springframework.stereotype.Component;

    import java.lang.reflect.Method;

    /**

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

    * authohr: wangji

    * date: 2017-08-21 10:42

    */

    @Aspect

    @Slf4j

    @Component

    @Order(1)

    public class HandlerDataSourceAop {

        //@within在类上设置

        //@annotation在方法上进行设置

        @Pointcut("@within(com.common.utils.manydatasource.DynamicSwitchDataSource)||@annotation(com.common.utils.manydatasource.DynamicSwitchDataSource)")

        public void pointcut() {}

        @Before("pointcut()")

        public void doBefore(JoinPoint joinPoint)

        {

            Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();

            DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class);//获取方法上的注解

            if(annotationClass == null){

                annotationClass = joinPoint.getTarget().getClass().getAnnotation(DynamicSwitchDataSource.class);//获取类上面的注解

                if(annotationClass == null) return;

            }

            //获取注解上的数据源的值的信息

            String dataSourceKey = annotationClass.dataSource();

            if(dataSourceKey !=null){

                //给当前的执行SQL的操作设置特殊的数据源的信息

                HandlerDataSource.putDataSource(dataSourceKey);

            }

            log.info("AOP动态切换数据源,className"+joinPoint.getTarget().getClass().getName()+"methodName"+method.getName()+";dataSourceKey:"+dataSourceKey==""?"默认数据源":dataSourceKey);

        }

        @After("pointcut()")

        public void after(JoinPoint point) {

            //清理掉当前设置的数据源,让默认的数据源不受影响

            HandlerDataSource.clear();

        }

    }

    配置数据源在Spring 核心容器中配置

    jdbc.driver=com.mysql.jdbc.Driver

    jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis

    jdbc.username=root

    jdbc.password=root

    jdbc2.url=jdbc:mysql://127.0.0.1:3306/datasource2

    <!-- 配置数据源 -->

        <bean id="dataSource0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">

            <property name="driverClassName" value="${jdbc.driver}"/>

            <property name="url" value="${jdbc.url}"/>

            <property name="username" value="${jdbc.username}"/>

            <property name="password" value="${jdbc.password}"/>

            <property name="maxActive" value="10"/>

        </bean>

        <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">

            <property name="driverClassName" value="${jdbc.driver}"/>

            <property name="url" value="${jdbc2.url}"/>

            <property name="username" value="${jdbc.username}"/>

            <property name="password" value="${jdbc.password}"/>

            <property name="maxActive" value="10"/>

        </bean>

    配置之前我们实现的数据源选择的中间层AbstractRoutingDataSource的实现类,这里的key就是数据源信息的别名,通过这个key可以选择到数据源的信息。MultipleDataSourceToChoose就是上面写的数据源选择器的实现类

    bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true">

            <description>数据源</description>

            <property name="targetDataSources">

                <map key-type="java.lang.String" value-type="javax.sql.DataSource">

                    <entry key="datasource0" value-ref="dataSource0" />

                    <entry key="datasource1" value-ref="dataSource1" />

                </map>

            </property>

            <!-- 设置默认的目标数据源 -->

            <property name="defaultTargetDataSource" ref="dataSource0" />

        </bean>

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

    简单的使用AOP进行测试一下,这里测试的结果时不同的,所以是生效的,使用了不同的数据源,但是底层的实现没有进行任何的修改处理。

    @Service

    @Slf4j

    public class UserInfoService implements IUserInfoService {

        @Resource

        private UserDao userDao;

        @Autowired

        private CommonHibernateDao commonDao;

        @TestValidateParam

        public User getUserInfoById(Integer id) {

            return userDao.findById(id);

        }

        @DynamicSwitchDataSource(dataSource = "datasource0")

        public void save(User user) {

            userDao.save(user);

        }

        @DynamicSwitchDataSource(dataSource = "datasource1")

        public List<User> findAll(){

            String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u";

            List<User> list =commonDao.findListBySQL(sql,User.class);

            return list;

        }

    }

    也可以不适用AOP,直接在编程中实现,通过测试,结果分别为两个数据库中的信息

    public void test(){

            HandlerDataSource.putDataSource("datasource1");

            String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u";

            List<User> list =commonDao.findListBySQL(sql,User.class);

            HandlerDataSource.putDataSource("datasource0");

            commonDao.deleteById("2",User.class);

        }

    AbstractRoutingDataSource 类说明:
    (1)它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。
    (2)项目启动时,先调用 setTargetDataSources() 方法,然后框架调用 afterPropertiesSet() 方法。
    后续需要实时刷新数据源时,需要手动调用以上两个方法!
    (3)我们手动提交给 setTargetDataSources 方法的是用来初始化 targetDataSources 的入参,一个 Map<Stirng, DataSource> 结构的对象。但是执行数据库操作时,是从 resolvedDataSources 对象中获取数据源的,afterPropertiesSet() 方法就是用来初始化后者的!

    DataSourceContextHolder-切换数据源

    public class DynamicDataSourceContextHolder {
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
        public static void setDataSourceKey(String key) {
            contextHolder.set(key);
        }
        public static String getDataSourceKey() {
            return contextHolder.get();
        }
        public static void clearDataSourceKey() {
            contextHolder.remove();
        }
    }
    

    RoutingDataSource - AbstractRoutingDataSource子类

    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.getDataSourceKey();
        }
    }
    

    RoutingDataSourceConfig - 配置类

    @Configuration
    public class DynamicDataSourceConfig {
        @Autowired
        private DataSource dataSourceDefault;
        @Autowired
        private JdbcTemplate jdbcTemplate;
        private DataSource createDataSource(Map<String, Object> map) {
            DruidDecryptDataSource dataSource = new DruidDecryptDataSource();
            dataSource.setDriverClassName(map.get("DRIVER_CLASS_NAME").toString());
            dataSource.setUrl(map.get("URL").toString());
            dataSource.setUsername(map.get("USERNAME").toString());
            dataSource.setPassword(map.get("PASSWORD").toString());
            dataSource.setMaxWait(10000);//10s
            return dataSource;
        }
        public Map<Object, Object> dataSourceMap() {
            List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from dataSource");
            Map<Object, Object> returnMap = new HashMap<Object, Object>();
            for(Map<String, Object> map : list) {
                returnMap.put(map.get("DATASOURCE_NAME"), createDataSource(map));
            }
            return returnMap;
        }
        @Bean("dataSource")
        public DynamicDataSource dataSource() {
            DynamicDataSource ddataSource = new DynamicDataSource();
            ddataSource.setDefaultTargetDataSource(dataSourceDefault);// 设置默认数据源
            ddataSource.setTargetDataSources(dataSourceMap());
            return ddataSource;
        }
        @Bean("jdbcTemplate")
        public NamedParameterJdbcTemplate jdbcTemplate(@Qualifier("dataSource") DynamicDataSource dataSource) {
            return new NamedParameterJdbcTemplate(new JdbcTemplate(dataSource));
        }
    }
    
  • 相关阅读:
    HTML语义化
    OKAY take it away `electron-builder`一直提示这个
    gitbash选中不了自己想要的选择
    vue挂载
    vue关闭eslint
    第二天-5大浏览器内核和浏览器的组成
    第一天-JavaScript简介与历史
    bootstrap模态框遇到做复制的功能失效
    对象的key【键】和分别获取数组的key【键】和值
    AngularJS教程
  • 原文地址:https://www.cnblogs.com/zzsuje/p/15465431.html
Copyright © 2011-2022 走看看