一、要点:
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
4.0.0
com.csstj
srm
0.0.1-SNAPSHOT
jar
srm
interface_zkhz
org.springframework.boot
spring-boot-starter-parent
1.5.6.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-aop
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
mysql
mysql-connector-java
5.1.45
com.alibaba
druid-spring-boot-starter
1.1.2
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-compiler-plugin
1.7
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.2
src/main/resources/generatorConfig.xml
true
true
Generate MyBatis Artifacts
generate
org.mybatis.generator
mybatis-generator-core
1.3.2
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
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
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 contextHolder = new ThreadLocal();
// 调用此方法,切换数据源
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 demo(String token, String key) {
Map 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 add(String token) {
Map 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;
}
}