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; + } +}