The last article, we have done the early preparation, now, we will start to implement AOP dynamic data source switch, the first click to see, form a good habit ~

Disable SpringBoot from automatically injecting data source configuration

DataSourceAutoConfiguration. The class will automatically search application. Yml or the spring in the properties file. The datasource. * relevant properties and automatically single configuration data sources, We want to achieve is multiple data sources, it must to have their own configuration Is combined with the sentence: @ SpringBootApplication (exclude = {DataSourceAutoConfiguration. Class})

/ * * *@author yanglei
 * @descSpring start *@dateTherefore: 2020/8/3 * /
@MapperScan(basePackages={"com.yang.demo.dao","com.yang.demo.test"})
@ComponentScan({"com.yang.demo"})
@EnableTransactionManagement
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {

    public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

So how do you implement multiple data sources next?

The core AbstractRoutingDataSource – understanding

AbstractRoutingDataSource is spring – the JDBC package provides a AbstractDataSource abstract class, it implements the DataSource interface for database connection method. Let’s look at the key methods of this class:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    @Nullable
    private Map<Object, Object> targetDataSources;
    @Nullable
    private Object defaultTargetDataSource;
    private boolean lenientFallback = true;
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;
    @Nullable
    private DataSource resolvedDefaultDataSource;

    protected DataSource determineTargetDataSource(a) {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }

        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            returndataSource; }}@Nullable
    protected abstract Object determineCurrentLookupKey(a);
Copy the code

As you can see is an interface, then we have to do is certainly determineCurrentLookupKey implements this interface (), and this is why, her glance at the codeDataSource: Map<Object, DataSource> resolvedDataSources: Map<Object, DataSource> resolvedDataSources: Map<Object, DataSource> resolvedDataSources We’ll notify them when we need to switch data sources, so we’ll have to plug them in at first. We definitely need a default data source, and we need to switch Map data.

Configure the data source switchover method

Maintain a static variable datasourceContext that records the datasource key that each thread needs to use. It also provides methods to switch, read, and clear data source configuration information.

/** * Data source switch processing *@author yanglei
 * 2020/9/1 10:56
 */  
public class DynamicDataSourceContextHolder
{
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /** * Maintains variables using ThreadLocal. ThreadLocal provides a separate copy of the variable for each thread that uses it, so that each thread can independently change its own copy without affecting the corresponding copy of other threads. * /
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /** * Sets the data source variable */
    public static void setDataSourceType(String dsType)
    {
        log.info("Switch to {} data source", dsType);
        CONTEXT_HOLDER.set(dsType);
    }

    /** * get the data source variable */
    public static String getDataSourceType(a)
    {
        return CONTEXT_HOLDER.get();
    }

    /** * Empty the data source variable */
    public static void clearDataSourceType(a)
    { CONTEXT_HOLDER.remove(); }}Copy the code

Realize the core AbstractRoutingDataSource

/** * dynamic data source *@author yanglei
 * 2020/9/1 10:56
 */
public class DynamicDataSource extends AbstractRoutingDataSource
{
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey(a)
    {
        returnDynamicDataSourceContextHolder.getDataSourceType(); }}Copy the code

Configuring Data Source Information

Spring: a datasource: type: com. Alibaba. Druid. Pool. DruidDataSource druid: # main library data master: # useSSL =falseIs to cancel the connection to the MySQL database according to MySQL5.545.+,5.626.+ and5.76.+, if no explicit option is set, SSL connections must be established by default//aliyuncs.com:3306/gulishop?useSSL=falseDriverClassName: com.mysql.cj.jdbc.driver # Slave: # Slave data source switch/default off enabled:true
                url: jdbc:oracle:thin:@//localhost:1521/ORCLUsername: system password: system # specify the data source of the fully qualified name driverClassName: oracle). The JDBC driver. The initial connection number initialSize OracleDriver # :5MinIdle:10MaxActive:20MaxWait:60000# configuration interval how to conduct a test, testing needs to be closed free connection, unit is a millisecond timeBetweenEvictionRunsMillis:60000# to configure a connection in the pool minimum survival time, the unit is a millisecond minEvictableIdleTimeMillis:300000# to configure a connection in the pool the largest survival time, the unit is a millisecond maxEvictableIdleTimeMillis:900000ValidationQuery: SELECT1 FROM DUAL
            testWhileIdle: true
            testOnBorrow: false
            testOnReturn: false

Copy the code

Configure data source toggle enumeration classes

Can’t be that low, must be thinking of using annotations to switch

/** * data source *@author yanglei
 * 2020/9/1 10:56
 */
public enum DataSourceType
{
    /**
     * 主库
     */
    MASTER,

    /**
     * 从库
     */
    SLAVE
}
Copy the code

Configuration annotations

/** * Custom multi-data source switching annotations ** Precedence: method first, class second, if method overrides the data source type on the class, method first, otherwise class first *@author yanglei
 * 2020/9/1 10:56
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
    /** * Switch the data source name */
    public DataSourceType value(a) default DataSourceType.MASTER;
}
Copy the code

Druid Configures properties

