• springBoot多数据源


    针对微服务架构中常用的设计模块,通常我们都会需要使用到druid作为我们的数据连接池,当架构发生扩展的时候 ,通常面对的数据存储服务器也会渐渐增加,从原本的单库架构逐渐扩展为复杂的多库架构。

    当在业务层需要涉及到查询多种同数据库的场景下,我们通常需要在执行sql的时候动态指定对应的datasource。

    而Spring的AbstractRoutingDataSource则正好为我们提供了这一功能点,下边我将通过一个简单的基于springboot+aop的案例来实现如何通过自定义注解切换不同的数据源进行读数据操作,同时也将结合部分源码的内容进行讲解。

    首先我们需要自定义一个专门用于申明当前java应用程序所需要使用到哪些数据源信息:

      <!--阿里数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
    
            <!--oracle驱动 -->
            <dependency>
                <groupId>com.oracle</groupId>
                <artifactId>ojdbc6</artifactId>
                <version>11.2.0.3</version>
            </dependency>
    # Spring配置
    spring:
      profiles:
        active: @package.environment@
      # 数据源
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: oracle.jdbc.OracleDriver
        druid:
          #主库数据源
          master:
            url: 
            username: 
            password: 
          # 从库数据源
          slave:
          # 从数据源开关/默认关闭
            enabled: true
            url: 
            username: 
            password: 123
          # 初始连接数
          initialSize: 5
          # 最小连接池数量
          minIdle: 10
          # 最大连接池数量
          maxActive: 20
          # 配置获取连接等待超时的时间
          maxWait: 60000
          # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
          timeBetweenEvictionRunsMillis: 60000
          # 配置一个连接在池中最小生存的时间,单位是毫秒
          minEvictableIdleTimeMillis: 300000
          # 配置一个连接在池中最大生存的时间,单位是毫秒
          maxEvictableIdleTimeMillis: 900000
          # 配置检测连接是否有效
          validationQuery: SELECT 1 FROM DUAL
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          webStatFilter:
            enabled: true
          statViewServlet:
            enabled: true
            # 设置白名单,不填则允许所有访问
            allow:
            url-pattern: /druid/*
            # 控制台管理用户名和密码
            login-username:
            login-password:
          filter:
            stat:
              enabled: true
              # 慢SQL记录
              log-slow-sql: true
              slow-sql-millis: 1000
              merge-sql: true
            wall:
              config:
                multi-statement-allow: true

    之所以要创建这个@DataSource注解,是要在springboot的启动类上边进行标注:

    /**
     * 自定义多数据源切换注解
     *
     * @author wang
     */
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource {
        /**
         * 切换数据源名称
         */
        public DataSourceType value() default DataSourceType.MASTER;
    }
    /**
     * 数据源
     *
     * @author wang
     */
    public enum DataSourceType {
        /**
         * 主库
         */
        MASTER,
    
        /**
         * 从库
         */
        SLAVE
    }
    import org.example.common.utils.StringUtils;
    import org.example.framework.aspectj.lang.annotation.DataSource;
    import org.example.framework.datasource.DynamicDataSourceContextHolder;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    /**
     * 多数据源处理
     *
     * @author wang
     */
    @Aspect
    @Order(1)
    @Component
    public class DataSourceAspect {
        protected Logger logger = LoggerFactory.getLogger(getClass());
    
        @Pointcut("@annotation(org.example.framework.aspectj.lang.annotation.DataSource)"
                + "|| @within(org.example.framework.aspectj.lang.annotation.DataSource)")
        public void dsPointCut() {
    
        }
    
        @Around("dsPointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            DataSource dataSource = getDataSource(point);
    
            if (StringUtils.isNotNull(dataSource)) {
                DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
            }
    
            try {
                return point.proceed();
            } finally {
                // 销毁数据源 在执行方法之后
                DynamicDataSourceContextHolder.clearDataSourceType();
            }
        }
    
        /**
         * 获取需要切换的数据源
         */
        public DataSource getDataSource(ProceedingJoinPoint point) {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Class<? extends Object> targetClass = point.getTarget().getClass();
            DataSource targetDataSource = targetClass.getAnnotation(DataSource.class);
            if (StringUtils.isNotNull(targetDataSource)) {
                return targetDataSource;
            } else {
                Method method = signature.getMethod();
                DataSource dataSource = method.getAnnotation(DataSource.class);
                return dataSource;
            }
        }
    }
    import com.alibaba.druid.pool.DruidDataSource;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * druid 配置属性
     *
     * @author wang
     */
    @Configuration
    public class DruidProperties {
        @Value("${spring.datasource.druid.initialSize}")
        private int initialSize;
    
        @Value("${spring.datasource.druid.minIdle}")
        private int minIdle;
    
        @Value("${spring.datasource.druid.maxActive}")
        private int maxActive;
    
        @Value("${spring.datasource.druid.maxWait}")
        private int maxWait;
    
        @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
        private int timeBetweenEvictionRunsMillis;
    
        @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
        private int minEvictableIdleTimeMillis;
    
        @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
        private int maxEvictableIdleTimeMillis;
    
        @Value("${spring.datasource.druid.validationQuery}")
        private String validationQuery;
    
        @Value("${spring.datasource.druid.testWhileIdle}")
        private boolean testWhileIdle;
    
        @Value("${spring.datasource.druid.testOnBorrow}")
        private boolean testOnBorrow;
    
        @Value("${spring.datasource.druid.testOnReturn}")
        private boolean testOnReturn;
    
        public DruidDataSource dataSource(DruidDataSource datasource) {
            /** 配置初始化大小、最小、最大 */
            datasource.setInitialSize(initialSize);
            datasource.setMaxActive(maxActive);
            datasource.setMinIdle(minIdle);
    
            /** 配置获取连接等待超时的时间 */
            datasource.setMaxWait(maxWait);
    
            /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
            datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
    
            /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
            datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
            datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
    
            /**
             * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
             */
            datasource.setValidationQuery(validationQuery);
            /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
            datasource.setTestWhileIdle(testWhileIdle);
            /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
            datasource.setTestOnBorrow(testOnBorrow);
            /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
            datasource.setTestOnReturn(testOnReturn);
            return datasource;
        }
    }
    import com.alibaba.druid.pool.DruidDataSource;
    import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
    import com.alibaba.druid.util.Utils;
    
    import org.example.framework.aspectj.lang.enums.DataSourceType;
    import org.example.framework.config.properties.DruidProperties;
    import org.example.common.utils.spring.SpringUtils;
    import org.example.framework.datasource.DynamicDataSource;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.sql.DataSource;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * druid 配置多数据源
     *
     * @author wang
     */
    @Configuration
    public class DruidConfig {
        @Bean
        @ConfigurationProperties("spring.datasource.druid.master")
        public DataSource masterDataSource(DruidProperties druidProperties) {
            DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
            return druidProperties.dataSource(dataSource);
        }
    
        @Bean
        @ConfigurationProperties("spring.datasource.druid.slave")
        @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
        public DataSource slaveDataSource(DruidProperties druidProperties) {
            DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
            return druidProperties.dataSource(dataSource);
        }
    
        @Bean(name = "dynamicDataSource")
        @Primary
        public DynamicDataSource dataSource(DataSource masterDataSource) {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
            setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
            return new DynamicDataSource(masterDataSource, targetDataSources);
        }
    
        /**
         * 设置数据源
         *
         * @param targetDataSources 备选数据源集合
         * @param sourceName        数据源名称
         * @param beanName          bean名称
         */
        public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {
            try {
                DataSource dataSource = SpringUtils.getBean(beanName);
                targetDataSources.put(sourceName, dataSource);
            } catch (Exception e) {
            }
        }
    
        /**
         * 去除监控页面底部的广告
         */
        @SuppressWarnings({"rawtypes", "unchecked"})
        @Bean
        @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
        public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
            // 获取web监控页面的参数
            DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
            // 提取common.js的配置路径
            String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
            String commonJsPattern = pattern.replaceAll("\*", "js/common.js");
            final String filePath = "support/http/resources/js/common.js";
            // 创建filter进行过滤
            Filter filter = new Filter() {
                @Override
                public void init(FilterConfig filterConfig) throws ServletException {
                }
    
                @Override
                public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                        throws IOException, ServletException {
                    chain.doFilter(request, response);
                    // 重置缓冲区,响应头不会被重置
                    response.resetBuffer();
                    // 获取common.js
                    String text = Utils.readFromResource(filePath);
                    // 正则替换banner, 除去底部的广告信息
                    text = text.replaceAll("<a.*?banner"></a><br/>", "");
                    text = text.replaceAll("powered.*?shrek.wang</a>", "");
                    response.getWriter().write(text);
                }
    
                @Override
                public void destroy() {
                }
            };
            FilterRegistrationBean registrationBean = new FilterRegistrationBean();
            registrationBean.setFilter(filter);
            registrationBean.addUrlPatterns(commonJsPattern);
            return registrationBean;
        }
    }
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    import javax.sql.DataSource;
    import java.util.Map;
    
    /**
     * 动态数据源
     *
     * @author wang
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
    }
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * 数据源切换处理
     *
     * @author wang
     */
    public class DynamicDataSourceContextHolder {
        public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    
        /**
         * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
         * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
         */
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
        /**
         * 设置数据源的变量
         */
        public static void setDataSourceType(String dsType) {
            log.info("切换到{}数据源", dsType);
            CONTEXT_HOLDER.set(dsType);
        }
    
        /**
         * 获得数据源的变量
         */
        public static String getDataSourceType() {
            return CONTEXT_HOLDER.get();
        }
    
        /**
         * 清空数据源变量
         */
        public static void clearDataSourceType() {
            CONTEXT_HOLDER.remove();
        }
    }
  • 相关阅读:
    java实现简单web服务器(分析+源代码)
    Java中常见的5种WEB服务器介绍
    Android ViewDragHelper完全解析 自定义ViewGroup神器
    设置SVN,Git忽略MAC的.DS_Store文件的方法
    Android中图片大小和屏幕密度的关系讲解
    Android组件化方案及组件消息总线modular-event实战
    机器学习在美团配送系统的实践:用技术还原真实世界
    智能支付稳定性测试实战
    数据库智能运维探索与实践
    【人物志】技术十年:美团第一位前端工程师潘魏增
  • 原文地址:https://www.cnblogs.com/sxw123/p/13806769.html
Copyright © 2020-2023  润新知