SpringBoot整合Druid多数据源含Sql和Web监控

强颜欢笑 提交于 2020-01-30 16:36:55

新建数据库

新建三个数据库,准备测试。为了简单就随便意思一下吧,分别起名为springboot,springboot1,springboot2。然后在每个数据库里面都新建上一张名为person的表,并存入不同的数据。测试的时候就来查这些个person的列表。
在这里插入图片描述

导入依赖

其中 aop 多数据源要用到,druid用来实现监控,lombok用来减少代码,不用自己写getter、setter方法。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.4</version>
</dependency>

修改配置文件

修改全局yaml配置文件,即resources文件夹下的 application.yml 文件:

#数据源配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    
    initial-size: 10
    max-active: 100
    min-idle: 10
    max-wait: 6000
    pool-prepared-statements: true
    max-pool-prepared-statement-per-connection-size: 20
    time-between-eviction-runs-millis: 60000
    min-evictable-idle-time-millis: 300000   
    #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

    #多数据源配置;不需要时直接删掉即可
    dynamic:
      datasource:
        slave1:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/springboot1?serverTimezone=Asia/Shanghai
          username: root
          password: 123456
        slave2:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/springboot2?serverTimezone=Asia/Shanghai
          username: root
          password: 123456

新建参数配置相关类

新建两个参数配置类,用来映射配置文件中的参数。

package com.ge.datasource;

import lombok.Data;

@Data
public class DataSourceProperties {

    private String driverClassName;
    private String url;
    private String username;
    private String password;

    // Druid默认参数
    private int initialSize = 2;
    private int maxActive = 10;
    private int minIdle = -1;
    private long maxWait = 60 * 1000L;
    private long timeBetweenEvictionRunsMillis = 60 * 1000L;
    private long minEvictableIdleTimeMillis = 1000L * 60L * 30L;
    private long maxEvictableIdleTimeMillis = 1000L * 60L * 60L * 7;
    private boolean poolPreparedStatements = false;
    private String filters = "stat,wall";
    private int maxPoolPreparedStatementPerConnectionSize =20;
    private boolean useGlobalDataSourceStat =true;
    private String connectionProperties = "druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500";
}

从数据源属性:

package com.ge.datasource;

import lombok.Data;

import java.util.LinkedHashMap;
import java.util.Map;

@Data
public class DynamicDataSourceProperties {

    private Map<String, DataSourceProperties> datasource = new LinkedHashMap<>();
}

新建上下文环境相关类

新建上下文环境类,用于存储我们当前线程的数据源key。

package com.ge.datasource;

/**
 * 多数据源上下文
 * 用于存储我们当前线程的数据源key
 */
public class DynamicDataContextHolder {

    /**
     * 线程级别的私有变量
     */
    private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();

    /**
     * 获得当前线程数据源
     */
    public static String get () {
        return HOLDER.get();
    }

    /**
     * 设置当前线程数据源
     */
    public static void set (String dataSourceRouterKey) {
        HOLDER.set(dataSourceRouterKey);
    }

    /**
     * 清空当前线程数据源
     * 设置数据源之前一定要先移除
     */
    public static void remove () {
        HOLDER.remove();
    }
}

通知spring从上下文环境中获取当前线程的数据源的key值:

package com.ge.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源路由
 * 通知spring用当前key的数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    //返回当前线程的数据源的key
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataContextHolder.get();
    }
}

新建主配置类

绑定配置参数,并创建自定义数据源。

package com.ge.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 * 配置多数据源
 */
@Configuration
public class DynamicDataSourceConfig {

    //将配置文件数据库的属性值赋给 DataSourceProperties 类
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    //除主数据源外的其他数据源配置信息
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.dynamic")
    public DynamicDataSourceProperties dynamicDataSourceproperties() {
        return new DynamicDataSourceProperties();
    }

    //配置自定义数据源
    @Bean
    public DynamicDataSource dynamicDataSource(DataSourceProperties dataSourceProperties,DynamicDataSourceProperties dynamicDataSourceproperties) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();

        //其他数据源
        Map<String, DataSourceProperties> dataSourcePropertiesMap = dynamicDataSourceproperties.getDatasource();
        Map<Object, Object> targetDataSources = new HashMap<>(dataSourcePropertiesMap.size());
        //遍历除主数据源外的其他数据源
        for(Map.Entry<String, DataSourceProperties> properties:dataSourcePropertiesMap.entrySet()){
            //创建其他数据源
            DruidDataSource druidDataSource = buildDruidDataSource(properties.getValue());
            targetDataSources.put(properties.getKey(), druidDataSource);
        }
        dynamicDataSource.setTargetDataSources(targetDataSources);

