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

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

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

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

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

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

接下来,我们在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;

然后往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:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.0.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.testDb</groupId><artifactId>dbsource</artifactId><version>0.0.1-SNAPSHOT</version><name>dbsource</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.0</version></dependency><!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- druid数据源驱动 1.1.10解决springboot从1.0——2.0版本问题--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

紧接着,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=convertToNullusername: rootpassword: root#新版mysql驱动配置方法driverClassName: com.mysql.cj.jdbc.Driver###################以下为druid增加的配置###########################type: com.alibaba.druid.pool.DruidDataSource# 下面为连接池的补充设置,应用到上面所有数据源中# 初始化大小,最小,最大initialSize: 5minIdle: 5maxActive: 20# 配置获取连接等待超时的时间maxWait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒timeBetweenEvictionRunsMillis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: false# 打开PSCache,并且指定每个连接上PSCache的大小poolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 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.xmlserver: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(20);//  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:walldatasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000datasource.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*/@Beanpublic 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*/@Beanpublic 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);//配置缺省的数据源// 默认数据源配置 DefaultTargetDataSourcedynamicDataSource.setDefaultTargetDataSource(dataSource());Map<Object, Object> targetDataSources = new HashMap<Object, Object>();//额外数据源配置 TargetDataSourcestargetDataSources.put("mainDataSource", dataSource());dynamicDataSource.setTargetDataSources(targetDataSources);return dynamicDataSource;}@Beanpublic 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");//手动配置mybatis的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<String> contextHolder = new ThreadLocal<String>();// 调用此方法,切换数据源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<Object, Object> dynamicTargetDataSources;private Object dynamicDefaultTargetDataSource;@Overrideprotected Object determineCurrentLookupKey() {String datasource = DBContextHolder.getDataSource();if (!StringUtils.isEmpty(datasource)) {Map<Object, Object> 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;}@Overridepublic void setTargetDataSources(Map<Object, Object> 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(1); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时druidDataSource.setMaxActive(20); //最大连接池数量druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象druidDataSource.setMinIdle(5); //最小连接池数量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:walldruidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000druidDataSource.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赋值给父类的TargetDataSourcessuper.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<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources;if (dynamicTargetDataSources2.containsKey(datasourceid)) {Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();for (DruidDataSource l : druidDataSourceInstances) {if (datasourceid.equals(l.getName())) {dynamicTargetDataSources2.remove(datasourceid);DruidDataSourceStatManager.removeDataSource(l);setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSourcessuper.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;}}@Overridepublic 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<Object, Object> getDynamicTargetDataSources() {return dynamicTargetDataSources;}/*** @param dynamicTargetDataSources*            the dynamicTargetDataSources to set*/public void setDynamicTargetDataSources(Map<Object, Object> 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<Object, Object> 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<DataSource> get();}

DBChangeService.java:

