Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions spring-boot-4/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<module>mybatisplus3-sample</module>
<module>name-pattern-sample</module>
<module>quartz-sample</module>
<module>refresh-datasource-sample</module>
<module>shardingsphere-jdbc-5.x-core-sample</module>
<module>spel-sample</module>
<module>tx-local-sample</module>
Expand Down
48 changes: 48 additions & 0 deletions spring-boot-4/refresh-datasource-sample/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-boot-4-samples</artifactId>
<groupId>com.baomidou</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>refresh-datasource-sample</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--仅为演示「spring.datasource.dynamic.refresh-enabled=true」的自动刷新路径:
classpath 存在 RefreshEvent 时框架会自动挂上监听器,换成 Nacos/Apollo 只需换这个依赖。-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot4-starter</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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;

/**
* 配置中心刷新数据源示例。
*
* <p>本示例刻意不连真实的 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\"}]'");
}
}
Original file line number Diff line number Diff line change
@@ -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;

Check failure on line 20 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 17 on ubuntu-latest

cannot find symbol

Check failure on line 20 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 21 on ubuntu-latest

cannot find symbol

Check failure on line 20 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 21 on ubuntu-latest

cannot find symbol
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;

/**
* 数据源刷新入口。
*
* <p>{@code POST /datasources/refresh} 等价于配置中心推送了一份新配置:把整份数据源清单交给 {@link
* DynamicDataSourceRefresher#refresh},由它做 diff 后只重建变化的连接池。 接入 Spring Cloud 时这一步由框架自带的监听器完成,无需本接口。
*/
@RestController
@RequestMapping("/datasources")
public class RefreshController {

private final DataSource dataSource;
private final DynamicDataSourceRefresher refresher;

Check failure on line 44 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 17 on ubuntu-latest

cannot find symbol

Check failure on line 44 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 21 on ubuntu-latest

cannot find symbol

Check failure on line 44 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 21 on ubuntu-latest

cannot find symbol

public RefreshController(DataSource dataSource, DynamicDataSourceRefresher refresher) {

Check failure on line 46 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 17 on ubuntu-latest

cannot find symbol

Check failure on line 46 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 21 on ubuntu-latest

cannot find symbol

Check failure on line 46 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 21 on ubuntu-latest

cannot find symbol
this.dataSource = dataSource;
this.refresher = refresher;
}

/** 当前所有数据源 */
@GetMapping
public Set<String> now() {
return routing().getDataSources().keySet();
}

/** 用一份新配置刷新数据源,返回本次新增/更新/移除/跳过的明细 */
@PostMapping("/refresh")
public DynamicDataSourceRefresher.Result refresh(@RequestBody List<DataSourceDTO> config) {

Check failure on line 59 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 17 on ubuntu-latest

package DynamicDataSourceRefresher does not exist

Check failure on line 59 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 21 on ubuntu-latest

package DynamicDataSourceRefresher does not exist

Check failure on line 59 in spring-boot-4/refresh-datasource-sample/src/main/java/com/baomidou/samples/refresh/controller/RefreshController.java

View workflow job for this annotation

GitHub Actions / Test CI - JDK 21 on ubuntu-latest

package DynamicDataSourceRefresher does not exist
Map<String, DataSourceProperty> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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.<name>} 的片段。 */
@Data
public class DataSourceDTO {

/** 数据源名称 */
private String name;

/** JDBC url */
private String url;

/** 用户名 */
private String username;

/** 密码 */
private String password;

/** 驱动类名 */
private String driverClassName;
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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<DataSourceDTO> 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<DataSourceDTO> initialConfig() {
List<DataSourceDTO> 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;
}
}
Loading