preface
We recently received a requirement to dynamically configure data sources with an unknown number of data sources but a default one.
Traditional dynamic data source configuration methods
1. Introduce dependencies
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>2.5.6</version>
</dependency>
Copy the code
2. Write the data source configuration
spring:
datasource:
dynamic:
primary: master Set the default data source or data source group to master
datasource:
master:
username: Your database user name
password: Your username and password
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xx.xx.xx/xx? useUnicode=true&characterEncoding=utf-8&useSSL=false
slave: # This is the slave library
username: Your database user name
password: Your database user name
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://xxx.xxx.xxx/xxx? useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
. You can also configure the Oracle database
Copy the code
Use 3.
/ * * *@author zly
* @version 1.0.0
* @ClassName HisTaskServiceImpl.java
* @Description TODO
* @createTimeMay 25, 2021 11:49:00 */
@Service("hisTaskService")
@DS("slave") Spring Boot has loaded the data source into the service. Annotations can also be written on the method
@Slf4j
public class HisTaskServiceImpl implements HisTaskService {... }Copy the code
Wait a minute, it seems like this method is not going to fulfill the requirement that we said at the beginning that we don’t know how many data sources there are, there’s no way to reconfigure them in the configuration file so there’s another way
The second way
1. Introduce dependencies as well
<dependency>
<groupId>com.github.yeecode.dynamicdatasource</groupId>
<artifactId>DynamicDataSource</artifactId>
<version>1.3.2</version>
</dependency>
Copy the code
Wheels git address code not spread for the first two that are based on AbstractRoutingDataSource realize this class to write, wheeled za don’t built standing on the shoulders giants see scenery
2. Configure the default data source
dynamicDataSource:
default:
url: Database address
username: Database user name
password: Database password
driverClassName: com.p6spy.engine.spy.P6SpyDriver
Copy the code
Configuration is the same as importing dependencies, but the code is open source and can be changed to suit your project
Use 3.
@RestController
public class MainController {
@Autowired
private UserService userService;
@Autowired
private DynamicDataSource dynamicDataSource;// Inject dynamic data sources
@RequestMapping(value = "/01")
public String queryFromDS01(a) {
DataSourceInfo dataSourceInfo = new DataSourceInfo("ds01"."com.mysql.cj.jdbc.Driver"."jdbc:mysql://xxx.xxx.xxx/db1? useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8"."root"."password");
dynamicDataSource.addDataSource(dataSourceInfo, true);
dynamicDataSource.switchDataSource("ds01");
return userService.select();
}
@RequestMapping(value = "/02")
public String queryFromDS02(a) {
DataSourceInfo dataSourceInfo = new DataSourceInfo("ds02"."com.mysql.cj.jdbc.Driver"."jdbc:mysql://xxx.xxx.xxx:port/db2? useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8"."root"."password");
dynamicDataSource.addAndSwitchDataSource(dataSourceInfo, true);
return userService.select();
}
Copy the code
This can be injected in the form of injection, which implements dynamic transformation of the page configuration data source by passing in data source information through the interface
4. DynamicDataSource source code
package com.github.yeecode.dynamicdatasource;
import com.github.yeecode.dynamicdatasource.datasource.DynamicDataSourceConfig;
import com.github.yeecode.dynamicdatasource.model.DataSourceInfo;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.concurrent.ConcurrentHashMap;
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> CURRENT_DATASOURCE_NAME = new ThreadLocal<String>();
private ConcurrentHashMap<Object, Object> dataSourcesMap = new ConcurrentHashMap<Object, Object>();
private ConcurrentHashMap<Object, DataSourceInfo> dataSourceInfoMap = new ConcurrentHashMap<Object, DataSourceInfo>();
private final String DEFAULT_DATA_SOURCE = "com.github.yeecode.dynamicdatasource_defaultDataSource";
public DynamicDataSource(DataSource defaultDataSource) {
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(dataSourcesMap);
this.dataSourcesMap.put(DEFAULT_DATA_SOURCE, defaultDataSource);
}
@Override
public Object determineCurrentLookupKey(a) {
return CURRENT_DATASOURCE_NAME.get();
}
/ * * * * *@paramDataSourceInfo creates a new DataSource *@paramOverwrite Whether to allow overwriting * if a data source with the same name already exists@returnWhether a new data source */ has been added
public synchronized boolean addDataSource(DataSourceInfo dataSourceInfo, Boolean overwrite) {
if (dataSourcesMap.containsKey(dataSourceInfo.getName()) && dataSourceInfo.equals(dataSourceInfoMap.get(dataSourceInfo.getName()))) {
return true;
} else if(dataSourcesMap.containsKey(dataSourceInfo.getName()) && ! overwrite) {return false;
} else {
DataSource dataSource = DynamicDataSourceConfig.createDataSource(dataSourceInfo);
dataSourcesMap.put(dataSourceInfo.getName(), dataSource);
dataSourceInfoMap.put(dataSourceInfo.getName(), dataSourceInfo);
super.afterPropertiesSet();
return true; }}/** * Add a new data source and switch to it **@paramDataSourceInfo New data source name *@paramOverwrite Overwrites the existing * if a data source exists@returnWhether the new data source is available */
public synchronized boolean addAndSwitchDataSource(DataSourceInfo dataSourceInfo, Boolean overwrite) {
if (dataSourcesMap.containsKey(dataSourceInfo.getName()) && dataSourceInfo.equals(dataSourceInfoMap.get(dataSourceInfo.getName()))) {
CURRENT_DATASOURCE_NAME.set(dataSourceInfo.getName());
return true;
} else if(dataSourcesMap.containsKey(dataSourceInfo.getName()) && ! overwrite) {return false;
} else {
DataSource dataSource = DynamicDataSourceConfig.createDataSource(dataSourceInfo);
dataSourcesMap.put(dataSourceInfo.getName(), dataSource);
dataSourceInfoMap.put(dataSourceInfo.getName(),dataSourceInfo);
super.afterPropertiesSet();
CURRENT_DATASOURCE_NAME.set(dataSourceInfo.getName());
return true; }}/** * Switch data source **@paramDataSourceName The data source to switch@returnCheck whether the switchover succeeds */
public synchronized boolean switchDataSource(String dataSourceName) {
if(! dataSourcesMap.containsKey(dataSourceName)) {return false;
}
CURRENT_DATASOURCE_NAME.set(dataSourceName);
return true;
}
/** * Delete data source by name. If the specified data source is in use, deletion is not allowed. * *@paramDataSourceName Specifies the name of the data source to delete@returnCheck whether the deletion succeeded */
public synchronized boolean delDataSource(String dataSourceName) {
if (CURRENT_DATASOURCE_NAME.get().equals(dataSourceName)) {
return false;
} else {
dataSourcesMap.remove(dataSourceName);
dataSourceInfoMap.remove(dataSourceName);
return true; }}/** * get the default data source **@returnThe default data source */
public DataSource getDefaultDataSource(a) {
return (DataSource) dataSourcesMap.get(DEFAULT_DATA_SOURCE);
}
/** * Switch the default data source */
public void switchDefaultDataSource(a) { CURRENT_DATASOURCE_NAME.set(DEFAULT_DATA_SOURCE); }}Copy the code
Start loading the default data source configuration class
package com.github.yeecode.dynamicdatasource.datasource;
import com.github.yeecode.dynamicdatasource.DynamicDataSource;
import com.github.yeecode.dynamicdatasource.model.DataSourceInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DynamicDataSourceConfig {
@Value("${dynamicDataSource.default.url}")
private String defaultUrl;
@Value("${dynamicDataSource.default.driverClassName}")
private String driverClassName;
@Value("${dynamicDataSource.default.username}")
private String defaultUsername;
@Value("${dynamicDataSource.default.password}")
private String defaultPassword;
@Bean("defaultDataSource")
public DataSource defaultDataSource(a) {
return DataSourceBuilder.create().url(defaultUrl)
.driverClassName(driverClassName)
.username(defaultUsername)
.password(defaultPassword).build();
}
@Bean
@Primary
public DynamicDataSource dynamicDataSource(DataSource defaultDataSource) {
return new DynamicDataSource(defaultDataSource);
}
public static DataSource createDataSource(DataSourceInfo datasourceInfo) {
returnDataSourceBuilder.create().url(datasourceInfo.getUrl()) .driverClassName(datasourceInfo.getDriverClassName()) .username(datasourceInfo.getUserName()) .password(datasourceInfo.getPassword()).build(); }}Copy the code
5. Pay attention to the point
To load the data source in the bootstrap class mask add and scan the dynamic data source configuration class if you change the class configuration package name path changes yourself
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@ ComponentScan (basePackages = {" com. Making. Yeecode. Dynamicdatasource ", "this is your project through package name"})
Copy the code
This article only introduces two ways to use dynamic data sources, not the source code