• SpringBoot(十一)-- 动态数据源


    SpringBoot中使用动态数据源可以实现分布式中的分库技术,比如查询用户 就在用户库中查询,查询订单 就在订单库中查询。

    一、配置文件application.properties

    # 默认数据源
    spring.datasource.url=jdbc:mysql://localhost:3306/consult
    spring.datasource.username=myConsult
    spring.datasource.password=123456
    spring.datasource.driver-class-name=org.gjt.mm.mysql.Driver
    # 更多数据源
    custom.datasource.names=ds1,ds2 
    custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
    custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
    custom.datasource.ds1.username=root 
    custom.datasource.ds1.password=123456 
    custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
    custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2
    custom.datasource.ds2.username=root 
    custom.datasource.ds2.password=123456

    二、pox.xml

     <!-- aspectj -->
     <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjrt</artifactId>
     </dependency>
     <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjtools</artifactId>
     </dependency>
     <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
     </dependency>

    三、使用aop自定义注解,实现动态切换数据源

    1.动态数据源注册器

      1 package com.xsjt.dynamicDataSource;  
      2 import java.util.HashMap;
      3 import java.util.Map;
      4 import javax.sql.DataSource;
      5 import org.slf4j.Logger;
      6 import org.slf4j.LoggerFactory;
      7 import org.springframework.beans.MutablePropertyValues;
      8 import org.springframework.beans.PropertyValues;
      9 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
     10 import org.springframework.beans.factory.support.GenericBeanDefinition;
     11 import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
     12 import org.springframework.boot.bind.RelaxedDataBinder;
     13 import org.springframework.boot.bind.RelaxedPropertyResolver;
     14 import org.springframework.context.EnvironmentAware;
     15 import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
     16 import org.springframework.core.convert.ConversionService;
     17 import org.springframework.core.convert.support.DefaultConversionService;
     18 import org.springframework.core.env.Environment;
     19 import org.springframework.core.type.AnnotationMetadata;
     20 /**  
     21  * ClassName:DynamicDataSourceRegister 
     22  * Date:     2017年11月13日 下午7:40:42
     23  * @author   Joe  
     24  * @version    
     25  * @since    JDK 1.8
     26  */
     27 public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
     28     private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
     29     
     30     private ConversionService conversionService = new DefaultConversionService();
     31     private PropertyValues dataSourcePropertyValues;
     32     
     33     // 如配置文件中未指定数据源类型,使用该默认值
     34     private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
     35     
     36 //     private static final Object DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
     37     
     38     // 数据源
     39     private DataSource defaultDataSource;
     40     
     41     private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
     42     
     43     public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     44         Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
     45         // 将主数据源添加到更多数据源中
     46         targetDataSources.put("dataSource", defaultDataSource);
     47         DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
     48         // 添加更多数据源
     49         targetDataSources.putAll(customDataSources);
     50         for (String key : customDataSources.keySet()) {
     51             DynamicDataSourceContextHolder.dataSourceIds.add(key);
     52         }
     53         
     54         // 创建DynamicDataSource
     55         GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
     56         beanDefinition.setBeanClass(DynamicDataSource.class);
     57         beanDefinition.setSynthetic(true);
     58         MutablePropertyValues mpv = beanDefinition.getPropertyValues();
     59         mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
     60         mpv.addPropertyValue("targetDataSources", targetDataSources);
     61         registry.registerBeanDefinition("dataSource", beanDefinition); // 注册到Spring容器中
     62         
     63         logger.info("Dynamic DataSource Registry");
     64     }
     65     
     66     /**
     67      * 创建DataSource
     68      * @param type
     69      * @param driverClassName
     70      * @param url
     71      * @param username
     72      * @param password
     73      * @return
     74      */
     75     @SuppressWarnings("unchecked")
     76     public DataSource buildDataSource(Map<String, Object> dsMap) {
     77         try {
     78             Object type = dsMap.get("type");
     79             if (type == null)
     80                 type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
     81                 
     82             Class<? extends DataSource> dataSourceType;
     83             dataSourceType = (Class<? extends DataSource>)Class.forName((String)type);
     84             
     85             String driverClassName = dsMap.get("driver-class-name").toString();
     86             String url = dsMap.get("url").toString();
     87             String username = dsMap.get("username").toString();
     88             String password = dsMap.get("password").toString();
     89             
     90             DataSourceBuilder factory = DataSourceBuilder.create()
     91                     .driverClassName(driverClassName)
     92                     .url(url)
     93                     .username(username)
     94                     .password(password)
     95                     .type(dataSourceType);
     96             return factory.build();
     97         }
     98         catch (ClassNotFoundException e) {
     99             e.printStackTrace();
    100         }
    101         return null;
    102     }
    103     
    104     /**
    105      * 加载多数据源配置
    106      */
    107     public void setEnvironment(Environment env) {
    108         initDefaultDataSource(env);
    109         initCustomDataSources(env);
    110     }
    111     
    112     /**
    113      * 初始化主数据源
    114      */
    115     private void initDefaultDataSource(Environment env) {
    116         // 读取主数据源
    117         RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
    118         Map<String, Object> dsMap = new HashMap<String, Object>();
    119         dsMap.put("type", propertyResolver.getProperty("type"));
    120         dsMap.put("driver-class-name",propertyResolver.getProperty("driver-class-name"));
    121         dsMap.put("url", propertyResolver.getProperty("url"));
    122         dsMap.put("username", propertyResolver.getProperty("username"));
    123         dsMap.put("password", propertyResolver.getProperty("password"));
    124         defaultDataSource = buildDataSource(dsMap); 
    125         dataBinder(defaultDataSource, env);
    126     }
    127     
    128     /**
    129      * 为DataSource绑定更多数据
    130      * @param dataSource
    131      * @param env
    132      */
    133     private void dataBinder(DataSource dataSource, Environment env) {
    134         RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
    135         //dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
    136         dataBinder.setConversionService(conversionService);
    137         dataBinder.setIgnoreNestedProperties(false);//false
    138         dataBinder.setIgnoreInvalidFields(false);//false
    139         dataBinder.setIgnoreUnknownFields(true);//true
    140         if (dataSourcePropertyValues == null) {
    141             Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
    142             Map<String, Object> values = new HashMap<String, Object>(rpr);
    143             // 排除已经设置的属性
    144             values.remove("type");
    145             values.remove("driver-class-name");
    146             values.remove("url");
    147             values.remove("username");
    148             values.remove("password");
    149             dataSourcePropertyValues = new MutablePropertyValues(values);
    150         }
    151         dataBinder.bind(dataSourcePropertyValues);
    152     }
    153     
    154     /**
    155      * 初始化更多数据源
    156      *
    157      */
    158     private void initCustomDataSources(Environment env) {
    159         // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
    160         RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver( env, "custom.datasource.");
    161         String dsPrefixs = propertyResolver.getProperty("names");
    162         for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
    163             Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
    164             DataSource ds = buildDataSource(dsMap);
    165             customDataSources.put(dsPrefix, ds);
    166             dataBinder(ds, env);
    167         }
    168     }
    169 }
    View Code

    2.动态数据源适配器

    package com.xsjt.dynamicDataSource;  
    import java.util.ArrayList;
    import java.util.List;
    /**  
     * ClassName:DynamicDataSourceContextHolder 
     * Date:     2017年11月13日 下午7:41:49
     * @author   Joe  
     * @version    
     * @since    JDK 1.8
     */
    public class DynamicDataSourceContextHolder {
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
        public static List<String> dataSourceIds = new ArrayList<String>();
        
        public static void setDataSourceType(String dataSourceType) {
            contextHolder.set(dataSourceType);
        }
        
        public static String getDataSourceType() {
            return contextHolder.get();
        }
        
        public static void clearDataSourceType() {
            contextHolder.remove();
        }
        
        /**
         * 判断指定DataSrouce当前是否存在
         *
         * @param dataSourceId
         * @return
         */
        public static boolean containsDataSource(String dataSourceId){
            return dataSourceIds.contains(dataSourceId);
        }
    }
    View Code

    3.自定义注解

    /**  
     * ClassName:TargetDataSource 
     * Date:     2017年11月13日 下午7:42:15
     * @author   Joe  
     * @version    
     * @since    JDK 1.8
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
        String name();
    }
    View Code

    4.动态数据源切面

    package com.xsjt.dynamicDataSource;  
    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.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    /**  
     * ClassName:DynamicDataSourceAspect 
     * Date:     2017年11月13日 下午7:44:09
     * @author   Joe  
     * @version    
     * @since    JDK 1.8
     */
    @Aspect
    //保证该AOP在@Transactional之前执行
    @Order(-1)
    @Component
    public class DynamicDataSourceAspect {
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
        
        /** 
         * @Description 在方法执行之前执行  @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的
         * @param @param point
         * @param @param ds
         * @param @throws Throwable 参数 
         * @return void 返回类型  
         * @throws 
         */
        @Before("@annotation(ds)")
        public void changeDataSource(JoinPoint point, TargetDataSource ds)
                throws Throwable {
            String dsId = ds.name();
            if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
                logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
            }
            else {
                logger.debug("Use DataSource : {} > {}", ds.name(),point.getSignature());
                DynamicDataSourceContextHolder.setDataSourceType(ds.name());
            }
        }
        
        /** 
         * @Description 在方法执行之后执行  @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的 
         * @param @param point
         * @param @param ds 参数 
         * @return void 返回类型  
         * @throws 
         */
        @After("@annotation(ds)")
        public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
            logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
    View Code

    5.继承Spring AbstractRoutingDataSource实现路由切换

    package com.xsjt.dynamicDataSource;  
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    /**  
     * ClassName:DynamicDataSource 
     * 继承Spring AbstractRoutingDataSource实现路由切换
     * Date:     2017年11月13日 下午7:49:49
     * @author   Joe  
     * @version    
     * @since    JDK 1.8
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    }
    View Code

    四、怎么使用自定义的注解动态的切换数据源

    只需要在service实现类中 对需要切换数据源的方法上 加上 自定义的注解即可,如:@TargetDataSource(name = "ds2")

    五、启动类上添加@Import注解

      //注册动态多数据源
      @Import({DynamicDataSourceRegister.class})

    六.源码下载

      https://gitee.com/xbq168/spring-boot-learn

  • 相关阅读:
    安卓开发之ListAdapter(二)
    两listview联动
    不要天真了,这些简历HR一看就知道你是培训的,质量不佳的那种
    天真!这简历一看就是包装过的
    一线城市为何难逃离,职场饭局正在“失宠”?
    腾讯架构师分享的Java程序员需要突破的技术要点
    百度最穷程序员现身,工作4年晒出存款后,网友:你是真穷!
    震惊微软!招程序员的流程居然...
    python基础认识(一)
    input修改placeholder文字颜色
  • 原文地址:https://www.cnblogs.com/xbq8080/p/7822620.html
Copyright © 2020-2023  润新知