SpringBoot3多数据源动态切换

demo使用的时SpringBoot3.x、JDK17、MybatisPlus3.5.x、MySQL8

从数据中加载数据源
在这里插入图片描述
定义接口,指定数据源,从不同数据库获取数据
在这里插入图片描述
创建数据源表,用于指定不同数据源,程序自动动态获取
在这里插入图片描述

项目版本依赖关系

demo中所用到的工具以及版本号如下:

pom文件

<?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>cn.molu</groupId><artifactId>mgzyf-api</artifactId><version>2.4.1</version><description>基于 Java 17 + SpringBoot 3 。</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.5</version> <!-- lookup parent from repository --><relativePath/></parent><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><hutool.version>5.8.15</hutool.version><mysql.version>8.0.28</mysql.version><druid.version>1.2.16</druid.version><mybatis-plus.version>3.5.3.1</mybatis-plus.version><lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version></properties><dependencies><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><!--编译测试环境,不打包在lib--><scope>provided</scope></dependency><!-- 允许使用Lombok的Java Bean类中使用MapStruct注解 (Lombok 1.18.20+) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>${lombok-mapstruct-binding.version}</version><scope>provided</scope></dependency><!--    hutool工具包    --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency><!-- 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.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><!--    阿里druid工具包    --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.40</version></dependency><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!-- 自定义配置类支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

一、配置文件配置类型

1.1、配置文件配置

配置两个数据源:tets01 数据库和 test02 数据库
默认使用 master 主数据源,切换时只需要配置 @DataSource 注解来切换即可

application.yml

server:port: 8989spring:jackson:## 默认序列化时间格式date-format: yyyy-MM-dd HH:mm:ss## 默认序列化时区time-zone: GMT+8datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverdruid:# 主数据源(test01数据库)master:url: jdbc:mysql://127.0.0.1:3306/test01?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=trueusername: xxxpassword: 123driver-class-name: com.mysql.cj.jdbc.Driver# 从数据源(test02数据库)slave:# 是否开启从库(多数据源情况下,请打开)enable: true# 如果时SQL server、Oracle等数据库,这里需要改成对应的驱动配置url: jdbc:mysql://127.0.0.1:3306/test02?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=trueusername: xxxpassword: 123driver-class-name: com.mysql.cj.jdbc.Driverinitial-size: 15min-idle: 15max-active: 200max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: ""test-while-idle: truetest-on-borrow: falsetest-on-return: falsepool-prepared-statements: falseconnection-properties: false

1.2、数据源配置类

读取 application 配置文件中的数据源信息,并由Spring统一管理,以便后续使用

DataSourceConfig

