1. 程式人生 > 資料庫 >SpringBoot整合Druid多資料來源含Sql和Web監控

SpringBoot整合Druid多資料來源含Sql和Web監控

SpringBoot整合Druid多資料來源含Sql和Web監控

新建資料庫

新建三個資料庫,準備測試。為了簡單就隨便意思一下吧,分別起名為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監控就都完成了。