        //创建默认数据源
        DruidDataSource defaultDataSource = buildDruidDataSource(dataSourceProperties);
        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);

        return dynamicDataSource;
    }

    //给druid数据源成员赋值
    public static DruidDataSource buildDruidDataSource(DataSourceProperties properties) {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getDriverClassName());
        druidDataSource.setUrl(properties.getUrl());
        druidDataSource.setUsername(properties.getUsername());
        druidDataSource.setPassword(properties.getPassword());
        // Druid默认参数
        druidDataSource.setInitialSize(properties.getInitialSize());
        druidDataSource.setMaxActive(properties.getMaxActive());
        druidDataSource.setMinIdle(properties.getMinIdle());
        druidDataSource.setMaxWait(properties.getMaxWait());
        druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());
        druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());
        druidDataSource.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis());
        try {
            druidDataSource.setFilters(properties.getFilters());
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return druidDataSource;
    }
}

新建注解和AOP类

用注解+AOP来实现动态切换数据源。新建一个标识数据源的注解@DataSource。

package com.ge.datasource;

import java.lang.annotation.*;

/**
 * 多数据源注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "";
}

新建AOP切面类,这里直接用全能的Around环绕通知,在业务逻辑方法执行之前将当前指定的数据源 key 放入上下文中,
方法执行完之后再将它移除。

package com.ge.datasource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 多数据源,切面处理类
 */
@Aspect
@Component
public class DataSourceAspect {

    //用环绕通知执行业务逻辑方法
    @Around("@annotation(com.ge.datasource.DataSource) || @within(com.ge.datasource.DataSource)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class targetClass = point.getTarget().getClass();
        Method method = signature.getMethod();

        DataSource targetDataSource = (DataSource)targetClass.getAnnotation(DataSource.class);
        DataSource methodDataSource = method.getAnnotation(DataSource.class);
        if(targetDataSource != null || methodDataSource != null){
            //注释值
            String value;
            if(methodDataSource != null){
                value = methodDataSource.value();
            }else {
                value = targetDataSource.value();
            }
            //将当前指定值value放入上下文
            DynamicDataContextHolder.set(value);
        }

        Object proceed=null;
        try {
            //执行业务逻辑方法并返回
            proceed=point.proceed();
        } catch (Exception e){
            //方法执行异常
            e.printStackTrace();
        }finally {
            //清空当前线程数据源
            DynamicDataContextHolder.remove();
        }
        return proceed;
    }
}

到这里动态多数据源就基本实现了。大致思路就是通过注解+AOP的方式,在逻辑方法利用反射执行之前,从@DataSource注解中取出当前数据源的 key ,并将它存入上下文环境中供 spring 访问数据库的时候调用,然后逻辑方法执行完了之后再将这个 key 移除,从而实现注解式的数据源动态化。

新建Sql和Web监控类

上面实现了动态数据源,接下来实现Sql和Web监控。
新建监控类并设置好访问路径为 /druid/* 、用户名为admin、密码为123456。

package com.ge.config;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

    // 配置Druid的监控
    // 配置一个管理后台的Servlet
    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        Map<String, String> initParams = new HashMap<>();
        
        //登录用户名和密码
        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "123456");

        bean.setInitParameters(initParams);
        return bean;
    }

    // 配置一个web监控的filter
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*");

        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}

新建测试接口类

一切就绪,写三个接口试一下。
方法或者上用@DataSource注解标注并在其value属性中写出要连接的数据源名称key。为了方便起见我就直接用 JdbcTemplate 来查数据了。

package com.ge.controller;

import com.ge.datasource.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;

@Controller
public class MyController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @GetMapping("find")
    @ResponseBody
    public List<Map<String, Object>> find(){
        List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from person");
        return  list;
    }

    @DataSource("slave1")
    @GetMapping("find1")
    @ResponseBody
    public List<Map<String, Object>> find1(){
        List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from person");
        return  list;
    }

    @DataSource("slave2")
    @GetMapping("find2")
    @ResponseBody
    public List<Map<String, Object>> find2(){
        List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from person");
        return  list;
    }
}

测试结果展示

运行项目,分别请求不同数据源所对应的接口,下面看请求结果:
请求接口http://localhost:8080/find:
在这里插入图片描述
请求接口http://localhost:8080/find1:
在这里插入图片描述
请求接口http://localhost:8080/find2:
在这里插入图片描述
再来看看druid监控。访问上面配置好的路径 http://localhost:8080/druid/,用设置好的用户名 admin 和密码 123456 登录。
Sql监控:
在这里插入图片描述
Web监控:
在这里插入图片描述
Sql防火墙:
在这里插入图片描述
应该有的都有了,到这里多数据源和druid监控就都完成了。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!