import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;/*** @author 陌路* @apiNote 设置数据源* @date 2023/12/31 10:16* @tool Created by IntelliJ IDEA*/
@Configuration
public class DataSourceConfig {/*通过配置类,将配置文件中配置的数据库信息转换成datasource,并添加到DynamicDataSource中,同时通过@Bean将DynamicDataSource注入Spring中进行管理,后期在进行动态数据源添加时,会用到。*//*** 配置主数据源,默认使用该数据源,并且主数据源只能配置一个** @return DataSource* @description 该数据源是在application配置文件master中所配置的*/@Bean@ConfigurationProperties("spring.datasource.druid.master")public DataSource masterDataSource() {return DruidDataSourceBuilder.create().build();}/*** 配置从数据源,可以配置多个(目前只配置了一个test02)** @return DataSource* @description 该数据源是在application配置文件slave中所配置的*/@Bean@ConfigurationProperties("spring.datasource.druid.slave")public DataSource slaveDataSource() {return DruidDataSourceBuilder.create().build();}/*** 配置动态数据源的核心配置项** @return DynamicDataSource*/@Primary@Bean(name = "dynamicDataSource")public DynamicDataSource createDynamicDataSource() {Map<Object, Object> dataSourceMap = new HashMap<>();// 默认的数据源(主数据源)DataSource defaultDataSource = masterDataSource();// 配置主数据源,默认使用该数据源,并且主数据源只能配置一个dataSourceMap.put(DataSourceType.MASTER.name(), defaultDataSource);// 配置从数据源,可以配置多个(目前只配置了一个test02)dataSourceMap.put(DataSourceType.SLAVE.name(), slaveDataSource());// 配置动态数据源,默认使用主数据源,如果有从数据源配,则使用从数据库中读取源,并加载到dataSourceMap中return new DynamicDataSource(defaultDataSource, dataSourceMap);}
}

1.3、配置数据源枚举类

该枚举类主要用于数据源的指定,使用 MASTER 库还是使用 SLAVE 库,
如果有多个数据源,可以在这里继续指定即可

DataSourceType

/*** @author 陌路* @apiNote 动态数据源类型* @date 2023/12/31 10:23* @tool Created by IntelliJ IDEA*/
public enum DataSourceType {// 注意:枚举项要和 DataSourceConfig 中的 createDynamicDataSource()方法dataSourceMap的key保持一致/*** 主库*/MASTER,/*** 从库*/SLAVE,
}

1.4、线程局部变量

线程局部变量,不同于普通变量,每个线程都有自己的副本,互不影响。
多线程情况下也能保证数据源能够有序的切换,而不会造成数据的混乱

DynamicDataSourceContextHolder

import lombok.extern.slf4j.Slf4j;/*** @author 陌路* @apiNote 线程局部变量,不同于普通变量,每个线程都有自己的副本,互不影响。* @date 2023/12/31 10:05* @tool Created by IntelliJ IDEA* @description 创建一个类用于实现ThreadLocal,主要是通过get,set,remove方法来获取、设置、删除当前线程对应的数据源。*/
@Slf4j
public class DynamicDataSourceContextHolder {//此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();/*** 设置数据源** @param dataSourceName 数据源名称*/public static void setDataSource(String dataSourceName) {log.info("切换数据源到:{}", dataSourceName);DATASOURCE_HOLDER.set(dataSourceName);}/*** 获取当前线程的数据源** @return 数据源名称*/public static String getDataSource() {return DATASOURCE_HOLDER.get();}/*** 删除当前数据源*/public static void removeDataSource() {log.info("删除当前数据源:{}", getDataSource());DATASOURCE_HOLDER.remove();}
}

1.5、添加切换的数据源(核心)

定义一个动态数据源类实现AbstractRoutingDataSource,通过determineCurrentLookupKey方法与上述实现的ThreadLocal类中的get方法进行关联,实现动态切换数据源。

切换数据源后,具体查询哪个数据库,由此类决定

DynamicDataSource

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Objects;/*** @author 陌路* @apiNote 实现动态数据源,根据AbstractRoutingDataSource路由到不同数据源中* @date 2023/12/31 10:08* @tool Created by IntelliJ IDEA* @description 定义一个动态数据源类实现AbstractRoutingDataSource,通过determineCurrentLookupKey方法与上述实现的ThreadLocal类中的get方法进行关联,实现动态切换数据源。*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {/*代码中,还实现了一个动态数据源类的构造方法,主要是为了设置默认数据源,以及以Map保存的各种目标数据源。其中Map的key是设置的数据源名称,value则是对应的数据源(DataSource)*/// 数据源列表,多数据源情况下,具体使用哪一个数据源,由此获取private final Map<Object, Object> targetDataSourceMap;/*** 构造方法,设置默认数据源和目标多数据源** @param defaultDataSource 默认主数据源,只能有一个* @param targetDataSources 从数据源,可以是多个*/public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSources) {super.setDefaultTargetDataSource(defaultDataSource);super.setTargetDataSources(targetDataSources);this.targetDataSourceMap = targetDataSources;// 配置完成后,更新数据源配置列表,将TargetDataSources中的连接信息放入resolvedDataSources管理super.afterPropertiesSet();}/*** 动态数据源的切换(核心)* 决定使用哪个数据源** @return Object*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSource();}/*** 校验数据源是否存在** @param key 数据源保存的key* @return 返回结果,true:存在,false:不存在*/public boolean existsDataSource(String key) {return Objects.nonNull(this.targetDataSourceMap) && Objects.nonNull(this.targetDataSourceMap.get(key));}
}

1.6、数据源注解类

