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監控就都完成了。