一、要点:

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 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