此注解可以配置在类上,也可以配置在方法上
优先级:方法 > 类
当方法上加了此注解时,类上配置的注解将会失效
当类中所有方法都没有此注解,而类上存在时,那么该类中所有方法都将使用该类上的数据源
方法和类上都不添加该注解时,默认使用 master 数据源

DataSource

import java.lang.annotation.*;/*** 自定义多数据源切换注解* <p>* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准*/
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DataSource {/*** 切换数据源名称(默认是主数据源test01)*/public DataSourceType value() default DataSourceType.MASTER;
}

1.7、动态数据源切面(核心)

是否需要切换数据源,或者具体使用哪个数据源,由此类通过解析 DataSource 注解来决定
不添加或者解析不到该注解时,则使用默认的数据源(master)

DataSourceAspect

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.aspectj.lang.reflect.MethodSignature;
import java.util.Objects;/*** @author 陌路* @apiNote 多数据源切换* @date 2023/12/31 13:06* @tool Created by IntelliJ IDEA*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {// 配置织入点,为DataSource 注解@Pointcut("@annotation(cn.molu.system.common.datasource.DataSource)"+ "|| @within(cn.molu.system.common.datasource.DataSource)")public void dsPointCut() {}/*** * 环绕通知** @param point 切入点* @return Object* @throws Throwable 异常*/@Around("dsPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {DataSource dataSource = getDataSource(point);if (Objects.nonNull(dataSource) && StringUtils.isNotEmpty(dataSource.value().name())) {// 将用户自定义配置的数据源添加到线程局部变量中DynamicDataSourceContextHolder.setDataSource(dataSource.value().name());}try {return point.proceed();} finally {// 在执行完方法之后,销毁数据源 DynamicDataSourceContextHolder.removeDataSource();}}/*** 获取需要切换的数据源* 注意:顺序为:方法>类,方法上加了注解后类上的将不会生效* 注意:当类上配置后,方法上没有该注解,那么当前类中的所有方法都将使用类上配置的数据源*/public DataSource getDataSource(ProceedingJoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature();// 从方法上获取注解DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);// 方法上不存在时,再从类上匹配return Objects.nonNull(dataSource) ? dataSource : AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);}
}

1.8、启动类修改

上述中,我们自定义了数据源的配置,这里据需要将 DataSourceAutoConfiguration 自动配置的数据源排除,否则项目启动时可能会出现异常,导致项目无法正常运行

SystemApplication

// 排除数据源自动配置,使用自定义数据源,否则会出现循环依赖报错
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SystemApplication {public static void main(String[] args) {SpringApplication.run(SystemApplication.class, args);log.info("项目启动成功:http://127.0.0.1:8989");}
}

到此,配置类型的多数据源的切换就完成了

不足和缺陷

  • 每次添加数据源时都需要在 application 配置文件中添加
  • 线上环境时,每次都需要替换新的 application 文件
  • 当数据库密码修改或者切换服务器时,维护成本高,不够便利

以上不足和缺陷,将在以下内容中逐一解决

二、数据库动态获取类型

数据源信息配置在数据库中,便于维护

在以上代码的基础上,进行修改,所新增的代码以及变更的代码将会给出具体的说明

在以上代码的基础上新增:SysDbInfo、LoadDataSourceRunner【2个新增】
修改变更的类:DataSourceConfig、DynamicDataSource【2个改动】
以下类不做任何改动:DynamicDataSourceContextHolder、DataSourceType、DataSourceAspect、DataSource、SystemApplication【5个未作改动】

修改配置文件

改动内容:仅去除了从数据库的配置信息(不再需要在配置文件中配置从库数据)

server:port: 8989spring:jackson:## 默认序列化时间格式date-format: yyyy-MM-dd HH:mm:ss## 默认序列化时区time-zone: GMT+8datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverdruid:master:url: jdbc:mysql://127.0.0.1:3306/test01?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=trueusername: xxxpassword: 123driver-class-name: com.mysql.cj.jdbc.Driverslave:# 是否开启从库(多数据源情况下,请打开,并且将配置信息配置到sys_db_info表中,项目启动时会自动加载)enable: trueinitial-size: 15min-idle: 15max-active: 200max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: ""test-while-idle: truetest-on-borrow: falsetest-on-return: falsepool-prepared-statements: falseconnection-properties: false