import com.testdb.dbsource.pojo.DataSource;
import java.util.List;/*** @Author : JCccc* @CreateTime : 2019/10/22* @Description :**/public interface DBChangeService {List<DataSource> 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 {@AutowiredDataSourceMapper dataSourceMapper;@Autowiredprivate DynamicDataSource dynamicDataSource;@Overridepublic List<DataSource> get() {return dataSourceMapper.get();}@Overridepublic boolean changeDb(String datasourceId) throws Exception {//默认切换到主数据源,进行整体资源的查找DBContextHolder.clearDataSource();List<DataSource> 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<User> queryUserInfo();}

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

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.testdb.dbsource.mapper.UserMapper"><!--查询所有用户信息--><select id="queryUserInfo" resultType="com.testdb.dbsource.pojo.User">select *from user</select></mapper>

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

ps:
DruidDBConfig.java

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

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

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><setting name="useGeneratedKeys" value="true"/><setting name="useColumnLabel" value="true"/><setting name="mapUnderscoreToCamelCase" value="true"/></settings>
</configuration>

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

创建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 {@Autowiredprivate DBChangeService dbChangeServiceImpl;@AutowiredUserService userService;/*** 查询所有* @return*/@GetMapping("/test")public  String test() throws Exception {//切换到数据库dbtest2String datasourceId="dbtest2";dbChangeServiceImpl.changeDb(datasourceId);List<User> userList= userService.queryUserInfo();System.out.println(userList.toString());//再切换到数据库dbtest3dbChangeServiceImpl.changeDb("dbtest3");List<User> userList3= userService.queryUserInfo();System.out.println(userList3.toString());//切回主数据源DBContextHolder.clearDataSource();return "ok";}}

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

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

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

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

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

补充该实战教学的事务相关介绍配置和介绍:

实现动态数据源事务,找到该篇项目实例中的DruidDBConfig.java ,

将 我们实现的动态数据源加载类DynamicDataSource 添加到数据事务管理器里:

    @Beanpublic DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {return new DataSourceTransactionManager(dynamicDataSource);}

事务测试  (单个数据源的事务)

接着,给我们的新增User方法里面,故意搞个异常出来,测试下回滚是否生效:

在不指定任何异常时,使用注解事务,默认只对RuntimeException异常生效,所以咱们简单点,在啥时候想回滚,咱们就手动丢个RuntimeException异常出来。

写个测试接口,给db2 插入一条user信息,看看事务回滚情况:

    /*** 添加* @return*/@GetMapping("/addTest")public  String addTest() throws Exception {//切换到数据库dbtest2String datasourceId="dbtest2";dbChangeServiceImpl.changeDb(datasourceId);User user2=new User();user2.setUserName("db2用户");user2.setAge("11");userService.insertUser(user2);//切回主数据源DBContextHolder.clearDataSource();return "ok";}

调用该接口,可以看到在未抛出异常时,可以看到插入根据影响行数,提示是成功了,但后面咱们抛异常触发了事务回滚:

看下数据库没出现新增的数据,事务回滚成功:

事务测试  (多个数据源的事务)

可以上图这个场景,就是测试切换不同数据源的时候,目前这两个数据源的插入方法都开启了事务,我们把test2的插入User方法加了故意抛出异常触发事务的代码,看看对于不同数据源事务触发情况:

看看调用该接口,控制台的输出:

再看看数据库情况,

test2数据库,

test3数据库,

可以看到在多个数据源的时候,还是只有单独的数据自己的事务起作用了(怎么解决这种情况? 文章末尾有介绍)。

如果只是用于主从数据库两个数据源的业务场景,那么该篇非常适合使用,因为只需保证主的事务,从库会同步数据。
而且如果仅仅是为了满足主从/读写的场景,大可不必从数据库读取数据源,使用AOP方式读取配置文件的数据源即可满足业务场景,也就是参考我这篇:https://blog.csdn.net/qq_35387940/article/details/100122788

那该篇的动态数据源实战适合哪些场景呢?

一.业务场景需要 很多个不同数据源,进行数据获取,不仅仅是两个,这样一来在配置文件配置是基本不可取的。

二.进行多从数据源数据获取,加上逻辑分析筛选后, 插入或更新 某个数据库,只需为该数据库事务进行负责。

例如我的使用场景:

公司各个小项目很多a,b,c,d,e,都用着自己项目的数据库。

而新项目接口需求,收到第三方的调用后,需要到a,b,c,d,e系统的数据库里读取出不同的核心数据,然后进行业务逻辑分析,然后生成新的订单,插入到新项目的主数据库里,并返回结果给第三方,我只需要对一个数据进行事务管理。

那如果就是想多个数据源事务可以一块回滚呢? 难道就没解决方案了吗?

很可以,就是需要有这种精神,不管你目前的业务场景有没有触及。值得敬佩的你,请看:

并不然,使用JTA分布式事务即可解决。
请看我这篇:

Springboot 整合druid+mybatis+jta分布式事务+多数据源aop注解动态切换 (一篇到位)

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

PS: 这篇文章我很久前写的,日常搬砖也比较忙,但是从评论里包括我其他的动态切换数据源文章里面得知,大家使用mybatis-plus来开发还是比较多的。

不了解mybatis-plus的可以看看我这篇简单的入门使用(https://blog.csdn.net/qq_35387940/article/details/103381713)。

回归这个问题,那么在该篇文章里,如果使用 mybatis-plus 的话,为了保证使用,需要做什么调整呢?

1. 找到配置文件

2.把本文原先的 SqlSessionFactoryBean 调整为使用  MybatisSqlSessionFactoryBean ,其实也就是说换一下sqlSessionFactory()这个方法的代码即可:

代码:

    @Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dynamicDataSource());sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));// 设置mybatis的主配置文件ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");sqlSessionFactoryBean.setConfigLocation(mybatisConfigXml);return sqlSessionFactoryBean.getObject();}

ok,我们来简单写个接口测试一下,

这里新建一个Mapper继承mybatis-plus里面的BaseMapper,service层我就省略了。

然后写个测试接口,从主数据源分别切换到db2和db3 ,分别插入一下数据:

import com.testdb.dbsource.dbconfig.DBContextHolder;
import com.testdb.dbsource.pojo.User;
import com.testdb.dbsource.service.DBChangeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Author : JCccc* @CreateTime : 2020/10/10* @Description :**/@RestController
public class UserPlusController {@Autowiredprivate DBChangeService dbChangeServiceImpl;@AutowiredUserPlusMapper userMapper;@GetMapping("/testPlus")public void testPlus() throws Exception {User User2 = new User();User2.setUserName("TEST insert db 2");User2.setAge("2");//切换到数据库dbtest2String datasourceId2="dbtest2";dbChangeServiceImpl.changeDb(datasourceId2);int effectNum2 = userMapper.insert(User2);System.out.println("db2添加后的影响行数:"+effectNum2);//切换到数据库dbtest3String datasourceId3="dbtest3";dbChangeServiceImpl.changeDb(datasourceId3);User User3 = new User();User3.setUserName("TEST insert db 3");User3.setAge("3");int effectNum3 = userMapper.insert(User3);System.out.println("db3添加后的影响行数:"+effectNum3);//切回主数据源DBContextHolder.clearDataSource();}}

调用接口,控制台打印:

然后是数据库分别都有了数据:

ok,那这个对于mybatis-plus的使用补充就到这。

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

  1. springBoot学习(二)配置环境动态切换和部分注解的运用

    springBoot配置环境动态切换 建立第一个配置文件(springBoot默认读取的文件)application.properties test.name=default test.default ...

  2. springboot整合poi读取数据库数据和图片动态导出excel

    springboot整合poi读取数据库数据和图片动态导出excel 第一次操作 话不多说就直接上代码 实现代码 需要的依赖 <dependency><groupId>org. ...

  3. golang 读取 ini配置信息

    package main //BY: 29295842@qq.com //这个有一定问题   如果配置信息里有中文就不行 //[Server] ;MYSQL配置 //Server=localhost  ...

  4. Spring多数据源配置以及动态切换实现

    Spring多数据源配置以及动态切换实现 问题描述 一:首先是配置数据源 1.使用xml的bean节点来配置 2.使用yml配置+java代码实现配置 二:动态数据源 三:使用AOP切面实现动态数据源 ...

  5. SpringCloud Alibaba - Nacos 作为配置中心 读取Properties配置信息

    SpringCloud Alibaba是阿里巴巴致力于对微服务的管理.配置.注册等一整套的解决方案. 简介 Nacos 提供用于存储配置和其他元数据的 K-V 存储,为分布式系统中的外部化配置提供服务 ...

  6. PhoneGap(Cordova)通过插件读取android配置信息

    本文的场景是,通过phonegap编写的程序中,使用jsonp与服务端通信.而程序自动更新是通过java代码与服务端通信.从而导致了服务端的地址在js中维护了一份,在android的string.xm ...

  7. //MySQL核心技术// 数据库的介绍、MySQL-5.5.15安装包以及详细安装教程、卸载注册表、MySQL服务的启动与停止、登录与退出

    尚硅谷MySQL核心技术-李玉婷 1.开发者使用的数据库排名: 来源于调查数据: MySQL SQL Server SQLite PostgreSQL MongDB Oracle Redis Cass ...

  8. Spring Boot 如何动态切换多数据源?

    大约在19年的这个时候,老同事公司在做医疗系统,需要和HIS系统对接一些信息,比如患者.医护.医嘱.科室等信息.但是起初并不知道如何与HIS无缝对接,于是向我取经. 最终经过讨论采用了视图对接的方式, ...

  9. spring boot多数据源动态切换, 多数据源事务管理

    1 . 项目目标 实现 不同数据源的切换 (使用AbstractRoutingDataSource) 不同数据源之间,事物管理,多个数据源之间同时commit或者同时rollback 兼容不同的连接池 ...

最新文章

  1. exchange 2003配置ASSP 反垃圾邮件
  2. 干货丨【看图识算法】这是你见过最简单的 “算法说明书”
  3. python基础知识面试题-Python 基础面试题总结
  4. 常用Git命令清单。
  5. 数字调制2ASK误码率分析matlab实现
  6. Android中用URL模拟一个简单的图片加载器
  7. MySQL备份/还原 Unknown storage engine 'InnoDB'
  8. bzoj 1015 [JSOI2008]星球大战starwar
  9. python find next_bitset中_Find_first()与_Find_next()函数
  10. 财务女,30岁无情被辞:想给财务提个醒!!
  11. 一文带你彻底了解电子灌封(灌胶)工艺技术
  12. No ip domain-lookup和Logging synchronous和Exec-timeout 0 0
  13. Windows Server 2016 (Updated Feb 2018) (x64)下载
  14. IDEA怎样自定义 Touch Bar
  15. 基于51单片机的电子记分牌的设计
  16. Rxjava(2.操作符)
  17. 曹胜欢,java那些事儿
  18. HelloWord代码
  19. 算符优先算法java实现,算符优先算法
  20. node-red教程3.3 file控件介绍

热门文章

  1. python打印星号菱形_Python打印“菱形”星号代码方法
  2. 机器学习-有监督学习-分类算法:决策树算法【CART树:分类树(基于信息熵;分类依据:信息增益、信息增益率、基尼系数)、回归树(基于均方误差)】【损失函数:叶节点信息熵和】【对特征具有很好的分析能力】
  3. 公共交通类app用户对金融理财产品的使用场景有哪些?
  4. H3C交换机bootroom菜单清除Console密码
  5. c++编程题 求数列的和
  6. 20120316过去的一些url
  7. android回到桌面的方法
  8. MongoDB的地理位置搜索GeoSearch使用方法探索
  9. 小米路由器mini刷机过程/U盘刷系统
  10. Qt开发的上位机 硬件:固高八轴运动控制卡,海康威视相机,金橙子板卡,喷码机