Article From:https://www.cnblogs.com/badtheway/p/9061065.html

Because springboot2 updates the API of binding parameters, some springboot1 tool classes for binding, such as RelaxedPropertyResolver, are no longer available in the new version. This article implements the referencehttps://blog.csdn.net/catoop/article/details/50575038The general idea of this article is consistent. If you need to implement it in detail, you can refer to it. All of these are handover of dynamic data sources through AbstractRoutingDataSource. When I used spring to configure multiple data sources, I implemented it through it.In order to understand its principles, there is not much to say here.

 

There’s not much ado about it. First, the data source registration tool class. The main difference between springboot2 and 1 is here.

MultiDataSourceRegister.java:

package top.ivan.demo.springboot.mapper;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MultiDataSourceRegister implements EnvironmentAware, ImportBeanDefinitionRegistrar {

    private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases(); //alias

    static {
        //Because part of the data source configuration is different, add aliases here to avoid switching data sources with certain parameters that cannot be injected.
        aliases.addAliases("url", new String[]{"jdbc-url"});
        aliases.addAliases("username", new String[]{"user"});
    }

    private Environment evn; //The configuration context is also understood as a tool for obtaining configuration files.

    private Map<String, DataSource> sourceMap;  //Data source list

    private Binder binder; //Parameter binding tool

    /**
     * ImportBeanDefinitionRegistrarThe implementation method of the interface can register bean in its own way.**@param annotationMetadata
     * @param beanDefinitionRegistry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        Map config, properties, defaultConfig = binder.bind("spring.datasource", Map.class).get(); //Get all data source configuration
        sourceMap = new HashMap<>(); //Default configuration
        properties = defaultConfig;
        String typeStr = evn.getProperty("spring.datasource.type"); //Default data source type
        Class<? extends DataSource> clazz = getDataSourceType(typeStr); //Get the type of data source
        DataSource consumerDatasource, defaultDatasource = bind(clazz, properties); //Binding the default data source parameters
        List<Map> configs = binder.bind("spring.datasource.multi", Bindable.listOf(Map.class)).get(); //Get other data source configuration
        for (int i = 0; i < configs.size(); i++) { //Traversing the generation of other data sources
            config = configs.get(i);
            clazz = getDataSourceType((String) config.get("type"));
            if ((boolean) config.getOrDefault("extend", Boolean.TRUE)) { //Gets the extend field. If it is not defined or true, it is inherited.
                properties = new HashMap(defaultConfig); //Inheriting the default data source configuration
                properties.putAll(config); //Add data source parameters
            } else {
                properties = config; //Do not inherit default configuration
            }
            consumerDatasource = bind(clazz, properties); //Binding parameters
            sourceMap.put(config.get("key").toString(), consumerDatasource); //Gets the key of the data source, so that the data source can be located through the key.
        }
        GenericBeanDefinition define = new GenericBeanDefinition(); //beanDefinition class
        define.setBeanClass(MultiDataSource.class); //Set the type of bean, where MultiDataSource is the implementation class that inherits AbstractRoutingDataSource.
        MutablePropertyValues mpv = define.getPropertyValues(); //The parameters needed to be injected are similar to those in spring configuration file < property/>
        mpv.add("defaultTargetDataSource", defaultDatasource); //Add the default data source to avoid the absence of key. No data source is available.
        mpv.add("targetDataSources", sourceMap); //Adding other data sources
        beanDefinitionRegistry.registerBeanDefinition("datasource", define); //Register the bean as datasource and do not use datasource generated by springboot automatically.
    }

    /**
     * Getting the data source class object by string**@param typeStr
     * @return
     */
    private Class<? extends DataSource> getDataSourceType(String typeStr) {
        Class<? extends DataSource> type;
        try {
            if (StringUtils.hasLength(typeStr)) { //The string is not empty, and the class object is obtained by reflection
                type = (Class<? extends DataSource>) Class.forName(typeStr);
            } else {
                type = HikariDataSource.class;  //The default is the hikariCP data source, which is consistent with the springboot default data source.
            }
            return type;
        } catch (Exception e) {
            throw new IllegalArgumentException("can not resolve class with type: " + typeStr); //If you can't get the class object by reflection, you throw an exception, which is generally written wrong, so this time throw a runtimeException.
        }
    }

    /**
     * Binding parameters, the following three methods are implemented with reference to the bind method of DataSourceBuilder, with the aim of ensuring that our own added data source construction process is consistent with the springboot**@param result
     * @param properties
     */
    private void bind(DataSource result, Map properties) {
        ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
        binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));  //Binding parameters to objects
    }

    private <T extends DataSource> T bind(Class<T> clazz, Map properties) {
        ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
        return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get(); //Bind parameters by type and obtain instance objects
    }

    /**
     * @param clazz
     * @param sourcePath The parameter path corresponds to the value in the configuration file, such as spring.datasource*@param <T>
     * @return
     */
    private <T extends DataSource> T bind(Class<T> clazz, String sourcePath) {
        Map properties = binder.bind(sourcePath, Map.class).get();
        return bind(clazz, properties);
    }

    /**
     * EnvironmentAwareThe implementation of the interface is injected through aware, which is the environment object.**@param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.evn = environment;
        binder = Binder.get(evn); //Binding Configurator
    }
}

Let me release my configuration file application.yml here:

spring:
  datasource:
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/graduation_project?useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.jdbc.Driver
    username: ivan
    openMulti: true
    type: com.zaxxer.hikari.HikariDataSource
    idle-timeout: 30000
    multi:
    - key: default1
      password: 123456
      url: jdbc:mysql://127.0.0.1:3306/graduation_project?useUnicode=true&characterEncoding=UTF-8
      idle-timeout: 20000
      driver-class-name: com.mysql.jdbc.Driver
      username: ivan
      type: com.alibaba.druid.pool.DruidDataSource
    - key: gd
      password: 123456
      url: jdbc:mysql://gd.badtheway.xin:****/graduation_project?useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: ivan