2.1、数据源实体类

SysDbInfo

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;/*** @author 陌路* @apiNote 数据源实体类* @date 2023/12/31 10:29* @tool Created by IntelliJ IDEA*/
@Data
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@Accessors(chain = true)
@TableName(value = "sys_db_info")
public class SysDbInfo implements Serializable {@Serial@TableField(exist = false)private static final long serialVersionUID = 8115921127536664152L;
/*
-- 建表语句
CREATE TABLE `sys_db_info` (`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键Id',`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库URL',`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '用户名',`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '密码',`driver_class_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库驱动',`db_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库名称',`db_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库key,即保存Map中的key(保证唯一,并且和DataSourceType中的枚举项保持一致,包括大小写)',`status` int NOT NULL DEFAULT '0' COMMENT '是否停用:0-正常,1-停用',`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注说明',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `UNI_DB_INFO_DBKEY` (`db_key`) USING BTREE COMMENT '数据库的key必须唯一,并且要和DataSourceType中的枚举项保持一致'
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;*//*** 数据库地址*/private String url;/*** 数据库用户名*/private String username;/*** 密码*/private String password;/*** 数据库驱动*/private String driverClassName;/*** 数据库key,即保存Map中的key(保证唯一)* 定义一个key用于作为DynamicDataSource中Map中的key。* 这里的key需要和DataSourceType中的枚举项保持一致*/private String dbKey;/*** 数据库名称*/private String dbName;/*** 是否停用:0-正常,1-停用*/private Integer status;/*** 备注*/private String remark;
}

以下是实体类所对应的SQL语句:

