• springboot ssm propertis 如何搭建多数据源动态切换


    1.propertis

    #启动端口号
    server.port=8085
    # 打印 Mybatis sql 语句,两个配置都可以
    #logging.level.com.example.springboot.mapper = debug
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    #数据库1
    spring.datasource.master.url=jdbc:mysql://OOO:3306/库名?characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&allowMultiQueries=true
    spring.datasource.master.username =root
    spring.datasource.master.password =密码
    spring.datasource.master.type: com.alibaba.druid.pool.DruidDataSource
    spring.datasource.master.driver-class-name: com.mysql.cj.jdbc.Driver
    
    # 数据库2
    spring.datasource.hlp[0].key: slave1
    spring.datasource.hlp[0].url=jdbc:mysql://XXX:3306/test?characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&allowMultiQueries=true
    spring.datasource.hlp[0].username =root
    spring.datasource.hlp[0].password =密码
    spring.datasource.hlp[0].type: com.alibaba.druid.pool.DruidDataSource
    spring.datasource.hlp[0].driver-class-name: com.mysql.cj.jdbc.Driver

    2.DynamicDataSourceRegister  数据源注册

    package com;
    
    
    import com.config.DynamicDataSourceContextHolder;
    import com.config.DynamicRoutingDataSource;
    import com.zaxxer.hikari.HikariDataSource;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.boot.context.properties.bind.Bindable;
    import org.springframework.boot.context.properties.bind.Binder;
    import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
    import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
    import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
    import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.util.StringUtils;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 动态数据源注册
     * 实现 ImportBeanDefinitionRegistrar 实现数据源注册
     * 实现 EnvironmentAware 用于读取application.yml配置
     */
    @Slf4j
    public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    
    
    
    
        /**
         * 配置上下文(也可以理解为配置文件的获取工具)
         */
        private Environment evn;
    
        /**
         * 别名
         */
        private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
    
        /**
         * 由于部分数据源配置不同,所以在此处添加别名,避免切换数据源出现某些参数无法注入的情况
         */
        static {
            aliases.addAliases("url", new String[]{"jdbc-url"});
            aliases.addAliases("username", new String[]{"hlp"});
        }
    
        /**
         * 存储我们注册的数据源
         */
        private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
    
        /**
         * 参数绑定工具 springboot2.0新推出
         */
        private Binder binder;
    
        /**
         * ImportBeanDefinitionRegistrar接口的实现方法,通过该方法可以按照自己的方式注册bean
         *
         * @param annotationMetadata
         * @param beanDefinitionRegistry
         */
        @Override
        public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
            // 获取所有数据源配置
            Map config, defauleDataSourceProperties;
            defauleDataSourceProperties = binder.bind("spring.datasource.master", Map.class).get();
            // 获取数据源类型
            String typeStr = evn.getProperty("spring.datasource.master.type");
            // 获取数据源类型
            Class<? extends DataSource> clazz = getDataSourceType(typeStr);
            // 绑定默认数据源参数 也就是主数据源
            DataSource consumerDatasource, defaultDatasource = bind(clazz, defauleDataSourceProperties);
            DynamicDataSourceContextHolder.dataSourceIds.add("master");
            log.info("注册默认数据源成功");
            // 获取其他数据源配置
            List<Map> configs = binder.bind("spring.datasource.hlp", Bindable.listOf(Map.class)).get();
            // 遍历从数据源
            for (int i = 0; i < configs.size(); i++) {
                config = configs.get(i);
                clazz = getDataSourceType((String) config.get("type"));
                defauleDataSourceProperties = config;
                // 绑定参数
                consumerDatasource = bind(clazz, defauleDataSourceProperties);
                // 获取数据源的key,以便通过该key可以定位到数据源
                String key = config.get("key").toString();
                customDataSources.put(key, consumerDatasource);
                // 数据源上下文,用于管理数据源与记录已经注册的数据源key
                DynamicDataSourceContextHolder.dataSourceIds.add(key);
                log.info("注册数据源{}成功", key);
            }
            // bean定义类
            GenericBeanDefinition define = new GenericBeanDefinition();
            // 设置bean的类型,此处DynamicRoutingDataSource是继承AbstractRoutingDataSource的实现类
            define.setBeanClass(DynamicRoutingDataSource.class);
            // 需要注入的参数
            MutablePropertyValues mpv = define.getPropertyValues();
            // 添加默认数据源,避免key不存在的情况没有数据源可用
            mpv.add("defaultTargetDataSource", defaultDatasource);
            // 添加其他数据源
            mpv.add("targetDataSources", customDataSources);
            // 将该bean注册为datasource,不使用springboot自动生成的datasource
            beanDefinitionRegistry.registerBeanDefinition("datasource", define);
            log.info("注册数据源成功,一共注册{}个数据源", customDataSources.keySet().size() + 1);
        }
    
        /**
         * 通过字符串获取数据源class对象
         *
         * @param typeStr
         * @return
         */
        private Class<? extends DataSource> getDataSourceType(String typeStr) {
            Class<? extends DataSource> type;
            try {
                if (StringUtils.hasLength(typeStr)) {
                    // 字符串不为空则通过反射获取class对象
                    type = (Class<? extends DataSource>) Class.forName(typeStr);
                } else {
                    // 默认为hikariCP数据源,与springboot默认数据源保持一致
                    type = HikariDataSource.class;
                }
                return type;
            } catch (Exception e) {
                //无法通过反射获取class对象的情况则抛出异常,该情况一般是写错了,所以此次抛出一个runtimeexception
                throw new IllegalArgumentException("can not resolve class with type: " + typeStr);
            }
        }
    
        /**
         * 绑定参数,以下三个方法都是参考DataSourceBuilder的bind方法实现的,目的是尽量保证我们自己添加的数据源构造过程与springboot保持一致
         *
         * @param result
         * @param properties
         */
        private void bind(DataSource result, Map properties) {
            ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
            Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
            // 将参数绑定到对象
            binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
        }
    
        private <T extends DataSource> T bind(Class<T> clazz, Map properties) {
            ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
            Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
            // 通过类型绑定参数并获得实例对象
            return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get();
        }
    
        /**
         * @param clazz
         * @param sourcePath 参数路径,对应配置文件中的值,如: spring.datasource
         * @param <T>
         * @return
         */
        private <T extends DataSource> T bind(Class<T> clazz, String sourcePath) {
            Map properties = binder.bind(sourcePath, Map.class).get();
            return bind(clazz, properties);
        }
    
        /**
         * EnvironmentAware接口的实现方法,通过aware的方式注入,此处是environment对象
         *
         * @param environment
         */
        @Override
        public void setEnvironment(Environment environment) {
            log.info("开始注册数据源");
            this.evn = environment;
            // 绑定配置器
            binder = Binder.get(evn);
        }
    
    }
    

     3.DynamicDataSourceContextHolder 数据源上下文

     1 package com.config;
     2 
     3 import org.slf4j.Logger;
     4 import org.slf4j.LoggerFactory;
     5 
     6 import java.util.ArrayList;
     7 import java.util.List;
     8 
     9 /**
    10  * @Auther: yukong
    11  * @Date: 2018/8/15 10:49
    12  * @Description: 数据源上下文
    13  */
    14 public class DynamicDataSourceContextHolder {
    15 
    16 
    17 
    18     private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    19 
    20     /**
    21      * 存储已经注册的数据源的key
    22      */
    23     public static List<String> dataSourceIds = new ArrayList<>();
    24 
    25     /**
    26      * 线程级别的私有变量
    27      */
    28     private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();
    29 
    30     public static String getDataSourceRouterKey () {
    31         return HOLDER.get();
    32     }
    33 
    34     public static void setDataSourceRouterKey (String dataSourceRouterKey) {
    35         logger.info("切换至{}数据源", dataSourceRouterKey);
    36         HOLDER.set(dataSourceRouterKey);
    37     }
    38 
    39     /**
    40      * 设置数据源之前一定要先移除
    41      */
    42     public static void removeDataSourceRouterKey () {
    43         HOLDER.remove();
    44     }
    45 
    46     /**
    47      * 判断指定DataSrouce当前是否存在
    48      *
    49      * @param dataSourceId
    50      * @return
    51      */
    52     public static boolean containsDataSource(String dataSourceId){
    53         return dataSourceIds.contains(dataSourceId);
    54     }
    55 
    56 }

    4.DynamicRoutingDataSource  动态数据源路由配置

     1 package com.config;
     2 
     3 import lombok.extern.slf4j.Slf4j;
     4 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
     5 
     6 /**
     7  * @Auther: yukong
     8  * @Date: 2018/8/15 10:47
     9  * @Description: 动态数据源路由配置
    10  */
    11 @Slf4j
    12 public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    13 
    14 
    15 
    16     //private static Logger logger = LoggerFactory.getLogger(DynamicRoutingDataSource.class);
    17 
    18     @Override
    19     protected Object determineCurrentLookupKey() {
    20         String dataSourceName = DynamicDataSourceContextHolder.getDataSourceRouterKey();
    21         log.info("当前数据源是:{}", dataSourceName);
    22         return DynamicDataSourceContextHolder.getDataSourceRouterKey();
    23     }
    24 }

    5. DataSource

     1 package com.hlp.test.annotation;
     2 
     3 import java.lang.annotation.*;
     4 
     5 /**
     6  * 切换数据注解 可以用于类或者方法级别 方法级别优先级 > 类级别
     7  */
     8 @Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
     9 @Retention(RetentionPolicy.RUNTIME)
    10 @Documented
    11 public @interface DataSource {
    12     String value() default "master"; //该值即key值    默认的数据库
    13 
    14 }

    6.DynamicDataSourceAspect

     1 package com.aop;
     2 
     3 
     4 import com.config.DynamicDataSourceContextHolder;
     5 import com.hlp.test.annotation.DataSource;
     6 import org.aspectj.lang.JoinPoint;
     7 import org.aspectj.lang.annotation.After;
     8 import org.aspectj.lang.annotation.Before;
     9 import org.slf4j.Logger;
    10 import org.slf4j.LoggerFactory;
    11 
    12 //@Aspect
    13 //@Component
    14 //@Order(-1900)
    15 public class DynamicDataSourceAspect {
    16     private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    17 
    18     @Before("@annotation(ds)")
    19     public void changeDataSource(JoinPoint point, DataSource ds) throws Throwable {
    20         String dsId = ds.value();
    21         if (DynamicDataSourceContextHolder.dataSourceIds.contains(dsId)) {
    22             logger.debug("Use DataSource :{} >", dsId, point.getSignature());
    23             DynamicDataSourceContextHolder.setDataSourceRouterKey(dsId);
    24         } else {
    25             logger.info("数据源[{}]不存在,使用默认数据源 >{}", dsId, point.getSignature());
    26         }
    27     }
    28 
    29     @After("@annotation(ds)")
    30     public void restoreDataSource(JoinPoint point, DataSource ds) {
    31         logger.debug("Revert DataSource : " + ds.value() + " > " + point.getSignature());
    32         DynamicDataSourceContextHolder.removeDataSourceRouterKey();
    33 
    34     }
    35 }

    7.DynamicDataSourceAnnotationInterceptor

     1 package com.aop;
     2 
     3 
     4 import com.config.DynamicDataSourceContextHolder;
     5 import com.hlp.test.annotation.DataSource;
     6 import org.aopalliance.intercept.MethodInterceptor;
     7 import org.aopalliance.intercept.MethodInvocation;
     8 import org.slf4j.Logger;
     9 import org.slf4j.LoggerFactory;
    10 import org.springframework.core.annotation.AnnotationUtils;
    11 
    12 import java.lang.reflect.Method;
    13 import java.util.HashMap;
    14 import java.util.Map;
    15 
    16 /**
    17  * @Auther: yukong
    18  * @Date: 2018/8/17 09:15
    19  * @Description:
    20  */
    21 public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
    22 
    23     private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAnnotationInterceptor.class);
    24 
    25 
    26     /**
    27      * 缓存方法注解值
    28      */
    29     private static final Map<Method, String> METHOD_CACHE = new HashMap<>();
    30 
    31     @Override
    32     public Object invoke(MethodInvocation invocation) throws Throwable {
    33         try {
    34             String datasource = determineDatasource(invocation);
    35             if (! DynamicDataSourceContextHolder.containsDataSource(datasource)) {
    36                 logger.info("数据源[{}]不存在,使用默认数据源 >", datasource);
    37             }
    38             DynamicDataSourceContextHolder.setDataSourceRouterKey(datasource);
    39             return invocation.proceed();
    40         } finally {
    41             DynamicDataSourceContextHolder.removeDataSourceRouterKey();
    42         }
    43     }
    44 
    45     private String determineDatasource(MethodInvocation invocation) {
    46         Method method = invocation.getMethod();
    47         if (METHOD_CACHE.containsKey(method)) {
    48             return METHOD_CACHE.get(method);
    49         } else {   //看选择那个数据源
    50             DataSource ds = method.isAnnotationPresent(DataSource.class) ? method.getAnnotation(DataSource.class)
    51                     : AnnotationUtils.findAnnotation(method.getDeclaringClass(), DataSource.class);
    52             METHOD_CACHE.put(method, ds.value());
    53             return ds.value();
    54         }
    55     }
    56 
    57 }

    8.DynamicDataSourceAnnotationAdvisor

     1 package com.aop;
     2 
     3 
     4 import com.hlp.test.annotation.DataSource;
     5 import org.aopalliance.aop.Advice;
     6 import org.springframework.aop.Pointcut;
     7 import org.springframework.aop.support.AbstractPointcutAdvisor;
     8 import org.springframework.aop.support.ComposablePointcut;
     9 import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
    10 import org.springframework.beans.BeansException;
    11 import org.springframework.beans.factory.BeanFactory;
    12 import org.springframework.beans.factory.BeanFactoryAware;
    13 
    14 /**
    15  * @Auther: yukong
    16  * @Date: 2018/8/17 09:14
    17  * @Description: aop织入
    18  */
    19 public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
    20 
    21     private Advice advice;
    22 
    23     private Pointcut pointcut;
    24 
    25     public DynamicDataSourceAnnotationAdvisor(DynamicDataSourceAnnotationInterceptor dynamicDataSourceAnnotationInterceptor) {
    26         this.advice = dynamicDataSourceAnnotationInterceptor;
    27         this.pointcut = buildPointcut();
    28     }
    29 
    30     @Override
    31     public Pointcut getPointcut() {
    32         return this.pointcut;
    33     }
    34 
    35     @Override
    36     public Advice getAdvice() {
    37         return this.advice;
    38     }
    39 
    40     @Override
    41     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    42         if (this.advice instanceof BeanFactoryAware) {
    43             ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
    44         }
    45     }
    46 
    47     private Pointcut buildPointcut() {
    48         Pointcut cpc = (Pointcut) new AnnotationMatchingPointcut(DataSource.class, true);
    49         // 类注解
    50         Pointcut clpc = (Pointcut) AnnotationMatchingPointcut.forClassAnnotation(DataSource.class);
    51         // 方法注解
    52         Pointcut mpc = (Pointcut) AnnotationMatchingPointcut.forMethodAnnotation(DataSource.class);
    53         return new ComposablePointcut(cpc).union(clpc).union(mpc);
    54     }
    55 
    56 }

    9.  启动类

     1 package com.zdkj.umt;
     2 
     3 import com.DynamicDataSourceRegister;
     4 import com.aop.DynamicDataSourceAnnotationAdvisor;
     5 import com.aop.DynamicDataSourceAnnotationInterceptor;
     6 import lombok.extern.slf4j.Slf4j;
     7 import org.mybatis.spring.annotation.MapperScan;
     8 import org.springframework.boot.SpringApplication;
     9 import org.springframework.boot.autoconfigure.SpringBootApplication;
    10 import org.springframework.boot.web.servlet.ServletComponentScan;
    11 import org.springframework.context.ConfigurableApplicationContext;
    12 import org.springframework.context.annotation.Bean;
    13 import org.springframework.context.annotation.Import;
    14 import org.springframework.scheduling.annotation.EnableScheduling;
    15 import org.springframework.transaction.annotation.EnableTransactionManagement;
    16 
    17 /** 启动类
    18  * @author liuyanjun
    19  */
    20 @Slf4j
    21 @Import(DynamicDataSourceRegister.class)
    22 @EnableTransactionManagement
    23 @ServletComponentScan
    24 @SpringBootApplication
    25 @MapperScan("com.zdkj.umt.mapper")
    26 @EnableScheduling
    27 public class SpringbootApplication {
    28 
    29 //最主要的是这里  要注入  不然无法切换数据源
    30     @Bean
    31     public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor() {
    32         return new DynamicDataSourceAnnotationAdvisor(new DynamicDataSourceAnnotationInterceptor());
    33     }
    34 
    35     public static void main(String[] args) {
    36         ConfigurableApplicationContext ctx = SpringApplication.run(SpringbootApplication.class, args);
    37         String[] activeProfiles = ctx.getEnvironment().getActiveProfiles();
    38         for (String profile : activeProfiles) {
    39             log.info("▁▂▃▄▅▆▇█    Spring Boot 使用profile:" + profile + "    █▇▆▅▄▃▂▁");
    40         }
    41     }
    42 
    43 }
    44 
    

    10. 测试时使用  UserMapper

    package com.zdkj.umt.mapper;
    
    import com.hlp.test.annotation.DataSource;
    import com.zdkj.umt.domain.po.hlp;
    
    import java.util.List;
    
    /**
     * describe
     *
     * @author HuangLinPan
     * @date 2020/05/18
     */
    //如果方法上没有配置数据库那么就看mapper上的
    //如果也没有那么就使用 DataSource 默认的数据库
    @DataSource("slave1") public interface UserMapper {   

    //优先看方法上的库名
    @DataSource(
    "slave1") List<hlp> getAllUser(); }

    此为使用别人源码配置 (笔记)

  • 相关阅读:
    SpringCloud教程第10篇:高可用的服务注册中心(F版本)
    SpringCloud教程第9篇:Sleuth(F版本)
    requests.session保持会话
    Jmeter Constant Throughput Timer 使用
    Jmeter提取响应数据的结果保存到本地的一个文件
    练习2
    练习1
    一道简单的练习题
    Maven下org.junit.Test无法使用
    [转]解决pycharm无法导入本地包的问题(Unresolved reference 'tutorial')
  • 原文地址:https://www.cnblogs.com/huanglp/p/12912758.html
Copyright © 2020-2023  润新知