The Spring Festival is coming, today is a holiday, in this wish friends a happy New Year, good health, clear thinking, no bugs forever!


In a word: parameterized change source means to dynamically add data sources and switch data sources according to parameters to solve the problem of uncertain data sources.

1. The introduction

After the previous two articles on Spring Boot processing multiple database strategy explained, I believe that you have a good understanding of multi-data sources and dynamic data sources. For review, please see:

  • SpringBoot Multiple Data Sources (1) : Multiple Source Policies
  • SpringBoot Multi-Data Sources (2) : Dynamic Data Sources

In the previous article, I left a question, whether multiple sets of sources or dynamic data sources, relatively or fixed data sources (such as master one slave, one master many slave, etc.), that is, the number of databases has been determined at the time of coding, only when the specific use of which one is processed dynamically. What if the data source itself is uncertain, or you need to connect to the database based on user input? You can imagine that we have a requirement for database connection management. The user can enter the corresponding database connection information and then see which tables the database has. MySQL Workbench, Navicat, SQLyog, etc.

This article builds on the previous example by adding a feature that connects to the database and returns the table information for the database based on the database connection information entered by the user. The content includes adding data source dynamically, dynamic proxy simplifies data source operation and so on.

This paper involved the sample code: https://github.com/mianshenglee/my-example/tree/master/multi-datasource, readers can combine together.

2. Parameterize the change source

2.1 Solution

Spring Boot’s dynamic data source essentially stores multiple data sources in a Map. When a data source needs to be used, the Map obtains the data source for processing. In the dynamic data processing, through inheritance abstract class AbstractRoutingDataSource can implement this function. Since it is a Map, if there is a new data source, add the new data source to the Map. That’s the whole idea.

View AbstractRoutingDataSource source, however, can be found that store data source Map targetDataSources is private, and does not provide the Map itself to this operation, it provides are the two key operations: SetTargetDataSources and afterPropertiesSet. SetTargetDataSources sets the entire Map target data source, and afterPropertiesSet parses the Map target data source to form the final resolvedDataSources.

this.resolvedDataSources = new HashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
    Object lookupKey = this.resolveSpecifiedLookupKey(key);
    DataSource dataSource = this.resolveSpecifiedDataSource(value);
    this.resolvedDataSources.put(lookupKey, dataSource);
});
Copy the code

Therefore, to dynamically add data sources to a Map, we can follow these two key actions.

2.2 Process Description

  1. User input database connection parameters (including IP, port, driver name, database name, user name, password)
  2. Create a data source based on database connection parameters
  3. Add a data source to a dynamic data source
  4. Switching data sources
  5. Operating database

3. Implement parameterized change sources

The following operations are based on the examples in previous articles. Basic engineering construction and configuration will not be repeated. If necessary, please refer to the article.

3.1 Transform dynamic data sources

3.1.1 Dynamic data source adding function

To enable dynamic data sources to be added to the Map, we need to modify the dynamic data sources. As follows:

public class DynamicDataSource extends AbstractRoutingDataSource {
    private Map<Object, Object> backupTargetDataSources;

    /** * custom constructor */
    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSource){
        backupTargetDataSources = targetDataSource;
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(backupTargetDataSources);
        super.afterPropertiesSet();
    }

    /** * Add a new data source */
    public void addDataSource(String key, DataSource dataSource){
        this.backupTargetDataSources.put(key,dataSource);
        super.setTargetDataSources(this.backupTargetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey(a) {
        returnDynamicDataSourceContextHolder.getContextKey(); }}Copy the code
  • Added a custombackupTargetDataSourcesAs the originaltargetDataSourcesA copy of the
  • Custom constructor to copy the target data source to the custom Map
  • Still used when adding a new data sourcesetTargetDataSourcesafterPropertiesSetThe new data source is added.
  • Note:afterPropertiesSetIs responsible for parsing into available target data sources.

3.1.2 Dynamic Data Source Configuration

The parameter constructor is used to create a dynamic data source. The parameter constructor is used as follows:

@Bean
@Primary
public DataSource dynamicDataSource(a) {
    Map<Object, Object> dataSourceMap = new HashMap<>(2);
    dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
    dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
    // Has a parameter constructor
    return new DynamicDataSource(masterDataSource(), dataSourceMap);
}
Copy the code

3.2 Adding a data source utility class

3.2.1 Spring Context utility classes

In the process of using Spring Boot, the Spring context is often used. It is common to obtain beans from Spring IOC for operation. Because the IOC used by Spring essentially injects beans into the container, the Spring context is required to fetch them. We add SpringContextHolder to the context package as follows:

@Component
public class SpringContextHolder implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
    }
    /**
     * 返回上下文
     */
    public static ApplicationContext getContext(a){
        returnSpringContextHolder.applicationContext; }}Copy the code

