前言
多数据源的切换具有十分广泛的应用场景,同时可以简化主从复制、读写分离等方案的实现过程,通过继承AbstractRoutingDataSource并重写相关方法,结合拦截器、AOP以及自定义注解即可实现,但过程比较繁琐。因此可以利用Dynamic-datasource框架轻松实现数据源切换,并且通过框架预留的一些接口,结合前端可以实现动态新增数据源、使用新数据源(后端服务器无需重启,正常运行)以及查询和删除数据源。
一、框架介绍及多数据源切换
1.框架介绍
Dynamic-datasource是一个用于在 Spring 环境中动态切换数据源的框架,它提供了对多数据源的支持,允许在运行时根据不同的业务需求动态切换数据源,广泛应用于多租户应用、分库分表、数据库读写分离等场景。它主要是通过AOP来实现动态切换数据源,即会在数据访问层插入拦截器来拦截数据库操作,通过注解或上下文来指定当前操作应该使用哪个数据源。
主要特点:
- 动态数据源切换:支持在应用运行时根据请求的上下文(如当前用户、请求参数等)动态切换数据源。
- 高效的数据库管理:通过合理的使用数据源,可以优化数据库的性能,尤其在有多个数据源的情况下(例如分库分表或读写分离)。
- 无侵入式:集成后不需要修改现有业务代码,减少对现有系统的影响。
- 支持事务管理:可以在多个数据源之间进行事务管理,保证数据的一致性。
多数据源支持 | MyBatis-Plushttps://baomidou.com/guides/dynamic-datasource/
2.多数据切换
首先需要在Spring项目中引入Dynamic-datasource的相关依赖,取决于你使用的SpringBoot版本
SpringBoot2
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
SpringBoot3
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${version}</version>
</dependency>
如果你没有进行统一版本管理,可以直接指定相应的版本即可,随后需要在application.yml文件中配置多个数据源,具体如下
spring:
datasource:
dynamic:
primary: energydb1
strict: false
datasource:
energydb1:
username: xiaohuaduo
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.106.166:3306/energydb1
energydb2:
username: xiaohuaduo
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/energydb2
这里我配置了两个数据源,一个在本地,一个在远程虚拟机中。其中primary用于指定默认使用的主数据源,energydb1相当于key,数据源的相关配置为value,Dynamic-datasource会维护一个逻辑数据库,用于保存两者之间的映射关系,通过key就能找到对应的value。大家配置完成后在启动项目时可以观察输出,会有对应的数据源加载信息。
因此在进行数据源切换的时候我们需要标明想要使用的数据库对应的key,这就用到了@DS注解,它既可以加在类上也可以加在方法上(注意需要加在service层),如果都有则采用就近原则使用方法上的注解。例如在我的一个service中,其它方法都是写操作,需要走默认的主库energydb1,查询方法需要走从库energydb2,因此只需要在查询方法上添加对应注解即可切换数据源
@DS("energydb2")
@Override
public List<ContractInfo> findAll() {
List<ContractInfo> contractInfoList = contractInfoMapper.findAll();
return contractInfoList;
}
这样在进行数据库查询操作时会被拦截,通过@DS注解中的key决定要操作的数据库,从而实现数据源的切换,因此简单的数据源切换总结下来就是引入依赖,配置数据源,添加注解即可实现。
二、动态新增数据源及使用新数据源
动态新增数据源并使用新数据源是指在后端服务器不停机的状态下进行,这就要求我们不能提前在application.yml中配置数据源,而是在运行过程中去新增。之前我们说过,Dynamic-datasource会维护一个逻辑数据库,因此现在可以拆分为两个问题,一是如何向逻辑数据库中增加新的DB条目,二是如何增加指向新DB的key,接下来我们逐一分析
1.Dynamic-datasource底层源码分析
想要实现动态新增必须能够理解框架底层的实现逻辑,因此简单对其源码进行分析。首先引入依赖后可以看到Dynamic-datassource的扩展包,在这种spring-boot-starter方法中有一个文件是spring.factories,打开后可以看到相关内容
这个配置是Spring Boot项目中的自动配置类的相关设置,@EnableAutoConfiguration注解会根据项目的依赖和配置文件自动配置应用程序的基础设施服务。DynamicDataSourceAutoConfiguration是Dynamic-dataSource框架提供的自动配置类,它会在 Spring Boot启动时被自动装配,用来为你的应用程序配置和管理多个数据源。
接下来我们进入到底层源码去看一下,先来看一下整体结构,主要有定义的注解、AOP、数据源的creator、异常处理、matcher、processor、provider、strategy以及autoconfigure等。
接下来我们进入到自动配置中去看一下具体配置信息,我们重点关注里面的动态数据源自动配置类DynamicDataSourceAutoConfiguration
通过下面的代码,可以看到它通过@Bean的方式注入了一个DataSource实例,这个实例是通过使用DynamicRoutingDataSource创建的,这是一个带有动态路由功能的数据源,我们之前提到过如果自己实现的话,就是需要继承DynamicRoutingDataSource并重写抽象方法。创建数据源后这里设置了它的一些相关属性,我们需要进入到DynamicRoutingDataSource中去分析一下
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider, DynamicDataSourceStrategy dynamicDataSourceStrategy) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(this.properties.getPrimary());
dataSource.setStrict(this.properties.getStrict());
dataSource.setStrategy(dynamicDataSourceStrategy);
dataSource.setProvider(dynamicDataSourceProvider);
dataSource.setP6spy(this.properties.getP6spy());
dataSource.setSeata(this.properties.getSeata());
return dataSource;
}
可以看到DynamicRoutingDataSource中定义的一些方法,如获取当前的所有数据源,它返回的是一个Map集合,包含所有数据源与其对应的key,这里的getDataSource(String ds)方法就是根据传入的key找到对应的数据源,如果key为空就返回默认的主数据源。
public Map<String, DataSource> getCurrentDataSources() {
return this.dataSourceMap;
}
public Map<String, DynamicGroupDataSource> getCurrentGroupDataSources() {
return this.groupDataSources;
}
public DataSource getDataSource(String ds) {
if (StringUtils.isEmpty(ds)) {
return this.determinePrimaryDataSource();
} else if (!this.groupDataSources.isEmpty() && this.groupDataSources.containsKey(ds)) {
log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
return ((DynamicGroupDataSource)this.groupDataSources.get(ds)).determineDataSource();
} else if (this.dataSourceMap.containsKey(ds)) {
log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
return (DataSource)this.dataSourceMap.get(ds);
} else if (this.strict) {
throw new RuntimeException("dynamic-datasource could not find a datasource named" + ds);
} else {
return this.determinePrimaryDataSource();
}
}
好的,既然它提供了获取数据源的方法,有没有提供动态新增数据源的方法呢,我们接着分析DynamicRoutingDataSource中后面的源码。可以看到,它确实提供了新增数据源的方法即addDataSource(),我们需要传入对应的key和创建好的dataSource,如果不存咋则添加到Map中,如果已经存在则返回失败信息。
public synchronized void addDataSource(String ds, DataSource dataSource) {
if (!this.dataSourceMap.containsKey(ds)) {
dataSource = this.wrapDataSource(ds, dataSource);
this.dataSourceMap.put(ds, dataSource);
this.addGroupDataSource(ds, dataSource);
log.info("dynamic-datasource - load a datasource named [{}] success", ds);
} else {
log.warn("dynamic-datasource - load a datasource named [{}] failed, because it already exist", ds);
}
}
Ok,通过上面的源码我们已经知道了应该如何动态新增一个数据源,无需在文件中提前配置,那么有了新数据源,我应该如何增加指向新数据源的key,也就是说在@DS注解中我应该写什么?因为数据源是新增的啊,我在启动之前根本不知道会新增数据源,更不知道注解中该写什么了,想要解决这个问题,还需要进一步分析源码 。
下面代码同样是在DynamicDataSourceAutoConfiguration里面,可以看到这个注入的Bean是一个AnnotationAdvisor,即一个注解切片方法,在里面定义了一个interceptor拦截器,在拦截器中定义了一些具体的处理逻辑,同时设置了dsProcessor,并将其传递给了advisor,那么我们先去拦截器内部看一下,分析它的处理逻辑。
invoke方法是接口中的核心方法,当目标方法被调用时,这个方法会被执行,它的作用是在执行目标方法之前,通过DynamicDataSourceContextHolder.push()方法来设置当前线程使用的数据源,随后执行目标方法即invocation.procee(),在执行完成后再清理当前线程的数据源上下文。而方法determineDatasource的作用是根据目标方法或目标类上的@DS注解,来确定使用的数据源。如果数据源的名称以#开头,则需要通过dsProcessor.determineDatasource来解析动态数据源名称。方法setDsProcessor() 是一个 Setter 方法,用于注入DsProcessor实例,责根据某些规则或动态逻辑来解析数据源名称。
@Bean
@ConditionalOnMissingBean
public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor();
interceptor.setDsProcessor(dsProcessor);
DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
advisor.setOrder(this.properties.getOrder());
return advisor;
}
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
private static final String DYNAMIC_PREFIX = "#";
private static final DataSourceClassResolver RESOLVER = new DataSourceClassResolver();
private DsProcessor dsProcessor;
public DynamicDataSourceAnnotationInterceptor() {
}
public Object invoke(MethodInvocation invocation) throws Throwable {
Object var2;
try {
DynamicDataSourceContextHolder.push(this.determineDatasource(invocation));
var2 = invocation.proceed();
} finally {
DynamicDataSourceContextHolder.poll();
}
return var2;
}
private String determineDatasource(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
DS ds = method.isAnnotationPresent(DS.class) ? (DS)method.getAnnotation(DS.class) : (DS)AnnotationUtils.findAnnotation(RESOLVER.targetClass(invocation), DS.class);
String key = ds.value();
return !key.isEmpty() && key.startsWith("#") ? this.dsProcessor.determineDatasource(invocation, key) : key;
}
public void setDsProcessor(DsProcessor dsProcessor) {
this.dsProcessor = dsProcessor;
}
}
好的,接下来我们就需要看一下DsProcessor实例的具体内容,dsProcessor()方法构建了一个处理链条,该链条由DsHeaderProcessor、DsSesssionProcessor、DsSpelExpressionProcessor三个处理器组成,用于逐个处理请求,接下来我们去DsHeaderProcessor类中一探究竟
@Bean
@ConditionalOnMissingBean
public DsProcessor dsProcessor() {
DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
DsSessionProcessor sessionProcessor = new DsSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
}
HEADER_PREFIX是一个静态常量,用来表示 HTTP 请求头的前缀,其值是#header。matchs方法用于判断一个给定的key是否以#header为前缀。如果传入的key以#header开头,则返回true,说明该请求可以由DsHeaderProcessor来处理;否则返回false,表示该处理器不适用于这个key。doDetermineDatasource方法用于从HTTP请求的头部提取数据源相关的信息,它去除#header前缀,然后从请求头中获取实际的值,用于决定数据源,也就是说我们从前端传过来的请求头中需要携带决定数据源使用的相关信息。而DsSesssionProcessor、DsSpelExpressionProcessor处理逻辑与其基本一致,这里不再详细展开。
public class DsHeaderProcessor extends DsProcessor {
private static final String HEADER_PREFIX = "#header";
public DsHeaderProcessor() {
}
public boolean matches(String key) {
return key.startsWith("#header");
}
public String doDetermineDatasource(MethodInvocation invocation, String key) {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
return request.getHeader(key.substring(8));
}
}
OK,这样我们就理解了,我们在添加@DS注解的时候不需要写死,这里我以header为例,就是我们可以这样进行传递@DS("#header.dsKey"),同时在前端发起请求时进行拦截,为其添加一个dsKey的字段,其内容就是需要操作的数据源,当然需要提前让用户选择目标数据源,当用户新增数据源后,我们在前端动态的更新数据源选择列表,即可使用新增的数据源。
好的,底层源码的分析就到这里,接下来我们开始动手操作。
2.动态新增数据源
新增数据源时需要指定数据源的相关信息,因此我们首先要创建用于接收数据源信息的实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DataSourceDTO {
private String pollName; //即为我们说是的key
private String username;
private String password;
private String url;
private String driverClassName;
}
OK,接下来新建一个controller,用于处理前端发送过来的新增数据源的请求,这里为了看的清楚我就直接在controller中写了处理逻辑,没有放到service中,大家可以自行修改。
首先我通过@Resource注解拿到dataSource和dataSourceCreator实例,随后将前端传递过来的dataSourceDTO的属性进行拷贝,使用dataSourceCreator新建数据源,并将其添加到当前的dataSource集合中,key为前端传递过来的pollName。
随后我获取当前所有的dataSoure,随后利用stream流的方式依次获取每个数据源实例,拿出key构建一个List集合返回给前端,其中value和label是与前端的约定,新增数据源成功后会有相应提示,并且会跳转到登录界面让重新选择数据源。
@RestController
@Slf4j
@RequestMapping(value = "/DS")
@CrossOrigin
public class DsController {
@Resource
private DynamicRoutingDataSource dataSource;
@Resource
private DataSourceCreator dataSourceCreator;
@PostMapping("/addDB")
public AjaxResult addDB(@RequestBody DataSourceDTO dataSourceDTO) {
try {
DataSourceProperty dataSourceProperty = new DataSourceProperty();
BeanUtil.copyProperties(dataSourceDTO, dataSourceProperty);
DataSource source = dataSourceCreator.createDataSource(dataSourceProperty);
dataSource.addDataSource(dataSourceDTO.getPollName(), source);
// 获取当前所有的数据源
Map<String, DataSource> currentDataSources = dataSource.getCurrentDataSources();
// 将数据源的键(dsKey)和名称传递给前端
List<Map<String, String>> dataSourceList = currentDataSources.entrySet().stream()
.map(entry -> {
Map<String, String> ds = new HashMap<>();
ds.put("value", entry.getKey());
ds.put("label", entry.getKey()); // 这里可以根据需求修改 label(比如映射为显示名称)
return ds;
})
.collect(Collectors.toList());
log.info("Current data sources: {}", dataSource.getCurrentDataSources().keySet());
return ResponseTool.success(dataSourceList);
} catch (Exception e) {
log.error("添加数据源失败: ", e);
return ResponseTool.failed("新增数据源失败");
}
}
}
这里我将部分前端代码进行展示,便于理解整体的交互流程
import { addDB } from "@/api/oxygen/Oxygen.js";
export default {
data() {
return {
// 新增数据源表单数据
newDataSource: {
pollName: "",
url: "",
driverClassName: "",
username: "",
password: "",
},
// 新增数据源表单验证规则
addDataSourceRules: {
pollName: [
{ required: true, message: "请输入数据源名称", trigger: "blur" },
],
url: [{ required: true, message: "请输入数据库 URL", trigger: "blur" }],
driverClassName: [
{ required: true, message: "请输入数据库驱动", trigger: "blur" },
],
username: [
{ required: true, message: "请输入数据库用户名", trigger: "blur" },
],
password: [
{ required: true, message: "请输入数据库密码", trigger: "blur" },
],
},
// 控制新增数据源弹窗的显示与隐藏
addDataSourceDialogVisible: false,
};
},
methods: {
openAddDataSourceDialog() {
this.addDataSourceDialogVisible = true;
},
// 提交新增数据源
submitNewDataSource() {
this.$refs.dataSourceForm.validate((valid) => {
if (valid) {
console.log(this.newDataSource);
console.log(JSON.stringify(this.newDataSource));
// 提交数据源到后端
this.addDataSource(this.newDataSource);
}
});
},
// 向后端提交新增的数据源
addDataSource(newDataSource) {
addDB({
pollName: newDataSource.pollName,
url: newDataSource.url,
username: newDataSource.username,
password: newDataSource.password,
driverClassName: newDataSource.driverClassName,
})
.then((response) => {
console.log(response.data);
if (response.code === 1000) {
this.$message.success("数据源添加成功,请重新登录");
// 调用 Vuex 的 action,传递 response.data 更新 dataSources
this.$store.dispatch("fetchAndUpdateDataSources", response.data);
this.addDataSourceDialogVisible = false; // 关闭对话框
this.resetForm(); // 清空表单数据
// 跳转到主页
this.$router.push("/");
} else {
console.error("添加数据源失败");
this.$message.error(response.data.message);
}
})
.catch((error) => {
console.error("添加数据源失败:", error);
this.$message.error("添加数据源失败");
});
},
resetForm() {
this.newDataSource = {
pollName: "",
url: "",
username: "",
password: "",
driverClassName: "",
};
},
},
};
</script>
这里还有一个问题,就是返回登录界面后,数据源选择列表不会主动刷新的问题,这里我的方法是首先在权限校验拦截器中放行查询当前所有数据源的请求getDB(),然后在前端登录页面的钩子函数中设置向后端查询数据源的请求,并利用后端返回的结果更新Vuex中定义的数据源列表,同时渲染到前端选项列表中,具体过程如下
后端新增查询数据源方法getDB()
@GetMapping()
public AjaxResult getDB() {
Set<String> keys = dataSource.getCurrentDataSources().keySet();
log.info("Current data sources: {}", keys);
// 获取当前所有的数据源
Map<String, DataSource> currentDataSources = dataSource.getCurrentDataSources();
// 将数据源的键(dsKey)和名称传递给前端
List<Map<String, String>> dataSourceList = currentDataSources.entrySet().stream()
.map(entry -> {
Map<String, String> ds = new HashMap<>();
ds.put("value", entry.getKey());
ds.put("label", entry.getKey());
return ds;
})
.collect(Collectors.toList());
return ResponseTool.success(dataSourceList);
}
拦截器中放行该方法
//登录拦截器
registry.addInterceptor(loginInterceptor()).excludePathPatterns(
"/DS"
).order(1);
但是不能放行其它的如新增数据源的请求
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 只放行 GET 请求到 /DS 路径
if ("/DS".equals(request.getRequestURI()) && "GET".equals(request.getMethod())) {
return true; // 放行 GET 请求
}
if (UserHolder.getUser() == null){
response.setStatus(401);
return false;
}
return true;
}
}
前端请求
data() {
computed: {
//从Vuex中获取数据源列表
dataSources() {
return this.$store.state.dataSources;
},
},
mounted() {
// 组件加载时,获取数据源
this.fetchDataSources();
},
methods: {
fetchDataSources() {
// 从后端请求数据源
this.$http
.get("/api/energy/DS")
.then((response) => {
console.log(response.data.data);
// 更新 Vuex 中的 dataSources
this.$store.dispatch("fetchAndUpdateDataSources", response.data.data);
})
.catch((error) => {
console.error("获取数据源失败:", error);
this.$message.error("获取数据源失败");
});
},
3.增加指向新数据源的key
OK,上述过程已经成功实现动态新增数据源并在前端查询回显,那么接下来就需要考虑如何增加指向新数据源的key,换句话说就是如何动态的通过@DS注解来使用新增数据源。
按照我们之前的分析,我们不能在一个类或者方法上面写死数据源,而是需要实现动态解析然后切换数据源,可以使用header、session等方式,因为我开发的是前后端项目,所有请求都是通过前端传递过来并且携带请求头,因此我采用了header的处理模式,下面是详细过程
首先需要准备多个数据源,之前我在application.yml文件中已经配置了两个数据源,这里我又在虚拟机中准备了两个不同的数据源并使用IDEA测试连接通过,同时在每个数据源中新建一个数据库分别为energydb3和energydb4,并且将其中一条数据修改为1、2、3、4分别对应四个数据源,用于验证数据库切换效果
接下来需要修改controller、service接口以及实现类中的方法
@Slf4j
@RestController
@RequestMapping("/steel/contractinfo")
@CrossOrigin
public class ContractInfoController {
@Resource
private ContractInfoService contractInfoService;
@GetMapping("/findAll") public AjaxResult findAll(HttpServletRequest request){
String dsKey = request.getHeader("Dskey");
log.info("当前数据源为:{}", dsKey);
List<ContractInfo> contractInfoList = contractInfoService.findAll();
if (contractInfoList.isEmpty()){
return ResponseTool.failed("暂无数据");
}
return ResponseTool.success(contractInfoList);
}
List<ContractInfo> findAll();
@DS("#header.Dskey")
@Override
public List<ContractInfo> findAll() {
List<ContractInfo> contractInfoList = contractInfoMapper.findAll();
return contractInfoList;
}
为了便于查看,我在控制台输出了当前数据源,通过使用@DS("#header.Dskey")就可以动态解析前端传递过来的请求头中Dskey字段所携带的数据源信息,并对应进行数据源切换。同时前端要求用户在登录之前选择数据源,并通过请求拦截器设置Dskey字段,与JWT令牌方式比较类似,下面附上前端部分代码,便于理解
export default {
data() {
return {
form: {
username: "",
password: "",
dsKey: "", // 用于存储数据源
},
rules: {
username: [
{ required: true, message: "请输入用户名", trigger: "blur" },
],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
dsKey: [{ required: true, message: "请选择数据源", trigger: "change" }],
},
};
},
computed: {
//从Vuex中获取数据源列表
dataSources() {
return this.$store.state.dataSources;
},
},
mounted() {
// 组件加载时,获取数据源
this.fetchDataSources();
},
methods: {
submitForm() {
this.$refs.login.validate((valid) => {
if (valid) {
// 表单验证通过,发送数据到后端
this.$http
.post(LOGIN_URL, {
username: this.form.username,
password: this.form.password,
})
.then((response) => {
if (response.data.code == 1000) {
// 登录成功,保存 token
this.$message.success("登录成功");
// 存储 token 和选择的数据源
const token = response.data.data; // 从响应体中获取 token
sessionStorage.setItem("token", token); // 存储 token
// 存储用户选择的数据源
sessionStorage.setItem("dsKey", this.form.dsKey); // 存储数据源
// 跳转到主页
this.$router.push("/shouYe");
} else {
this.$message.error(response.data.message);
}
})
.catch((error) => {
console.error("登录请求失败:", error);
this.$message.error("服务器错误,请稍后重试");
});
} else {
this.$message.error("请输入账号和密码");
return false;
}
});
},
},
};
// 请求拦截器
service.interceptors.request.use(
config => {
const dsKey = sessionStorage.getItem('dsKey');
if (dsKey) {
// 如果 dsKey 存在,则添加 dsKey 头部
config.headers['dsKey'] = dsKey;
}
return config;
},
error => {
console.log(error);
return Promise.reject(error);
}
);
OK,至此功能顺利完成,接下来我们可以进行启动进行测试。
4.功能测试
现在我的application.yml中还是之前配置的两个数据源energydb1和energydb2,我们启动分别前后端进行测试,首先观察控制台,输出了配置文件中配置的数据源的相关信息
<iframe allowfullscreen="true" data-mediaembed="csdn" frameborder="0" id="6lKEEfY0-1735222177867" src="https://live.csdn.net/v/embed/440774"></iframe>
可以看到,初始状态下前端可以查询到在application.yml中配置的两个数据源,登录成功后可以进行数据源的新增,随后会自动跳转到登录界面,此时能够在数据源选择列表中查询到新增的数据源,选择该数据源后进行数据查询,后端显示了当前操作的数据源是energydb3。
至此,动态新增数据源并进行动态切换的内容结束。
三、查询、删除数据源
1.查询数据源
查询数据源在上述过程中已经分析过,这里不再赘述,主要是调用dataSource的getCurrentDataSource()方法,可以自行对结果进行进一步处理。
使用Postman测试数据源查询功能
2.删除数据源
同理,删除数据源需要传递数据源对应的key,并调用dataSource的removeDataSource()方法进行删除操作,主要实现如下
@DeleteMapping("/deleteDB/{pollName}")
public AjaxResult deleteDB(@PathVariable String pollName) {
dataSource.removeDataSource(pollName);
log.info("数据源 {} 删除成功", pollName);
return ResponseTool.success("删除数据源成功");
}
使用Postman测试数据源删除功能
至此,全部涉及动态数据源的全部内容结束。
四、参考文章
面试官:如何在项目运行过程中增加访问一个新数据源?这可能是80%程序员没有考虑过的问题!_哔哩哔哩_bilibili
标签:return,数据源,Dynamic,public,datasource,key,ds,dataSource From: https://blog.csdn.net/m0_68787365/article/details/144734358