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