Using getContext, you can get the context and operate on it.

3.2.2 Data source Operation Tools

To add a data source by parameters, you need to construct the data source from the parameters and then add it to the Map described earlier. As follows:

public class DataSourceUtil {
    /** * create a new data source, note: this is only for MySQL database */
    public static DataSource makeNewDataSource(DbInfo dbInfo){
        String url = "jdbc:mysql://"+dbInfo.getIp() + ":"+dbInfo.getPort()+"/"+dbInfo.getDbName()
                +"? useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8";
        String driveClassName = StringUtils.isEmpty(dbInfo.getDriveClassName())? "com.mysql.cj.jdbc.Driver":dbInfo.getDriveClassName();
        return DataSourceBuilder.create().url(url)
                .driverClassName(driveClassName)
                .username(dbInfo.getUsername())
                .password(dbInfo.getPassword())
                .build();
    }

    /** * Add data source to dynamic source */
    public static void addDataSourceToDynamic(String key, DataSource dataSource){
        DynamicDataSource dynamicDataSource = SpringContextHolder.getContext().getBean(DynamicDataSource.class);
        dynamicDataSource.addDataSource(key,dataSource);
    }

    /** * Add data source to dynamic source based on database connection information *@param key
     * @param dbInfo
     */
    public static void addDataSourceToDynamic(String key, DbInfo dbInfo){ DataSource dataSource = makeNewDataSource(dbInfo); addDataSourceToDynamic(key,dataSource); }}Copy the code
  • throughDataSourceBuilderFor other databases, the corresponding URL and DriveClassName need to be changed accordingly.
  • When adding a data source, get the beans for the dynamic data source through the Spring context, and then add.

3.3 Changing a data source using parameters

Now that the previous two steps have added the data source, let’s look at how to use it based on the requirements (connecting to the database based on the database connection information entered by the user, and returning the table information for the database).

3.3.1 Adding the Mapper for Querying database table Information

You can obtain table information from MySQL’s Information_SCHEMA.

@Repository
public interface TableMapper extends BaseMapper<TestUser> {
    /** * query table information */
    @Select("select table_name, table_comment, create_time, update_time " +
            " from information_schema.tables " +
            " where table_schema = (select database())")
    List<Map<String,Object>> selectTableList();
}
Copy the code

3.3.2 Defining database connection information objects

Encapsulate database connection information through a class.

@Data
public class DbInfo {
    private String ip;
    private String port;
    private String dbName;
    private String driveClassName;
    private String username;
    private String password;
}
Copy the code

3.3.3 Parameterize change sources and Query table information

At the Controller layer, we define an interface to query table information, connect to the data source, and return table information based on the parameters passed in:

/** * Obtain table information from database connection information */
@GetMapping("table")
public Object findWithDbInfo(DbInfo dbInfo) throws Exception {
    // Data source key
    String newDsKey = System.currentTimeMillis()+"";
    // Add data source
    DataSourceUtil.addDataSourceToDynamic(newDsKey,dbInfo);
    DynamicDataSourceContextHolder.setContextKey(newDsKey);
    // Query table information
    List<Map<String, Object>> tables = tableMapper.selectTableList();
    DynamicDataSourceContextHolder.removeContextKey();
    return ResponseResult.success(tables);
}
Copy the code
  • Access to the addresshttp://localhost:8080/dd/table?ip=localhost&port=3310&dbName=mytest&username=root&password=111111, corresponding to database connection parameters.
  • The key of the data source is meaningless. You are advised to set a meaningful value based on actual scenarios