mybatis:
  config-location: classpath:mapper/configure.xml
  mapper-locations: classpath:mapper/*Mapper.xml

Here, the configuration under the spring.datasource path is the configuration of the default data source. I’m for personal beauty and convenience, so use the spring.datasource.multi path when configuring multiple data sources, if you need to change it.Then modify the corresponding value in MultiDataSourceRegister.java.

Finally, don’t forget to add @Import (MultiDataSourceRegister.class) to @SpringBootApplication.

 

The following are some of the cut configurations that I use to mark the classes that need to switch the data source through the @MultiDataSource$DataSource annotation, through the method body parameter -> the method annotate -> the class annotation implements the switch data source. For your reference:

MultiDataSource.java:

package top.ivan.demo.springboot.mapper;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.Set;

public class MultiDataSource extends AbstractRoutingDataSource {

    private final static ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<>(); //Save the key of the data source of the current thread

    private Set<Object> keySet;  //Key sets of all data sources

    private static void switchSource(String key) {
        DATA_SOURCE_KEY.set(key); //Switching the key of the first thread
    }

    private static void clear() {
        DATA_SOURCE_KEY.remove(); //Remove the key value
    }
    
    public static Object execute(String ds, Run run) throws Throwable {
        switchSource(ds);
        try {
            return run.run();
        } finally {
            clear();
        }
    }

    //AbstractRoutingDataSourceThe abstract class implementation method is to get the key of the current thread data source.
    @Override
    protected Object determineCurrentLookupKey() {
        String key = DATA_SOURCE_KEY.get();
        if (!keySet.contains(key)) {
            logger.info(String.format("can not found datasource by key: '%s',this session may use default datasource", key));
        }
        return key;
    }

    /**
     * The collection of key is only intended to add some alarm logs.*/
    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        try {
            Field sourceMapField = AbstractRoutingDataSource.class.getDeclaredField("resolvedDataSources");
            sourceMapField.setAccessible(true);
            Map<Object, javax.sql.DataSource> sourceMap = (Map<Object, javax.sql.DataSource>) sourceMapField.get(this);
            this.keySet = sourceMap.keySet();
            sourceMapField.setAccessible(false);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }
    
    public interface Run {
        Object run() throws Throwable;
    }

    /**
     * For getting the AOP tangent and the annotation of the data source key*/
    @Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource {
        String value() default ""; //This value is the key value
    }

    /**
     * Declarations*/
    @Component
    @Aspect
    @Order(-10)  //Make the cut before the transaction
    public static class DataSourceSwitchInterceptor {

        /**
         * Scan all classes containing @MultiDataSource$DataSource annotations.*/
        @Pointcut("@within(top.ivan.demo.springboot.mapper.MultiDataSource.DataSource)")
        public void switchDataSource() {
        }

        /**
         * Monitoring using around*@param point
         * @return
         * @throws Throwable
         */
        @Around("switchDataSource()")
        public Object switchByMethod(ProceedingJoinPoint point) throws Throwable {
            Method method = getMethodByPoint(point); //Get the execution method
            Parameter[] params = method.getParameters(); //Get the execution parameters
            Parameter parameter;
            String source = null;
            boolean isDynamic = false;
            for (int i = params.length - 1; i >= 0; i--) { //Scan whether there is a parameter with @DataSource annotation
                parameter = params[i];
                if (parameter.getAnnotation(DataSource.class) != null && point.getArgs()[i] instanceof String) {
                    source = (String) point.getArgs()[i]; //keyThe value is the value of this parameter, which requires that the parameter must be String type.
                    isDynamic = true;
                    break;
                }
            }
            if (!isDynamic) { //No parameter with Datasource annotation
                DataSource dataSource = method.getAnnotation(DataSource.class); //@DataSource annotation for obtaining methods
                if (null == dataSource || !StringUtils.hasLength(dataSource.value())) { //Methods do not contain annotations
                    dataSource = method.getDeclaringClass().getAnnotation(DataSource.class); //Get the class level @DataSource annotation
                }
                if (null != dataSource) {
                    source = dataSource.value(); //Setting the key value
                }
            }
            return persistBySource(source, point); //Continue to execute the method
        }


        private Object persistBySource(String source, ProceedingJoinPoint point) throws Throwable {
            try {
                switchSource(source); //Switching data source
                return point.proceed(); //implement
            } finally {
                clear(); //Clearing key value
            }
        }

        private Method getMethodByPoint(ProceedingJoinPoint point) {
            MethodSignature methodSignature = (MethodSignature) point.getSignature();
            return methodSignature.getMethod();
        }
    }
    
}

Examples:

package top.ivan.demo.springboot.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import top.ivan.demo.springboot.pojo.ProductPreview;

import java.util.List;

@Mapper
@MultiDataSource.DataSource("ds1")
public interface PreviewMapper {

    //Use the value of DS as key
    List<ProductPreview> getList(@Param("start") int start, @Param("count") int count, @MultiDataSource.DataSource String ds);

    //Use "DS2" as key
    @MultiDataSource.DataSource("ds2")
    List<ProductPreview> getList2(@Param("start") int start, @Param("count") int count);
    
    //Use "DS1" as key
    List<ProductPreview> getList3(@Param("start") int start, @Param("count") int count);

}

These days just contacted springboot, still in the state of small white, if there are any problems, please welcome everyone to advise.

Similar Posts:

Leave a Reply

Your email address will not be published. Required fields are marked *