CREATE TABLE `sys_db_info` (`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键Id',`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库URL',`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '用户名',`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '密码',`driver_class_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库驱动',`db_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库名称',`db_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '数据库key,即保存Map中的key(保证唯一,并且和DataSourceType中的枚举项保持一致,包括大小写)',`status` int NOT NULL DEFAULT '0' COMMENT '是否停用:0-正常,1-停用',`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注说明',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `UNI_DB_INFO_DBKEY` (`db_key`) USING BTREE COMMENT '数据库的key必须唯一,并且要和DataSourceType中的枚举项保持一致'
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;-- 添加数据库配置信息(此处按照自己的需求配置对应的数据库即可)
INSERT INTO `sys_db_info` (`id`, `url`, `username`, `password`, `driver_class_name`, `db_name`, `db_key`, `status`, `remark`) VALUES (1, 'jdbc:mysql://127.0.0.1:3306/test02?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true', 'xxx', '123', 'com.mysql.cj.jdbc.Driver', 'test02', 'SLAVE', 0, '连接test02数据库');
INSERT INTO `sys_db_info` (`id`, `url`, `username`, `password`, `driver_class_name`, `db_name`, `db_key`, `status`, `remark`) VALUES (2, 'jdbc:mysql://127.0.0.1:3306/test03?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true', 'xxx', '123', 'com.mysql.cj.jdbc.Driver', 'test03', 'SLAVE2', 1, '链接test03数据库');

2.2、初始化加载数据源

项目启动时,执行此代码,访问数据库加载数据库中配置的数据源信息

LoadDataSourceRunner

import cn.hutool.core.util.StrUtil;
import cn.molu.system.mapper.SysDbInfoMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;/*** @author 陌路* @apiNote 查询数据源* @date 2023/12/31 10:41* @tool Created by IntelliJ IDEA* CommandLineRunner 项目启动时执行*/
@Slf4j
@Component
public class LoadDataSourceRunner implements CommandLineRunner {/*** 是否启用从库多数据源配置*/@Value("${spring.datasource.druid.slave.enable}")private boolean enabled;@Resourceprivate DynamicDataSource dynamicDataSource;@Resourceprivate SysDbInfoMapper dbInfoMapper;/*** 项目启动时加载数据源*/@Overridepublic void run(String... args) {if (enabled) return;refreshDataSource();}/*** 刷新数据源*/public void refreshDataSource() {List<SysDbInfo> dbInfos = dbInfoMapper.selectList(new LambdaQueryWrapper<SysDbInfo>().eq(SysDbInfo::getStatus, 0));if (CollectionUtils.isEmpty(dbInfos)) return;List<SysDbInfo> ds = new ArrayList<>();log.info("====开始加载数据源====");for (SysDbInfo info : dbInfos) {if (StrUtil.isAllNotBlank(info.getUrl(), // 数据库连接地址info.getDriverClassName(), // 数据库驱动info.getUsername(), // 数据库用户名info.getPassword(), // 数据库密码info.getDbKey() // 数据源key)) {ds.add(info);log.info("加载到数据源 ---> dbName:{}、dbKey:{}、remark:{}", info.getDbName(), info.getDbKey(), info.getRemark());}}dynamicDataSource.createDataSource(ds);log.info("====数据源加载完成====");}
}

2.3、数据源配置类(改动)

该配置类稍作改动,去除了从配置文件中加载从数据源(slave),只加载主数据源(master)

DataSourceConfig

import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;/*** @author 陌路* @apiNote 设置数据源* @date 2023/12/31 10:16* @tool Created by IntelliJ IDEA*/
@Configuration
public class DataSourceConfig {/*通过配置类,将配置文件中的配置的数据库信息转换成datasource,并添加到DynamicDataSource中,同时通过@Bean将DynamicDataSource注入Spring中进行管理,后期在进行动态数据源添加时,会用到。*//*** 配置主数据源,默认使用该数据源,并且主数据源只能配置一个** @return DataSource* @description 该数据源是在application配置文件master中所配置的*/@Bean@ConfigurationProperties("spring.datasource.druid.master")public DataSource masterDataSource() {return DruidDataSourceBuilder.create().build();}/*** 配置从数据源,可以配置多个* <p>现在已经改为数据库配置,如果有多个数据源,请在sys_db_info表中添加即可,* 不需要在application中配置(此方法已注释,配置也不会生效)<p/>** @return DataSource* @description 该数据源是在application配置文件slave中所配置的*/
//    @Bean
//    @ConfigurationProperties("spring.datasource.druid.slave")public DataSource slaveDataSource() {return DruidDataSourceBuilder.create().build();}/*** 配置动态数据源的核心配置项** @return DynamicDataSource*/@Primary@Bean(name = "dynamicDataSource")public DynamicDataSource createDynamicDataSource() {Map<Object, Object> dataSourceMap = new HashMap<>();// 默认数据源DataSource defaultDataSource = masterDataSource();// 配置主数据源,默认使用该数据源,并且主数据源只能配置一个dataSourceMap.put(DataSourceType.MASTER.name(), defaultDataSource);// 配置从数据源,可以配置多个,目前已注释,不需要application中配置,如果有多个数据源,请在sys_db_info表中添加即可
//        dataSourceMap.put(DataSourceType.SLAVE.name(), slaveDataSource());// 配置动态数据源,默认使用主数据源,如果有从数据源配,则使用从数据库中读取源,并加载到dataSourceMap中return new DynamicDataSource(defaultDataSource, dataSourceMap);}
}

2.4、添加读取到的数据源

改动添加切换的数据源类,将数据库中读取出来的数据源列表逐一添加,以便后续使用

DynamicDataSource

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Objects;/*** @author 陌路* @apiNote 实现动态数据源,根据AbstractRoutingDataSource路由到不同数据源中* @date 2023/12/31 10:08* @tool Created by IntelliJ IDEA* @description 定义一个动态数据源类实现AbstractRoutingDataSource,通过determineCurrentLookupKey方法与上述实现的ThreadLocal类中的get方法进行关联,实现动态切换数据源。*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {/*代码中,还实现了一个动态数据源类的构造方法,主要是为了设置默认数据源,以及以Map保存的各种目标数据源。其中Map的key是设置的数据源名称,value则是对应的数据源(DataSource)*/// 数据源列表,多数据源情况下,具体使用哪一个数据源,由此获取private final Map<Object, Object> targetDataSourceMap;/*** 构造方法,设置默认数据源和目标多数据源** @param defaultDataSource 默认主数据源,只能有一个* @param targetDataSources 从数据源,可以是多个*/public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSources) {super.setDefaultTargetDataSource(defaultDataSource);super.setTargetDataSources(targetDataSources);this.targetDataSourceMap = targetDataSources;// 配置完成后,更新数据源配置列表,将TargetDataSources中的连接信息放入resolvedDataSources管理
//        super.afterPropertiesSet();}/*** 动态数据源的切换(核心)* 决定使用哪个数据源** @return Object*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSource();}/*** 添加数据源信息** @param dataSources 数据源实体集合*/public void createDataSource(List<SysDbInfo> dataSources) {try {if (CollectionUtils.isNotEmpty(dataSources)) {for (SysDbInfo ds : dataSources) {//校验数据库是否可以连接Class.forName(ds.getDriverClassName());DriverManager.getConnection(ds.getUrl(), ds.getUsername(), ds.getPassword());//定义数据源DruidDataSource dataSource = new DruidDataSource();BeanUtils.copyProperties(ds, dataSource);//申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用dataSource.setTestOnBorrow(true);//建议配置为true,不影响性能,并且保证安全性。//申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。dataSource.setTestWhileIdle(true);//用来检测连接是否有效的sql,要求是一个查询语句。dataSource.setValidationQuery("select 1 ");dataSource.init();// 将数据源放入Map中,key为数据源名称,要和DataSourceType中的枚举项对应,包括大小写,并且保证唯一this.targetDataSourceMap.put(ds.getDbKey(), dataSource);}// 更新数据源配置列表,这里主要是从数据源super.setTargetDataSources(this.targetDataSourceMap);// 将TargetDataSources中的连接信息放入resolvedDataSources管理super.afterPropertiesSet();}} catch (ClassNotFoundException | SQLException e) {log.error("---解析数据源出错---:{}", e.getMessage());}}/*** 校验数据源是否存在** @param key 数据源保存的key* @return 返回结果,true:存在,false:不存在*/public boolean existsDataSource(String key) {return Objects.nonNull(this.targetDataSourceMap) && Objects.nonNull(this.targetDataSourceMap.get(key));}
}

2.5、SysDbInfo相关

用于向数据库查询数据

import cn.molu.system.common.datasource.SysDbInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface SysDbInfoMapper extends BaseMapper<SysDbInfo> {}

到此,动态数据源相关就完成了,可以启动项目试一下

注意说明:

1、master主数据库一定要有,并且可以连接,否则项目无法启动
2、slave从数据库可以有也可以没有,也可以时很多个,
3、slave从数据库可以是其他数据库:Oracle、SQL serve等,对应修改配置即可
4、DataSourceType枚举中的枚举项一定要和数据库中配置的dbkey保持一致
5、使用的时候之间使用DataSource注解指定数据源即可

2.6、使用用例

动态数据源的使用

/*** 用户控制器** @author 陌路* @since 2023/12/31*/
@RestController
@RequiredArgsConstructor// 此注解用于指定具体使用哪个数据源
@DataSource(DataSourceType.SLAVE)
@RequestMapping("/api/v1/users")
public class SysUserController {private final SysUserService userService;/*** 用户分页列表* @param queryParams 查询参数* @return 用户分页列表*/@GetMapping("/page") // 使用类上的数据源SLAVEpublic PageResult<UserPageVO> getUserPage(UserPageQuery queryParams) {IPage<UserPageVO> result = userService.getUserPage(queryParams);return PageResult.success(result);}/*** 删除用户** @param ids 用户ID,多个以英文逗号(,)分割* @return 删除结果*/@DeleteMapping("/{ids}")@DataSource(DataSourceType.MASTER) // 在方法上,则类上的配置失效public Result<Boolean> deleteUsers(@PathVariable String ids) {boolean result = userService.deleteUsers(ids);return Result.judge(result);}}

不切换数据源,使用默认
不加@DataSource注解,使用默认数据源

```java
/*** 用户控制器** @author 陌路* @since 2023/12/31*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
public class SysUserController {private final SysUserService userService;/*** 用户分页列表* @param queryParams 查询参数* @return 用户分页列表*/@GetMapping("/page")public PageResult<UserPageVO> getUserPage(UserPageQuery queryParams) {IPage<UserPageVO> result = userService.getUserPage(queryParams);return PageResult.success(result);}/*** 删除用户** @param ids 用户ID,多个以英文逗号(,)分割* @return 删除结果*/@DeleteMapping("/{ids}")public Result<Boolean> deleteUsers(@PathVariable String ids) {boolean result = userService.deleteUsers(ids);return Result.judge(result);}}