4. Dynamic proxy eliminates template code

Already completed the function of parametric switching data source, but also is a template code, such as adding the data source, switching data source, data source for the CURD operation, release the data source, if every place to do this, is very tedious, this time, just need to use dynamic proxy, but parameters my previous article: Java development must learn: dynamic proxy. Here, we use the JDK’s built-in dynamic proxy to parameterize the change data source and eliminate template code.

4.1 Adding a JDK Dynamic Proxy

Add proxy package, add JdkParamDsMethodProxy class, implement the InvocationHandler interface, write parameterized data source switch logic in invoke. As follows:

public class JdkParamDsMethodProxy implements InvocationHandler {
    // The proxy object and its parameters
    private String dataSourceKey;
    private DbInfo dbInfo;
    private Object targetObject;
    public JdkParamDsMethodProxy(Object targetObject, String dataSourceKey, DbInfo dbInfo) {
        this.targetObject = targetObject;
        this.dataSourceKey = dataSourceKey;
        this.dbInfo = dbInfo;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Switch data sources
        DataSourceUtil.addDataSourceToDynamic(dataSourceKey, dbInfo);
        DynamicDataSourceContextHolder.setContextKey(dataSourceKey);
        // Call the method
        Object result = method.invoke(targetObject, args);
        DynamicDataSourceContextHolder.removeContextKey();
        return result;
    }

    /** * Create proxy */
    public static Object createProxyInstance(Object targetObject, String dataSourceKey, DbInfo dbInfo) throws Exception {
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader()
                , targetObject.getClass().getInterfaces(), newJdkParamDsMethodProxy(targetObject, dataSourceKey, dbInfo)); }}Copy the code
  • In the code, the parameters to be used are passed in through the constructor
  • throughProxy.newProxyInstanceCreate the proxy, and when the method executes (invoke) data source addition, switching, database operation, clearing, etc

4.2 Using proxies to Implement Functions

With the proxy, the template code can be erased when adding and switching data sources, and the previous business code becomes:

@GetMapping("table")
    public Object findWithDbInfo(DbInfo dbInfo) throws Exception {
        // Data source key
        String newDsKey = System.currentTimeMillis()+"";
        // Switch data sources using proxies
        TableMapper tableMapperProxy = (TableMapper)JdkParamDsMethodProxy.createProxyInstance(tableMapper, newDsKey, dbInfo);
        List<Map<String, Object>> tables = tableMapperProxy.selectTableList();
        return ResponseResult.success(tables);
    }
Copy the code

With a proxy, the code is much cleaner.

5. To summarize

In this paper, based on dynamic data source, the parametric change data sources and application scenario illustrates, connect to the database is put forward, the function of the query table information demand as an example, according to the parameters to build the data source, data source function is added to the dynamic, the use of parametric change data sources and explain, finally using dynamic proxy simplify the operation. This article focuses on the implementation of code, small partners can novice practice to deepen the cognition.

This article supporting examples, sample code, are interested in running examples to feel.

The resources

  • Spring master/slave database configuration and dynamic data source switching principle: https://www.liaoxuefeng.com/article/1182502273240832
  • Talk about Spring Boot data source loading and its simple implementation of multiple data sources: https://juejin.cn/post/6844903817260056589

The articles

  • SpringBoot Multiple Data Sources (2) : dynamic data sources
  • SpringBoot Multiple Data Sources (1) : Multiple source policies
  • Java development must learn: dynamic proxy
  • Good books to read in 2019
  • Springboot + Apache HTTPS is deployed on the front and back ends
  • Springboot + Logback Log Output
  • Springboot + Logback Log output

My official account (search Mason technical records) for more technical records: