From c3555b6ef250daafb7730731f115a88ec5d5269e Mon Sep 17 00:00:00 2001
From: TaoYu <332309254@qq.com>
Date: Thu, 15 Oct 2026 17:39:56 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20refresh-datasource?=
=?UTF-8?q?-sample?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
演示把一份新配置交给 DynamicDataSourceRefresher 完成增量刷新,
同时给出 spring.datasource.dynamic.refresh-enabled 的自动刷新用法。
---
spring-boot-4/pom.xml | 1 +
.../refresh-datasource-sample/pom.xml | 48 +++++++++++
.../samples/refresh/RefreshDsApplication.java | 38 +++++++++
.../refresh/controller/RefreshController.java | 75 ++++++++++++++++
.../samples/refresh/dto/DataSourceDTO.java | 38 +++++++++
.../src/main/resources/application.yml | 23 +++++
.../test/RefreshDatasourceSampleTest.java | 85 +++++++++++++++++++
7 files changed, 308 insertions(+)
create mode 100644 spring-boot-4/refresh-datasource-sample/pom.xml
create mode 100644 spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/RefreshDsApplication.java
create mode 100644 spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java
create mode 100644 spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/dto/DataSourceDTO.java
create mode 100644 spring-boot-4/refresh-datasource-sample/src/main/resources/application.yml
create mode 100644 spring-boot-4/refresh-datasource-sample/src/test/java/com/baomidou/samples/refresh/test/RefreshDatasourceSampleTest.java
diff --git a/spring-boot-4/pom.xml b/spring-boot-4/pom.xml
index 05e650ae..e9ed5750 100644
--- a/spring-boot-4/pom.xml
+++ b/spring-boot-4/pom.xml
@@ -42,6 +42,7 @@
mybatisplus3-sample
name-pattern-sample
quartz-sample
+ refresh-datasource-sample
shardingsphere-jdbc-5.x-core-sample
spel-sample
tx-local-sample
diff --git a/spring-boot-4/refresh-datasource-sample/pom.xml b/spring-boot-4/refresh-datasource-sample/pom.xml
new file mode 100644
index 00000000..7e7ae98c
--- /dev/null
+++ b/spring-boot-4/refresh-datasource-sample/pom.xml
@@ -0,0 +1,48 @@
+
+
+
+ spring-boot-4-samples
+ com.baomidou
+ 1.0.0
+
+ 4.0.0
+
+ refresh-datasource-sample
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+ org.springframework.cloud
+ spring-cloud-context
+
+
+ com.baomidou
+ dynamic-datasource-spring-boot4-starter
+
+
+ com.h2database
+ h2
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
diff --git a/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/RefreshDsApplication.java b/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/RefreshDsApplication.java
new file mode 100644
index 00000000..f3897efc
--- /dev/null
+++ b/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/RefreshDsApplication.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright © ${project.inceptionYear} organization baomidou
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.samples.refresh;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * 配置中心刷新数据源示例。
+ *
+ *
本示例刻意不连真实的 Nacos:刷新这件事与配置中心无关,换成任何配置中心都只是 「把新配置交给 DynamicDataSourceRefresher」这一步。
+ */
+@Slf4j
+@SpringBootApplication
+public class RefreshDsApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RefreshDsApplication.class, args);
+ log.info(
+ "try: curl -X POST localhost:8080/datasources/refresh -H 'Content-Type: application/json' "
+ + "-d '[{\"name\":\"slave_2\",\"url\":\"jdbc:h2:mem:slave2;DB_CLOSE_DELAY=-1\","
+ + "\"username\":\"sa\",\"password\":\"\",\"driverClassName\":\"org.h2.Driver\"}]'");
+ }
+}
diff --git a/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java b/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java
new file mode 100644
index 00000000..b28c413f
--- /dev/null
+++ b/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright © ${project.inceptionYear} organization baomidou
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.samples.refresh.controller;
+
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceRefresher;
+import com.baomidou.samples.refresh.dto.DataSourceDTO;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.sql.DataSource;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 数据源刷新入口。
+ *
+ *
{@code POST /datasources/refresh} 等价于配置中心推送了一份新配置:把整份数据源清单交给 {@link
+ * DynamicDataSourceRefresher#refresh},由它做 diff 后只重建变化的连接池。 接入 Spring Cloud 时这一步由框架自带的监听器完成,无需本接口。
+ */
+@RestController
+@RequestMapping("/datasources")
+public class RefreshController {
+
+ private final DataSource dataSource;
+ private final DynamicDataSourceRefresher refresher;
+
+ public RefreshController(DataSource dataSource, DynamicDataSourceRefresher refresher) {
+ this.dataSource = dataSource;
+ this.refresher = refresher;
+ }
+
+ /** 当前所有数据源 */
+ @GetMapping
+ public Set now() {
+ return routing().getDataSources().keySet();
+ }
+
+ /** 用一份新配置刷新数据源,返回本次新增/更新/移除/跳过的明细 */
+ @PostMapping("/refresh")
+ public DynamicDataSourceRefresher.Result refresh(@RequestBody List config) {
+ Map next = new LinkedHashMap<>();
+ for (DataSourceDTO item : config) {
+ DataSourceProperty property = new DataSourceProperty();
+ property.setUrl(item.getUrl());
+ property.setUsername(item.getUsername());
+ property.setPassword(item.getPassword());
+ property.setDriverClassName(item.getDriverClassName());
+ next.put(item.getName(), property);
+ }
+ return refresher.refresh(next);
+ }
+
+ private DynamicRoutingDataSource routing() {
+ return (DynamicRoutingDataSource) dataSource;
+ }
+}
diff --git a/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/dto/DataSourceDTO.java b/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/dto/DataSourceDTO.java
new file mode 100644
index 00000000..0eb1a7c8
--- /dev/null
+++ b/spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/dto/DataSourceDTO.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright © ${project.inceptionYear} organization baomidou
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.samples.refresh.dto;
+
+import lombok.Data;
+
+/** 一份数据源配置,对应配置中心里 {@code spring.datasource.dynamic.datasource.} 的片段。 */
+@Data
+public class DataSourceDTO {
+
+ /** 数据源名称 */
+ private String name;
+
+ /** JDBC url */
+ private String url;
+
+ /** 用户名 */
+ private String username;
+
+ /** 密码 */
+ private String password;
+
+ /** 驱动类名 */
+ private String driverClassName;
+}
diff --git a/spring-boot-4/refresh-datasource-sample/src/main/resources/application.yml b/spring-boot-4/refresh-datasource-sample/src/main/resources/application.yml
new file mode 100644
index 00000000..51dd11cc
--- /dev/null
+++ b/spring-boot-4/refresh-datasource-sample/src/main/resources/application.yml
@@ -0,0 +1,23 @@
+server:
+ port: 8080
+
+spring:
+ datasource:
+ dynamic:
+ primary: master
+ strict: true
+ # 替换连接池时等待活跃连接归还,避免刷新把在途请求直接打断
+ grace-destroy: true
+ # classpath 存在 Spring Cloud 的 RefreshEvent 时,配置变更会自动刷新数据源
+ refresh-enabled: true
+ datasource:
+ master:
+ url: jdbc:h2:mem:master;DB_CLOSE_DELAY=-1
+ username: sa
+ password: ''
+ driver-class-name: org.h2.Driver
+ slave_1:
+ url: jdbc:h2:mem:slave1;DB_CLOSE_DELAY=-1
+ username: sa
+ password: ''
+ driver-class-name: org.h2.Driver
diff --git a/spring-boot-4/refresh-datasource-sample/src/test/java/com/baomidou/samples/refresh/test/RefreshDatasourceSampleTest.java b/spring-boot-4/refresh-datasource-sample/src/test/java/com/baomidou/samples/refresh/test/RefreshDatasourceSampleTest.java
new file mode 100644
index 00000000..87b1dd99
--- /dev/null
+++ b/spring-boot-4/refresh-datasource-sample/src/test/java/com/baomidou/samples/refresh/test/RefreshDatasourceSampleTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright © ${project.inceptionYear} organization baomidou
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.samples.refresh.test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceRefresher;
+import com.baomidou.samples.refresh.controller.RefreshController;
+import com.baomidou.samples.refresh.dto.DataSourceDTO;
+import java.util.ArrayList;
+import java.util.List;
+import javax.sql.DataSource;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class RefreshDatasourceSampleTest {
+
+ @Autowired private RefreshController controller;
+
+ @Autowired private DataSource dataSource;
+
+ @Test
+ void shouldAddAndRemoveDataSourceByRefresh() {
+ DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
+ assertThat(ds.getDataSources())
+ .containsKeys("master", "slave_1")
+ .doesNotContainKey("slave_2");
+
+ List withNewOne = new ArrayList<>(initialConfig());
+ withNewOne.add(h2("slave_2", "jdbc:h2:mem:slave2;DB_CLOSE_DELAY=-1"));
+
+ DynamicDataSourceRefresher.Result added = controller.refresh(withNewOne);
+ assertThat(added.getAdded()).containsExactly("slave_2");
+ assertThat(ds.getDataSources()).containsKey("slave_2");
+
+ DynamicDataSourceRefresher.Result removed = controller.refresh(initialConfig());
+ assertThat(removed.getRemoved()).containsExactly("slave_2");
+ assertThat(ds.getDataSources()).doesNotContainKey("slave_2");
+ }
+
+ @Test
+ void shouldSkipWhenConfigUnchanged() {
+ DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
+ DataSource slaveBefore = ds.getDataSources().get("slave_1");
+
+ DynamicDataSourceRefresher.Result result = controller.refresh(initialConfig());
+
+ assertThat(result.isChanged()).isFalse();
+ assertThat(result.getSkipped()).contains("master", "slave_1");
+ assertThat(ds.getDataSources().get("slave_1")).isSameAs(slaveBefore);
+ }
+
+ private static List initialConfig() {
+ List config = new ArrayList<>();
+ config.add(h2("master", "jdbc:h2:mem:master;DB_CLOSE_DELAY=-1"));
+ config.add(h2("slave_1", "jdbc:h2:mem:slave1;DB_CLOSE_DELAY=-1"));
+ return config;
+ }
+
+ private static DataSourceDTO h2(String name, String url) {
+ DataSourceDTO dto = new DataSourceDTO();
+ dto.setName(name);
+ dto.setUrl(url);
+ dto.setUsername("sa");
+ dto.setPassword("");
+ dto.setDriverClassName("org.h2.Driver");
+ return dto;
+ }
+}