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));
        }
    }
    
  • 相关阅读:
    Java实现各种内部排序算法
    Java实现堆排序(大根堆)
    Java对象的序列化和反序列化
    Java实现链式存储的二叉查找树(递归方法)
    337. House Robber III(包含I和II)
    318. Maximum Product of Word Lengths
    114. Flatten Binary Tree to Linked List
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    96. Unique Binary Search Trees(I 和 II)
  • 原文地址:https://www.cnblogs.com/zzsuje/p/15465431.html
Copyright © 2011-2022 走看看