Springboot1.5.6整合druid(不重启项目动态添加数据源)和数据监控,附源码
druid
阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池
项目地址:https://gitcode.com/gh_mirrors/druid/druid
免费下载资源
·
一、要点:
1.重写AbstractRoutingDataSource类的determineCurrentLookupKey方法,实现数据源的切换
2.ThreadLocal 保证当前线程安全的前提下设置当前线程的数据源
3.application.properties文件配置数据源为spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
4.配置druid的拦截器和servlet
二、效果图:
token参数:自定义拦截器实现token的拦截
key参数:数据源的选择
创建三个数据库
程序初始化已经加载了master,slave,slave2在不重新启动的情况下添加
1.访问主数据源
2.访问slave数据源
3.访问slave2数据源(注意,报错数据源不存在)
4.添加数据源slave2(代码写死添加slave2,根据实际情况修改)
5.再次访问slave2数据源(查询出数据)
6.druid数据监控(开始会看见两个数据源,动态添加之后可以看见三个数据源)
三、项目源代码:
项目地址:https://gitee.com/373616511/springboot_druid_dyamicDataSource.git
部分源码:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.csstj</groupId>
<artifactId>srm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>srm</name>
<description>interface_zkhz</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<!-- 阿里连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.2</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<!--配置文件的位置-->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
application.properties
# application-dev.properties:用于开发环境
# application-test.properties:用于测试环境
# application-prod.properties:用于生产环境
# 在这个三个配置文件中设置不同的信息,application.properties 配置公共的信息
spring.profiles.active=dev
# 表示激活 application-dev.properties 文件配置, springboot 会加载
# 使用 application.properties 和 application-dev.properties 配置文件的信息。
#=============================================================================================
# 日志配置
logging.config=classpath:logback-spring.xml
#=============================================================================================
application-dev.properties
#端口
server.port=8080
server.tomcat.uri-encoding=UTF-8
#静态资源路径
spring.resources.static-locations=classpath:/
# 数据源1
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=root
# 数据源1
#spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.secondary.url=jdbc:mysql://localhost:3306/test
#spring.datasource.secondary.username=root
#spring.datasource.secondary.password=root
# 制定mybatis配置文件位置
mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mappers/*.xml
#打印sql
logging.level.com.csstj.srm.dao=debug
###################以下为druid增加的配置###########################
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=50
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60001
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters=stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.connectionInitSqls=SELECT 1 FROM DUAL
# 合并多个DruidDataSource的监控数据
spring.datasource.useGlobalDataSourceStat=true
Config
package com.csstj.srm.common;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.csstj.srm.common.dynamicDataSource.MyDynamicDataSource;
import com.csstj.srm.listener.SrmFilter;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class Config {
private final Logger logger = org.slf4j.LoggerFactory.getLogger(Config.class);
/**
* @return
* @Bean 防止数据监控报错,无法查看数据源
* @ConfigurationProperties 会把配置文件的参数自动赋值到dataSource里。
* @Primary 用于标识默认使用的 DataSource Bean
*/
@Bean(destroyMethod = "close", initMethod = "init", name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
@Primary
public DataSource masterDataSource() {
logger.info("创建masterDataSource");
//DruidDataSource druidDataSource = new DruidDataSource();
DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
return druidDataSource;
}
/* @Bean(destroyMethod = "close", initMethod = "init", name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource slaveDataSource() {
logger.info("创建slaveDataSource");
//DruidDataSource druidDataSource = new DruidDataSource();
DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
return druidDataSource;
}*/
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
MyDynamicDataSource myDynamicDataSource = new MyDynamicDataSource();
// 配置多数据源
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
//targetDataSources.put("slave", slaveDataSource());
myDynamicDataSource.setTargetDataSources(targetDataSources);
//设置默认数据源,在动态添加数据源的时候,就可以不再添加此数据源了
// myDynamicDataSource.setDefaultTargetDataSource(masterDataSource());
//dynamicDataSource.setDefaultTargetDataSource(slaveDataSource());
return myDynamicDataSource;
}
/**
* 配置 SqlSessionFactoryBean
*/
@Bean(value = "sqlSessionFactoryBean222")
@ConfigurationProperties(prefix = "mybatis")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource 作为数据源则不能实现切换
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
return sqlSessionFactoryBean;
}
/**
* 注入 DataSourceTransactionManager 用于事务管理
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
@Bean
public ServletRegistrationBean statViewServlet() {
//创建servlet注册实体
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
//设置ip白名单
servletRegistrationBean.addInitParameter("allow", "");
//设置ip黑名单
servletRegistrationBean.addInitParameter("deny", "");
//设置控制台管理用户__登录用户名和密码
servletRegistrationBean.addInitParameter("loginUsername", "druid");
servletRegistrationBean.addInitParameter("loginPassword", "123456");
//是否可以重置数据
servletRegistrationBean.addInitParameter("resetEnable", "false");
return servletRegistrationBean;
}
//过滤器
@Bean
public FilterRegistrationBean filterRegist() {
logger.info("注册过滤器");
FilterRegistrationBean frBean = new FilterRegistrationBean();
frBean.setFilter(new SrmFilter());
frBean.addUrlPatterns("/*");
return frBean;
}
/**
* druid过滤器
*
* @return
*/
@Bean
public FilterRegistrationBean statFilter() {
//创建过滤器
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
//设置过滤器过滤路径
filterRegistrationBean.addUrlPatterns("/*");
//忽略过滤的形式
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
SrmApplication
package com.csstj.srm;
import com.csstj.srm.common.DataSourceUtil;
import com.csstj.srm.utils.SpringContextUtil;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
@MapperScan("com.csstj.srm.dao")
public class SrmApplication {
private static final Logger logger = LoggerFactory.getLogger(SrmApplication.class);
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(SrmApplication.class, args);
logger.info("springboot启动成功");
SpringContextUtil.setApplicationContext(applicationContext);
DataSourceUtil.initDataSource();
}
}
DataSourceUtil
package com.csstj.srm.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.csstj.srm.common.dynamicDataSource.MyDynamicDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class DataSourceUtil {
private static final Logger logger = LoggerFactory.getLogger(DataSourceUtil.class);
public static final Map<Object, Object> dataSourceMap = new HashMap<>();
public static void initDataSource() {
//获取masterDataSource
DruidDataSource masterDataSource = (DruidDataSource) SpringContextUtil.getBean("masterDataSource");
addDataSource("master", masterDataSource);
//初始化其它数据源
initOthersDataSource();
//刷新数据源
flushDataSource();
}
public static void flushDataSource() {
//获取spring管理的dynamicDataSource
MyDynamicDataSource myDynamicDataSource = (MyDynamicDataSource) SpringContextUtil.getBean("dynamicDataSource");
//将数据源设置到 targetDataSources
myDynamicDataSource.setTargetDataSources(dataSourceMap);
//将 targetDataSources 中的连接信息放入 resolvedDataSources 管理
myDynamicDataSource.afterPropertiesSet();
}
public static void addDataSource(String key, DruidDataSource masterDataSource) {
dataSourceMap.put(key, masterDataSource);
}
private static void initOthersDataSource() {
//在此处可以查询出所有的数据源(例如,配置文件,数据库)然后添加
String key = "slave";
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/test");
//添加数据源到map
addDataSource(key, druidDataSource);
}
}
SpringContextUtil
package com.csstj.srm.utils;
import org.springframework.context.ApplicationContext;
public class SpringContextUtil {
private static ApplicationContext applicationContext;
//获取上下文
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//设置上下文
public static void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
//通过名字获取上下文中的bean
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
//通过类型获取上下文中的bean
public static Object getBean(Class<?> requiredType) {
return applicationContext.getBean(requiredType);
}
}
MyDynamicDataSource
package com.csstj.srm.common.dynamicDataSource;
import com.mysql.jdbc.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class MyDynamicDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(MyDynamicDataSource.class);
@Override
public Object determineCurrentLookupKey() {
//获取当前线程的数据源,如果不存在使用master数据源
String datasource = DBContextHolder.getDataSource();
if (StringUtils.isNullOrEmpty(datasource)) {
datasource = "master";
}
logger.info("datasource=" + datasource);
return datasource;
}
}
DBContextHolder
package com.csstj.srm.common.dynamicDataSource;
import com.csstj.srm.utils.DataSourceUtil;
public class DBContextHolder {
// 对当前线程的操作-线程安全的
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
// 调用此方法,切换数据源
public static void setDataSource(String dataSource) {
if (DataSourceUtil.dataSourceMap.containsKey(dataSource)) {
contextHolder.set(dataSource);
} else {
throw new RuntimeException("数据源:" + dataSource + "不存在");
}
}
// 获取数据源
public static String getDataSource() {
return contextHolder.get();
}
// 删除数据源
public static void clearDataSource() {
contextHolder.remove();
}
}
SrmFilter
package com.csstj.srm.listener;
import org.slf4j.Logger;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
public class SrmFilter implements javax.servlet.Filter {
private final Logger logger = org.slf4j.LoggerFactory.getLogger(SrmFilter.class);
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("过滤器初始化");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("过滤器执行");
String token = servletRequest.getParameter("token");
if (isIgnore(servletRequest)) {
//放开执行
filterChain.doFilter(servletRequest, servletResponse);
return;
}
//校验token
if (!"123456".equals(token)) {
logger.info("请求被拦截");
PrintWriter writer = null;
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html; charset=utf-8");
try {
writer = servletResponse.getWriter();
String error = "token信息有误";
writer.print(error);
} catch (IOException e) {
logger.error("response error", e);
} finally {
if (writer != null)
writer.close();
}
} else {
logger.info("请求放行");
//放开执行
filterChain.doFilter(servletRequest, servletResponse);
}
}
private boolean isIgnore(ServletRequest servletRequest) {
boolean isIgnore = false;
HttpServletRequest request = (HttpServletRequest) servletRequest;
String url = "http://" + request.getServerName() //服务器地址
+ ":"
+ request.getServerPort() //端口号
+ request.getContextPath() //项目名称
+ request.getServletPath(); //请求页面或其他地址
logger.info("url:" + url);
if (url.contains("druid")) {
isIgnore = true;
logger.info("不拦截的url:" + url);
}
if (url.contains("static")) {
isIgnore = true;
logger.info("不拦截的url:" + url);
}
return isIgnore;
}
public void destroy() {
logger.info("过滤器销毁");
}
}
DemoController
package com.csstj.srm.controller;
import com.alibaba.druid.pool.DruidDataSource;
import com.csstj.srm.common.DataSourceUtil;
import com.csstj.srm.common.dynamicDataSource.DBContextHolder;
import com.csstj.srm.entity.Demo;
import com.csstj.srm.service.DemoService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping(value = "/demo/")
public class DemoController {
//日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出。
private final Logger logger = org.slf4j.LoggerFactory.getLogger(DemoController.class);
@Autowired
private DemoService demoService;
@RequestMapping(value = "demo")
public Map<String, String> demo(String token, String key) {
Map<String, String> map = new HashMap<>();
try {
logger.trace("trace");
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");
DBContextHolder.setDataSource(key);
Demo demo = demoService.selectByPrimaryKey(1);
map.put("token", demo.toString());
for (int i = 0; i < 100; i++) {
final int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
if (finalI % 2 == 0) {
DBContextHolder.setDataSource("master");
} else {
DBContextHolder.setDataSource("slave");
}
Demo demo = demoService.selectByPrimaryKey(1);
logger.info("多线程查询:(i=" + finalI + "):" + demo.toString());
}
}).run();
}
} catch (Exception e) {
logger.error(e.toString());
map.put("msg", e.getMessage());
}
return map;
}
@RequestMapping(value = "add")
public Map<String, String> add(String token) {
Map<String, String> map = new HashMap<>();
String key = "slave2";
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/test2");
//添加数据源到map
DataSourceUtil.addDataSource(key, druidDataSource);
//刷新
DataSourceUtil.flushDataSource();
map.put("msg", "数据源数量:" + DataSourceUtil.dataSourceMap.size());
return map;
}
}
GitHub 加速计划 / druid / druid
27.83 K
8.56 K
下载
阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池
最近提交(Master分支:2 个月前 )
f060c270 - 4 天前
1613a765
* Improve gaussdb ddl parser
* fix temp table 5 天前
更多推荐
已为社区贡献2条内容
所有评论(0)