• Mybatis多数据源(二)使用AbstractRoutingDataSource实现动态数据源切换


    一、原理

    Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行数据库操作之前,设置使用的数据源,

    即可实现数据源的动态路由。它的抽象方法determineCurrentLookupKey() 决定使用哪个数据源。image

    image

    二、具体实现

    具体的业务代码不贴了,就放一下构造动态数据源的部分

    1、DynamicDataSource继承AbstractRoutingDataSource,重写determineCurrentLookupKey方法

    /**动态数据源
     * 扩展 Spring 的 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法
     * determineCurrentLookupKey() 方法决定使用哪个数据源
     *
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        /**
         * ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
         * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
         */
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
        /**
         * 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
         *
         * @param defaultTargetDataSource 默认数据源
         * @param targetDataSources       目标数据源
         */
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
    
        @Override
        protected Object determineCurrentLookupKey() {
            return getDataSource();
        }
    
        public static void setDataSource(String dataSource) {
            CONTEXT_HOLDER.set(dataSource);
        }
    
        public static String getDataSource() {
            return CONTEXT_HOLDER.get();
        }
    
        public static void clearDataSource() {
            CONTEXT_HOLDER.remove();
        }
    
    }

    2、将动态数据源注入到Spring容器中

    /**
     * 配置多数据源
     */
    @Configuration
    public class DynamicDataSourceConfig {
    
        private static final String FIRST = "first";
    
        private static final String SECOND = "second";
    
        @Bean
        @ConfigurationProperties("spring.datasource.druid.first")
        public DataSource firstDataSource(){
    
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties("spring.datasource.druid.second")
        public DataSource secondDataSource(){
    
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean
        @Primary
        public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
            Map<Object, Object> targetDataSources = new HashMap<>(5);
            targetDataSources.put(FIRST, firstDataSource);
            targetDataSources.put(SECOND, secondDataSource);
            return new DynamicDataSource(firstDataSource, targetDataSources);
        }
    
    }

    3、注解 + aop,指定本次数据库操作使用的数据源

    /**
     * 多数据源注解
     * 指定要使用的数据源
     *
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CurDataSource {
    
        String name() default "";
    
    }
    
    
    /**
     * 多数据源,切面处理类
     *
     * @author xiaohe
     * @version V1.0.0
     */
    @Slf4j
    @Aspect
    @Component
    public class DataSourceAspect implements Ordered {
    
        private static final String FIRST = "first";
    
        private static final String SECOND = "second";
    
        @Pointcut("@annotation(com.hc.datasource.CurDataSource)")
        public void dataSourcePointCut() {
    
        }
    
        @Around("dataSourcePointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
    
            CurDataSource ds = method.getAnnotation(CurDataSource.class);
            if (ds == null) {
                DynamicDataSource.setDataSource(FIRST);
                log.debug("set datasource is " + FIRST);
            } else {
                DynamicDataSource.setDataSource(ds.name());
                log.debug("set datasource is " + ds.name());
            }
    
            try {
                return point.proceed();
            } finally {
                DynamicDataSource.clearDataSource();
                log.debug("clean datasource");
            }
        }
    
        @Override
        public int getOrder() {
            return 1;
        }
    }

    4、yml文件

    spring:
      application:
        name: test
      datasource:
        druid:
          type: com.alibaba.druid.pool.DruidDataSource
          driverClassName: com.mysql.jdbc.Driver
          first:
            url: jdbc:mysql://xxxx
            username: root
            password: root
          second:
            url: jdbc:mysql://xxx
            username: root
            password: root
    mybatis:
      mapper-locations: classpath:mapper/*.xml

    三、测试

    service在调用mapper方法时,需要通过注解指定本次操作使用的数据源

    @Service
    public class TestService {
    
        @Autowired
        TestMapper testMapper;
    
        @CurDataSource(name = "first")
        public List<CategoryEntity> list() {
            List<CategoryEntity> list = testMapper.list();
            return list;
        }
    
        @CurDataSource(name = "second")
        public List<OrderEntity> orders() {
            List<OrderEntity> list = testMapper.orders();
            return list;
        }
    }

    访问 http://localhost:8080/categorys

    image

    查看日志

    image

    表明本次请求确实发生了数据源的切换

    四、缺点

    数据源的实例化做的不太好,本次就是有几个数据源,就手动实例化几个数据源。如果数据源很多的话,一个个构造的话很麻烦

    下篇文章批量构造多个数据源

    地址:https://gitee.com/houchen1996/abstract-routing-data-source-test

  • 相关阅读:
    MyEclipse里运行时报错
    Django中Template does not exit
    Django简单界面开发
    Django安装过程
    搭建NFS服务器和客户端过程中遇到的问题
    URL传值中文乱码的解决
    结合《需求征集系统》谈MVC框架
    对于微信小程序登录的理解图
    FpSpread基本句法
    sql,lambda,linq语句
  • 原文地址:https://www.cnblogs.com/houchen/p/16244112.html
Copyright © 2020-2023  润新知