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监控就都完成了。
来源:CSDN
作者:还是改日吧
链接:https://blog.csdn.net/weixin_43424932/article/details/104109977