以上就是配置动态数据源的过程了,可以根据不同需求使用配置文件的形式,或者使用数据库配置的形式

我这里没有将测试过程以及结果贴出来,请自行测试使用。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/597864.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码+视频,手把手教你R语言使用forestploter包绘制单组及双组森林图

森林图在论文中很常见&#xff0c;多用于表示多因素分析中的变量与结果变量的比值效应&#xff0c;可以用图示的方法比较直观的绘制出来。既往我们在文章《R语言快速绘制多因素回归分析森林图&#xff08;1&#xff09;》已经介绍了怎么绘制森林图&#xff0c;但是绘图比较简单…

开启Android学习之旅-2-架构组件实现数据列表及添加(kotlin)

Android Jetpack 体验-官方codelab 1. 实现功能 使用 Jetpack 架构组件 Room、ViewModel 和 LiveData 设计应用&#xff1b;从sqlite获取、保存、删除数据&#xff1b;sqlite数据预填充功能&#xff1b;使用 RecyclerView 展示数据列表&#xff1b; 2. 使用架构组件 架构组…

Python从入门到网络爬虫(内置函数详解)

前言 Python 内置了许多的函数和类型&#xff0c;比如print()&#xff0c;input()等&#xff0c;我们可以直接在程序中使用它们&#xff0c;非常方便&#xff0c;并且它们是Python解释器的底层实现的&#xff0c;所以效率是比一般的自定义函数更有效率。目前共有71个内置函数&…