/** * druid configureproperties ** /
@Configuration
public class DruidProperties
{
    @Value("${spring.datasource.druid.initialSize}")
    private int initialSize;

    @Value("${spring.datasource.druid.minIdle}")
    private int minIdle;

    @Value("${spring.datasource.druid.maxActive}")
    private int maxActive;

    @Value("${spring.datasource.druid.maxWait}")
    private int maxWait;

    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
    private int maxEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.validationQuery}")
    private String validationQuery;

    @Value("${spring.datasource.druid.testWhileIdle}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.druid.testOnBorrow}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.druid.testOnReturn}")
    private boolean testOnReturn;

    public DruidDataSource dataSource(DruidDataSource datasource)
    {
        /** Configures the initialization size, minimum, maximum */
        datasource.setInitialSize(initialSize);
        datasource.setMaxActive(maxActive);
        datasource.setMinIdle(minIdle);

        /** Set the timeout period for obtaining the connection wait */
        datasource.setMaxWait(maxWait);

        /** How often does the configuration detect idle connections that need to be closed, in milliseconds */
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

        /** Sets the minimum and maximum lifetime of a connection in the pool, in milliseconds */
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);

        Select * from 'x' where 'x' = 'x'; If validationQuery is null, testOnBorrow, testOnReturn, and testWhileIdle will not take effect. * /
        datasource.setValidationQuery(validationQuery);
        /** You are advised to set this parameter to true to ensure performance and security. Apply for connection, if free time is more than timeBetweenEvictionRunsMillis, performing validationQuery test connection is valid. * /
        datasource.setTestWhileIdle(testWhileIdle);
        /** Execute validationQuery when applying for a connection to check whether the connection is valid. This configuration reduces performance. * /
        datasource.setTestOnBorrow(testOnBorrow);
        /** Execute validationQuery to check if the connection is valid when the connection is returned. This configuration degrades performance. * /
        datasource.setTestOnReturn(testOnReturn);
        returndatasource; }}Copy the code

Injection data source

package com.yang.demo.dataConfig;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

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

/** * druid configure multiple data sources */
@Configuration
public class DruidConfig {
    
    public static final Logger log = LoggerFactory.getLogger(DruidConfig.class);

    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties) {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties) {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }

    /** * sets the data source **@paramTargetDataSources Collection of alternate data sources *@paramSourceName data sourceName *@paramBeanName bean name */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {
        try {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        } catch (Exception e) {
            log.error("Failed to get slave data source", e.getMessage()); }}}Copy the code

Gets the bean’s utility class

package com.yang.demo.dataConfig;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

/** * The Spring utility class makes it easy to get beans */ in a non-Spring management environment
@Component
public final class SpringUtils implements BeanFactoryPostProcessor {
    /** * Spring application context */
    private static ConfigurableListableBeanFactory beanFactory;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    /** * get the object **@param name
     * @returnObject An instance of a bean registered with the given name *@throws org.springframework.beans.BeansException
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }

    /** * Get an object of type requiredType **@param clz
     * @return
     * @throws org.springframework.beans.BeansException
     */
    public static <T> T getBean(Class<T> clz) throws BeansException {
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /** * Returns true if the BeanFactory contains a bean definition that matches the given name@param name
     * @return boolean
     */
    public static boolean containsBean(String name) {
        return beanFactory.containsBean(name);
    }

    /** * Determines whether the bean definition registered with the given name is a singleton or a prototype. If the corresponding bean definition with the given name is not found, will throw an exception (NoSuchBeanDefinitionException) * *@param name
     * @return boolean
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.isSingleton(name);
    }

    / * * *@param name
     * @returnClass Specifies the type of the registered object *@throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public staticClass<? > getType(String name)throws NoSuchBeanDefinitionException {
        return beanFactory.getType(name);
    }

    /** * If the given bean name has aliases in the bean definition, return those aliases **@param name
     * @return
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getAliases(name);
    }

    /** * Get the AOP proxy object **@param invoker
     * @return* /
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker) {
        return(T) AopContext.currentProxy(); }}Copy the code

The markup annotation method is faceted

package com.yang.demo.dataConfig;

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Objects;

/** * Multi-data processing *@author yanglei
 * 2020/9/1 10:56
 */
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.yang.demo.dataConfig.DataSource)" + "|| @within(com.yang.demo.dataConfig.DataSource)")
    public void dsPointCut(a) {}@Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        DataSource dataSource = getDataSource(point);

        if(dataSource ! =null) {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }

        try {
            return point.proceed();
        } finally {
            // Destroy the data source after executing the methodDynamicDataSourceContextHolder.clearDataSourceType(); }}/** * Get the data source to switch to */
    public DataSource getDataSource(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if (Objects.nonNull(dataSource)) {
            return dataSource;
        }

        returnAnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); }}Copy the code

Annotate the method or class you want to test

/ * * *@author yanglei
 * @desc
 * @date2020/9/4 * /

@Service
@DataSource(value = DataSourceType.SLAVE)
public class TestOracleServiceImpl implements TestOracleService {

    @Autowired
    private TestOracleMapper testOracleMapper;



    @Override
    public List<String> queryInfo(a) {
        returntestOracleMapper.queryInfo(); }}Copy the code

Show run results

Conclusion: emphasis is: AbstractRoutingDataSource, know what he did, to implement difficult

Note: Because my project only involves using the database to query Oracle, so I do not use the transaction problem. If you want to change mysql and Oracle using a service method, THEN I recommend you to look at the annotation + section implementation method. This week we’ll cover annotations + facets implementing transactions across data sources

So here it is, we should already understand the use of AOP to implement multi-data source switch, if this article is still useful to you, trouble to point a thumbs-up ah!