Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程

 以前写过一篇教程,Springboot AOP方式切换多数据源(主从两库类似情况使用最佳):

https://blog.csdn.net/qq_35387940/article/details/100122788


网上大多流传的springboot系列的切换多数据源都是以上那种写死在配置文件里的方式,这样如果我需要切换的数据源有10个,那么这种方式会不会显得稍微有点繁琐了。

现在这篇介绍的流程是,我们把各个数据源的配置信息写在一张数据库表里,从数据库表去加载这些数据源信息,根据我们给每个数据源命名的id去切换数据源,操作对应的数据库。

OK,接下来我们开始(如果真的想弄懂,最好跟我一步步来)

 

首先准备多个数据库,test1 ,test2 ,test3  :

Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程_第1张图片

接下来,我们在test1中,创建表 databasesource ,相关的SQL语句:

CREATE TABLE `databasesource`  (
  `datasource_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '数据源的id',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '连接信息',
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `pass_word` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '暂留字段',
  `databasetype` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '数据库类型'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程_第2张图片

然后往test1数据库中的表databasesource里填充test2 、test3 这两个数据库的相关配置信息(对应的数据库帐号密码改成自己的),相关的SQL语句:
ps:这里面的datasource_id的值,是我们后面手动切换数据源的是使用的数据源 id

INSERT INTO `test1`.`databasesource`(`datasource_id`, `url`, `user_name`, `pass_word`, `code`, `databasetype`) VALUES ('dbtest2', 'jdbc:mysql://localhost:3306/test2?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull', 'root', 'root', NULL, 'mysql');
INSERT INTO `test1`.`databasesource`(`datasource_id`, `url`, `user_name`, `pass_word`, `code`, `databasetype`) VALUES ('dbtest3', 'jdbc:mysql://localhost:3306/test3?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull', 'root', 'root', NULL, 'mysql');

 接下来,我们分别在test2数据库和test3数据库中都创建user表,相关的SQL语句:

CREATE TABLE `user`  (
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(3) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

然后往test2数据库的user表里面填充两条数据用于测试, 相关的SQL语句:

INSERT INTO `test2`.`user`(`user_name`, `age`) VALUES ('数据库2-小明', 20);
INSERT INTO `test2`.`user`(`user_name`, `age`) VALUES ('数据库2-小方', 17);

然后往test3数据库的user表里面填充两条数据用于测试, 相关的SQL语句:

INSERT INTO `test3`.`user`(`user_name`, `age`) VALUES ('数据库3-啊强', 11);
INSERT INTO `test3`.`user`(`user_name`, `age`) VALUES ('数据库3-啊木', 12);

OK,到这里我们的数据库模拟场景已经准备完毕了,接下来下面就是真正的核心环节了!

首先是pom.xml:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.0.RELEASE
         
    
    com.testDb
    dbsource
    0.0.1-SNAPSHOT
    dbsource
    Demo project for Spring Boot

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-jdbc
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.0.0
        
        
        
            org.projectlombok
            lombok
            1.16.10
        
        
            mysql
            mysql-connector-java
            runtime
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
            com.alibaba
            druid-spring-boot-starter
            1.1.10
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


紧接着,application.yml(这里面的数据库配置信息将作为默认数据库):

spring:
  aop:
    proxy-target-class: true #true为使用CGLIB代理
  datasource:

    #nullCatalogMeansCurrent=true&
    url: jdbc:mysql://localhost:3306/test1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
    username: root
    password: root
    #新版mysql驱动配置方法
    driverClassName: com.mysql.cj.jdbc.Driver
    ###################以下为druid增加的配置###########################
    type: com.alibaba.druid.pool.DruidDataSource
    # 下面为连接池的补充设置,应用到上面所有数据源中
    # 初始化大小,最小,最大
    initialSize: 5
    minIdle: 5
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    # 合并多个DruidDataSource的监控数据
    useGlobalDataSourceStat: true
    ###############以上为配置druid添加的配置########################################

mybatis:
  type-aliases-package: com.testdb.dbsource.pojo  #扫描包路径
  configuration:
    map-underscore-to-camel-case: true #打开驼峰命名
  config-location: classpath:mybatis/mybatis-config.xml

server:
  port: 8097

 先创建DataSource.java实体类,数据源信息装配的时候用:

import lombok.Data;
import lombok.ToString;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
@Data
@ToString
public class DataSource {
    String datasourceId;
    String url;
    String userName;
    String passWord;
    String code;
    String databasetype;
}

接下来,创建DruidDBConfig.java:

这里主要是配置默认的数据源,配置Druid数据库连接池,配置sql工厂加载mybatis的文件,扫描实体类等

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
@Configuration
@EnableTransactionManagement
public class DruidDBConfig {
    private final Logger log = LoggerFactory.getLogger(getClass());

    // adi数据库连接信息
    @Value("${spring.datasource.url}")
    private String dbUrl;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.driverClassName}")
    private String driverClassName;
    // 连接池连接信息
    @Value("${spring.datasource.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.maxActive}")
    private int maxActive;
    @Value("${spring.datasource.maxWait}")
    private int maxWait;

    @Bean // 声明其为Bean实例
    @Primary // 在同样的DataSource中,首先使用被标注的DataSource
    @Qualifier("mainDataSource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource datasource = new DruidDataSource();
        // 基础连接信息
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        // 连接池连接信息
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
        datasource.setMaxPoolPreparedStatementPerConnectionSize(50);
        //  datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
        datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
        datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
        datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
        String validationQuery = "select 1 from dual";
        datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
        datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
        datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
        datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
        datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
        datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
        datasource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志
        return datasource;


    }


    /**
     * 注册一个StatViewServlet    druid监控页面配置1-帐号密码配置
     *
     * @return servlet registration bean
     */
    @Bean
    public ServletRegistrationBean druidStatViewServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
                new StatViewServlet(), "/druid/*");
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "123456");
        servletRegistrationBean.addInitParameter("resetEnable", "false");
        return servletRegistrationBean;
    }

    /**
     * 注册一个:filterRegistrationBean   druid监控页面配置2-允许页面正常浏览
     *
     * @return filter registration bean
     */
    @Bean
    public FilterRegistrationBean druidStatFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(
                new WebStatFilter());
        // 添加过滤规则.
        filterRegistrationBean.addUrlPatterns("/*");
        // 添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

    @Bean(name = "dynamicDataSource")
    @Qualifier("dynamicDataSource")
    public DynamicDataSource dynamicDataSource() throws SQLException {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDebug(false);
        //配置缺省的数据源
        // 默认数据源配置 DefaultTargetDataSource
        dynamicDataSource.setDefaultTargetDataSource(dataSource());
        Map targetDataSources = new HashMap();
        //额外数据源配置 TargetDataSources
        targetDataSources.put("mainDataSource", dataSource());
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        //解决手动创建数据源后字段到bean属性名驼峰命名转换失效的问题
        sqlSessionFactoryBean.setConfiguration(configuration());

        // 设置mybatis的主配置文件
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");
        //  sqlSessionFactoryBean.setConfigLocation(mybatisConfigXml);
        // 设置别名包
        //  sqlSessionFactoryBean.setTypeAliasesPackage("com.testdb.dbsource.pojo");
        //就是这句代码,只能指定单个mapper.xml文件,加通配符的话找不到文件
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mybatis/mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }


    /**
     * 读取驼峰命名设置
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "mybatis.configuration")
    public org.apache.ibatis.session.Configuration configuration() {
        return new org.apache.ibatis.session.Configuration();
    }


}

 然后是用于手动切换数据源的 DBContextHolder.java:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
public class DBContextHolder {
    private final static Logger log = LoggerFactory.getLogger(DBContextHolder.class);
    // 对当前线程的操作-线程安全的
    private static final ThreadLocal contextHolder = new ThreadLocal();

    // 调用此方法,切换数据源
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
        log.info("已切换到数据源:{}",dataSource);
    }

    // 获取数据源
    public static String getDataSource() {
        return contextHolder.get();
    }

    // 删除数据源
    public static void clearDataSource() {
        contextHolder.remove();
        log.info("已切换到主数据源");
    }

}

然后是核心,手动加载默认数据源、创建数据源连接、检查数据源连接、删除数据源连接等 ,DynamicDataSource.java:

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.testdb.dbsource.pojo.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.StringUtils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Map;
import java.util.Set;


public class DynamicDataSource extends AbstractRoutingDataSource {
    private boolean debug = true;
    private final Logger log = LoggerFactory.getLogger(getClass());
    private Map dynamicTargetDataSources;
    private Object dynamicDefaultTargetDataSource;


    @Override
    protected Object determineCurrentLookupKey() {
        String datasource = DBContextHolder.getDataSource();
        if (!StringUtils.isEmpty(datasource)) {
            Map dynamicTargetDataSources2 = this.dynamicTargetDataSources;
            if (dynamicTargetDataSources2.containsKey(datasource)) {
                log.info("---当前数据源:" + datasource + "---");
            } else {
                log.info("不存在的数据源:");
                return null;
//                    throw new ADIException("不存在的数据源:"+datasource,500);
            }
        } else {
            log.info("---当前数据源:默认数据源---");
        }

        return datasource;
    }

    @Override
    public void setTargetDataSources(Map targetDataSources) {

        super.setTargetDataSources(targetDataSources);

        this.dynamicTargetDataSources = targetDataSources;

    }


    // 创建数据源
    public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {
        try {
            try { // 排除连接不上的错误
                Class.forName(driveClass);
                DriverManager.getConnection(url, username, password);// 相当于连接数据库

            } catch (Exception e) {

                return false;
            }
            @SuppressWarnings("resource")
//            HikariDataSource druidDataSource = new HikariDataSource();
                    DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setName(key);
            druidDataSource.setDriverClassName(driveClass);
            druidDataSource.setUrl(url);
            druidDataSource.setUsername(username);
            druidDataSource.setPassword(password);
            druidDataSource.setInitialSize(50); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
            druidDataSource.setMaxActive(200); //最大连接池数量
            druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象
            druidDataSource.setMinIdle(40); //最小连接池数量
            String validationQuery = "select 1 from dual";
//            if("mysql".equalsIgnoreCase(databasetype)) {
//                driveClass = DBUtil.mysqldriver;
//                validationQuery = "select 1";
//            } else if("oracle".equalsIgnoreCase(databasetype)){
//                driveClass = DBUtil.oracledriver;
//                druidDataSource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
//                druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(50);
//                int sqlQueryTimeout = ADIPropUtil.sqlQueryTimeOut();
//                druidDataSource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout="+sqlQueryTimeout);//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
//            } else if("sqlserver2000".equalsIgnoreCase(databasetype)){
//                driveClass = DBUtil.sql2000driver;
//                validationQuery = "select 1";
//            } else if("sqlserver".equalsIgnoreCase(databasetype)){
//                driveClass = DBUtil.sql2005driver;
//                validationQuery = "select 1";
//            }
            druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
            druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
            druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
            druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
            druidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
            druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
            druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
            druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
            druidDataSource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志
            druidDataSource.init();
            this.dynamicTargetDataSources.put(key, druidDataSource);
            setTargetDataSources(this.dynamicTargetDataSources);// 将map赋值给父类的TargetDataSources
            super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
            log.info(key+"数据源初始化成功");
            //log.info(key+"数据源的概况:"+druidDataSource.dump());
            return true;
        } catch (Exception e) {
            log.error(e + "");
            return false;
        }
    }
    // 删除数据源
    public boolean delDatasources(String datasourceid) {
        Map dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        if (dynamicTargetDataSources2.containsKey(datasourceid)) {
            Set druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
            for (DruidDataSource l : druidDataSourceInstances) {
                if (datasourceid.equals(l.getName())) {
                    dynamicTargetDataSources2.remove(datasourceid);
                    DruidDataSourceStatManager.removeDataSource(l);
                    setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources
                    super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }

    // 测试数据源连接是否有效
    public boolean testDatasource(String key, String driveClass, String url, String username, String password) {
        try {
            Class.forName(driveClass);
            DriverManager.getConnection(url, username, password);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    @Override
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        this.dynamicDefaultTargetDataSource = defaultTargetDataSource;
    }

    /**
     * @param debug
     *            the debug to set
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * @return the debug
     */
    public boolean isDebug() {
        return debug;
    }

    /**
     * @return the dynamicTargetDataSources
     */
    public Map getDynamicTargetDataSources() {
        return dynamicTargetDataSources;
    }

    /**
     * @param dynamicTargetDataSources
     *            the dynamicTargetDataSources to set
     */
    public void setDynamicTargetDataSources(Map dynamicTargetDataSources) {
        this.dynamicTargetDataSources = dynamicTargetDataSources;
    }

    /**
     * @return the dynamicDefaultTargetDataSource
     */
    public Object getDynamicDefaultTargetDataSource() {
        return dynamicDefaultTargetDataSource;
    }

    /**
     * @param dynamicDefaultTargetDataSource
     *            the dynamicDefaultTargetDataSource to set
     */
    public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) {
        this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource;
    }

    public void createDataSourceWithCheck(DataSource dataSource) throws Exception {
        String datasourceId = dataSource.getDatasourceId();
        log.info("正在检查数据源:"+datasourceId);
        Map dynamicTargetDataSources2 = this.dynamicTargetDataSources;
        if (dynamicTargetDataSources2.containsKey(datasourceId)) {
            log.info("数据源"+datasourceId+"之前已经创建,准备测试数据源是否正常...");
            //DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);
            DruidDataSource druidDataSource = (DruidDataSource) dynamicTargetDataSources2.get(datasourceId);
            boolean rightFlag = true;
            Connection connection = null;
            try {
                log.info(datasourceId+"数据源的概况->当前闲置连接数:"+druidDataSource.getPoolingCount());
                long activeCount = druidDataSource.getActiveCount();
                log.info(datasourceId+"数据源的概况->当前活动连接数:"+activeCount);
                if(activeCount > 0) {
                    log.info(datasourceId+"数据源的概况->活跃连接堆栈信息:"+druidDataSource.getActiveConnectionStackTrace());
                }
                log.info("准备获取数据库连接...");
                connection = druidDataSource.getConnection();
                log.info("数据源"+datasourceId+"正常");
            } catch (Exception e) {
                log.error(e.getMessage(),e); //把异常信息打印到日志文件
                rightFlag = false;
                log.info("缓存数据源"+datasourceId+"已失效,准备删除...");
                if(delDatasources(datasourceId)) {
                    log.info("缓存数据源删除成功");
                } else {
                    log.info("缓存数据源删除失败");
                }
            } finally {
                if(null != connection) {
                    connection.close();
                }
            }
            if(rightFlag) {
                log.info("不需要重新创建数据源");
                return;
            } else {
                log.info("准备重新创建数据源...");
                createDataSource(dataSource);
                log.info("重新创建数据源完成");
            }
        } else {
            createDataSource(dataSource);
        }

    }

    private  void createDataSource(DataSource dataSource) throws Exception {
        String datasourceId = dataSource.getDatasourceId();
        log.info("准备创建数据源"+datasourceId);
        String databasetype = dataSource.getDatabasetype();
        String username = dataSource.getUserName();
        String password = dataSource.getPassWord();
        String url = dataSource.getUrl();
        String driveClass = "com.mysql.cj.jdbc.Driver";
//        if("mysql".equalsIgnoreCase(databasetype)) {
//            driveClass = DBUtil.mysqldriver;
//        } else if("oracle".equalsIgnoreCase(databasetype)){
//            driveClass = DBUtil.oracledriver;
//        }  else if("sqlserver2000".equalsIgnoreCase(databasetype)){
//            driveClass = DBUtil.sql2000driver;
//        } else if("sqlserver".equalsIgnoreCase(databasetype)){
//            driveClass = DBUtil.sql2005driver;
//        }
        if(testDatasource(datasourceId,driveClass,url,username,password)) {
            boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype);
            if(!result) {
                log.error("数据源"+datasourceId+"配置正确,但是创建失败");
//                throw new ADIException("数据源"+datasourceId+"配置正确,但是创建失败",500);
            }
        } else {
            log.error("数据源配置有错误");
//            throw new ADIException("数据源配置有错误",500);
        }
    }

}

ok,然后是我们切换数据源使用的方法, 我们这里才有mybatis注解的方式获取test1数据库里的databasesource 表信息,然后根据我们传入对应的数据源id进行数据源切换:

DataSourceMapper.java :

import com.testdb.dbsource.pojo.DataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/23
 * @Description :
 **/
@Mapper
public interface DataSourceMapper {

    @Select("SELECT * FROM databasesource")
    List get();


}

DBChangeService.java:

import com.testdb.dbsource.pojo.DataSource;
import java.util.List;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/

public interface DBChangeService {

    List get();

    boolean changeDb(String datasourceId) throws Exception;

}

DBChangeServiceImpl.java:

import com.testdb.dbsource.dbconfig.DBContextHolder;
import com.testdb.dbsource.dbconfig.DynamicDataSource;
import com.testdb.dbsource.mapper.DataSourceMapper;
import com.testdb.dbsource.pojo.DataSource;
import com.testdb.dbsource.service.DBChangeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/
@Service
public class DBChangeServiceImpl implements DBChangeService {

   @Autowired
   DataSourceMapper dataSourceMapper;
    @Autowired
    private DynamicDataSource dynamicDataSource;
    @Override
    public List get() {
        return dataSourceMapper.get();
    }

    @Override
    public boolean changeDb(String datasourceId) throws Exception {

        //默认切换到主数据源,进行整体资源的查找
        DBContextHolder.clearDataSource();

        List dataSourcesList = dataSourceMapper.get();

        for (DataSource dataSource : dataSourcesList) {
            if (dataSource.getDatasourceId().equals(datasourceId)) {
                System.out.println("需要使用的的数据源已经找到,datasourceId是:" + dataSource.getDatasourceId());
                //创建数据源连接&检查 若存在则不需重新创建
                dynamicDataSource.createDataSourceWithCheck(dataSource);
                //切换到该数据源
                DBContextHolder.setDataSource(dataSource.getDatasourceId());
                return true;
            }
        }
        return false;

    }

}

注意认真看看上面的changeDb这个方法里面的代码,这就是后续手动切换调用的方法。

 

接下来,写相关操作user表的代码,因为user表分别在test2、test3数据库里,这样用于我们切换到test2或者test3数据库操作这些数据。

User.java:

import lombok.Data;
import lombok.ToString;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/22
 * @Description :
 **/

@Data
@ToString
public class User {

    String userName;
    String age;
}

UserMappper.java  (上面简单介绍了下使用注解的方式获取表数据,可能有些人不习惯,那么这里也使用传统的mapper.xml方式编写mysql语句):

import com.testdb.dbsource.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/23
 * @Description :
 **/
@Mapper
public interface UserMapper {

    List queryUserInfo();


    
}

userMapper.xml(注意namespace命名空间对应的路径以及user实体类对应的路径):




    
    
    

    

顺便一提,我的mapper.xml放在了下面的这个目录结果里,这里的目录结构路径非常关键,因为我们是手动切换数据源,采取了手动配置SqlSessionFactory,需要我们自己去配置mapper.xml路径的,一开始的DruidDBConfig里面有相关的代码,可以去回顾下:

Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程_第3张图片

顺带,mybatis-config.xml里面,我做了简单的配置:

其实关于驼峰命名方式开启,我们在手动配置的时候也特意做了配置代码的。





    
        
        
        
    



到这里,我们已经可以开始测试,

创建UserController.java,写个简单的测试接口:

import com.testdb.dbsource.dbconfig.DBContextHolder;
import com.testdb.dbsource.pojo.User;
import com.testdb.dbsource.service.DBChangeService;
import com.testdb.dbsource.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

/**
 * @Author : JCccc
 * @CreateTime : 2019/10/23
 * @Description :
 **/

@RestController
public class UserController {


    @Autowired
    private DBChangeService dbChangeServiceImpl;
    @Autowired
    UserService userService;




    /**
     * 查询所有
     * @return
     */
    @GetMapping("/test")
    public  String test() throws Exception {

        //切换到数据库dbtest2
        String datasourceId="dbtest2";
        dbChangeServiceImpl.changeDb(datasourceId);
        List userList= userService.queryUserInfo();
        System.out.println(userList.toString());

        //再切换到数据库dbtest3
        dbChangeServiceImpl.changeDb("dbtest3");
        List userList3= userService.queryUserInfo();
        System.out.println(userList3.toString());


        //切回主数据源
        DBContextHolder.clearDataSource();
        return "ok";
    }

}

 主要看代码注释,调用整合出来的changeDb方法,通过传送数据源id (dbtest2)

切换到对应的数据库,然后操作对应数据库。

 

项目运行起来,调用接口 /test :

 Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程_第4张图片

然后我们来看看控制台输出内容:

Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程_第5张图片

OK,非常简单顺利,教程就到此。 

你可能感兴趣的:(Springboot 从数据库读取数据库配置信息,动态切换多数据源 最详细实战教程)