全球海洋数据 (GLODAP) v2.2023(海洋碳数据产品)

全球海洋数据分析项目 (GLODAP) v2.2023 全球海洋数据分析项目 (GLODAP) v2.2023 代表了海洋生物地球化学瓶数据合成方面的重大进步。此更新主要关注海水无机碳化学&#xff0c;以 GLODAPv2.2022 为基础&#xff0c;包含多项关键增强功能。值得注意的是&#xff0c;增加了 43 …

CISSP 第9章:安全脆弱性、威胁和对策

第九章 安全脆弱性、威胁和对策 9.1 评估和缓解安全脆弱性 9.1 硬件 处理器 执行类型 多任务处理&#xff1a; 同时处理两个或更多任务 多处理&#xff1a; 利用多个处理器完成一个应用程序的处理能力 多程序设计&#xff1a;通过操作系统对单个处理器上的两个任务进行协调&…

Node.js+Express+Mysql实现分页查询

根据记录数总数和分页数获到页总数 function pageCount (totalnum,limit){return totalnum > 0 ? ((totalnum < limit) ? 1 : ((totalnum % limit) ? (parseInt(totalnum / limit) 1) : (totalnum / limit))) : 0; } 接收请求代码 router.get(/api/user/page, asy…

SSCI及SCI撰写|查找文献doi的八大方法

一、前言 (一)文献DOI概念介绍 DOI&#xff08;Digital Object Identifier&#xff09;是一种用于标识数字对象的持久性标识符系统。在学术出版领域&#xff0c;DOI通常用于标识和定位学术文献&#xff0c;包括期刊文章、会议论文、报告等。以下是 DOI 的一些重要特点和介绍&a…

【网络】网络层IP地址和IP数据报的格式

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&am…

【Python小游戏】贪吃蛇丨名侦探柯南(完整代码)

文章目录 写在前面PyGame入门贪吃蛇注意事项写在后面写在前面 本期内容:基于pygame的贪吃蛇小游戏 实验环境 python3.11及以上pycharmpygame安装pygame的命令: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pygamePyGame入门 pygame是一个开源的Python模块,…

iOS手机查看蓝牙底层日志

文章目录 一、需要的环境二、在iOS设备上安装Profiles文件三、安装Xcode四、安装packetLogger五、启动蓝牙数据包分析 参考文档&#xff1a;A New Way to Debug iOS Bluetooth Applications 一、需要的环境 iOS 13 device and cableMac computer/laptopApple Developer Progr…

顶顶通呼叫中心中间件配置指定振铃时间挂断(mod_cti基于FreeSWITCH)

介绍 一般情况默认是振铃60秒挂断&#xff0c;但是如果想振铃10秒就挂断可以根据下方配置方法一步步去配置。 一、通过线路控制振铃时间 打开ccadmin-》点击线路-》点击你需要控制振铃时间的线路-》配置呼叫超时-》点击更新。 二、通过队列外呼控制振铃时间 打开ccadmin-》…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《考虑分布式资源交易的气电综合能源配网系统出清模型》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主的免费专栏栏目《论文与完整程序》 这个标题涉及到气电综合能源配网系统的出清模型&#xff0c;特别强调了对分布式资源交易的考虑。以下是对标题中关键术语的解读&#xff1a; 气电综合能…

paddlehub 文本检测使用

PaddleHub负责模型的管理、获取和预训练模型的使用。 参考&#xff1a;https://github.com/PaddlePaddle/PaddleHub/tree/develop/modules/image/text_recognition/chinese_text_detection_db_server import paddlehub as hub import cv2 # from utils import cv_show import…

《网络是怎样连接的》2.1节图表(自用)

图3.1&#xff1a;协议栈的组成 图3.2&#xff1a;netstat命令查看套接字 上图中每一行就是一个套接字 图3.3&#xff1a;协议栈在浏览器访问DNS服务器与web服务器时的具体工作流程 套接字由协议栈创建 应用程序通过Socket库中的程序组件与协议栈交互 图3.4&#xff1a;TCP头…

【JAVA】深入了解 Java 中的 DelayQueue

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 Java中的DelayQueue是一个强大的工具&#xff0c;常用于处理需要延迟执行的任务或具有过期时间的元素。通过实现Delayed接口&#x…

丰田凯美瑞雾灯改双光透镜解决方案

丰田凯美瑞大灯采用CAN总线控制车灯的近光、远光、日行灯、转向灯信号&#xff0c;无法直接从车灯插头上直接获取近光远光信号。传统改灯需要拆开车灯通过光耦线阻取得近远光开光信号&#xff0c;工序繁琐且不美观。 厦门市创宇致诚电子科技推出一款丰田凯美瑞车灯信号解码器&a…

Halcon粘连木材图像的目标分割计数

Halcon粘连木材图像的目标分割计数 文章目录 Halcon粘连木材图像的目标分割计数1. 提取面积较小区域2. 分割较大区域3. 区域合并与计数 本文以一个实际场景图片为例&#xff0c;介绍阈值处理与形态学计算的应用。案例图像如图所示,图&#xff08;a&#xff09;为输入的原始图像…

MacOS M1/M2 Go Debug 配置

前言 换电脑&#xff0c;Go 环境带来一些麻烦&#xff0c;耽误很多时间&#xff0c;稍作记录。 原始电脑是 Mac 旧款&#xff0c;CPU x86 构型&#xff0c;新电脑 M2&#xff0c;因为旧电脑里本地文件很多&#xff0c;为了简化搬迁&#xff0c;还是用了 Mac 自带的迁移&#x…

Spring的依赖注入(DI)

1.DI 概述&#xff1a;DI&#xff08;Dependency Injection&#xff09;依赖注入&#xff0c;在Spring创建对象的同时&#xff0c;为其属性赋值&#xff0c;称之为依赖注入。 1.1构造函数注入 顾名思义&#xff0c;就是使用类中的构造函数&#xff0c;给成员变量赋值。注意&…

Java ArrayList 面试题

Java ArrayList 面试题 文章目录 Java ArrayList 面试题ArrayList源码分析成员变量构造方法ArrayList源码分析面试题-ArrayList listnew ArrayList(10)中的list扩容几次面试题-如何实现数组和List之间的转换 ArrayList源码分析 分析ArrayList源码主要从三个方面去翻阅&#xf…