[Long text with ten thousand words] SpringBoot integrates MyBatis to build a complete tutorial of MySQL multi-data source (provide Gitee source code)

Foreword: In my previous blog, I introduced two kinds of how to use SpringBoot to build multi-data source operations. In this blog, I refer to the current mainstream framework, and explain the last way to integrate multiple data sources in the form of a blog. , the integration process is relatively traditional and complicated, but I will still explain the idea of ​​each entity class to everyone clearly, and I will provide the Gitee source code address at the end of the project.

Past blogs:

The first one: SpringBoot+Jpa configures Oracle multiple data sources (Gitee source code is provided)

The second type: SpringBoot+Mybatis builds a brief description of Oracle multi-data source configuration (Gitee source code is provided)

Subsequent additions:

[Wanzi long text] SpringBoot integrates Atomikos to realize multi-data source distributed transactions (Gitee source code is provided)

Table of contents

1. Import pom dependencies

Two, yml configuration file

3. Data source enumeration class 

Four, Spring tool class

Five, configuration class

5.1, DynamicDataSourceContextHolder data source switching processing class

5.2, DynamicDataSource dynamic data source routing class

5.3, DruidProperties configuration properties

5.4, ​​DruidConfig multi-data source core configuration class 

Six, DataSource custom multi-data source switching annotation

7. The aspect class of DataSourceAspect dynamic data source 

8. Complete screenshot of the project

9. How to use

10. Gitee source code 

11. Summary


1. Import pom dependencies

<dependencies>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Lombok驱动依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- MySQL驱动依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>

        <!--Mybatis依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <!-- 阿里数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>

        <!--aop依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
    </dependencies>

Two, yml configuration file

# Mybatis配置
mybatis:
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapper-locations: classpath:mapper/*/*.xml

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 主库数据源
      master:
        url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username:
        password: 
      # 从库数据源
      slave:
        # 从数据源开关/默认关闭
        enabled: true
        url: jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username:
        password:
      # 初始连接数
      initialSize: 5
      # 最小连接池数量
      minIdle: 10
      # 最大连接池数量
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置连接超时时间
      connectTimeout: 30000
      # 配置网络超时时间
      socketTimeout: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # 设置白名单,不填则允许所有访问
        allow:
        url-pattern: /druid/*
      filter:
        stat:
          enabled: true
          # 慢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true

3. Data source enumeration class 

public enum DataSourceType
{
    /**
     * 主库
     */
    MASTER,

    /**
     * 从库
     */
    SLAVE
}

Four, Spring tool class

The main function is to provide a static method for obtaining Bean instances by name.

1. Implement the BeanFactoryPostProcessor and ApplicationContextAware interfaces. When the Spring container is initialized, save the instances of ConfigurableListableBeanFactory and ApplicationContext in static variables.

@Component marks this tool class to be managed by the Spring container.

@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware{

    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

}

2. The postProcessBeanFactory method will be executed before the Bean definition is loaded but instantiated, and the BeanFactory instance is saved at this time.

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

3. setApplicationContext will be executed after the context preparation is completed, and the ApplicationContext instance will be saved at this time.

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
    SpringUtils.applicationContext = applicationContext;
}

4. Provide the getBean method to obtain the Bean instance from the static BeanFactory according to the name.

@SuppressWarnings("unchecked") means to suppress warnings related to unchecked transformations and parameterized variables.

@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException
{
    return (T) beanFactory.getBean(name);
}

Full code: 

package com.example.multiple.utils;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * spring工具类 方便在非spring管理环境中获取bean
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
{
    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

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

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    {
        SpringUtils.applicationContext = applicationContext;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     *
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException
    {
        return (T) beanFactory.getBean(name);
    }

}

Five, configuration class

First, a basic introduction to configuration classes and the relationship between them

1. DynamicDataSourceContextHolder
ThreadLocal holder, used to store the key of the current data source .

2. DynamicDataSource customizes the dynamic data source, and holds multiple target data source Maps inside, and can dynamically switch the data source by setting the key.

3. DruidProperties is used to read the configuration properties of the Druid data source, such as the maximum number of connections, the minimum number of connections, etc.

4. DruidConfig implements Druid 's multi-data source configuration, creates two data source beans , master and slave , and then assembles them into a dynamic data source through DynamicDataSource.

5. The DataSource
annotation is used to mark a method or class to specify which data source to use.

6. For the DataSourceAspect
AOP aspect, the data source key is obtained through the DataSource annotation before the method is executed, and is set in the DynamicDataSourceContextHolder to switch to the specified data source. 

The workflow is :

1. DruidConfig first creates multiple data source beans and hand them over to DynamicDataSource for integration.

2. The business method specifies the data source through the DataSource annotation.

3. Before the method is executed, DataSourceAspect reads the DataSource annotation, obtains the data source key, and sets it to ContextHolder. 

4. When the business method calls the method of the Mapper interface, SQL will be executed through SqlSessionTemplate.

5. The DataSource used inside SqlSessionTemplate is DynamicDataSource.

6. Before obtaining the Connection, the DynamicDataSource will first call the determineCurrentLookupKey method.

7. determineCurrentLookupKey obtains the data source Key from ContextHolder, and determines the target data source to be used.

8. According to the key in ContextHolder , DynamicDataSource obtains Connection from it, and routes to the corresponding data source to execute SQL .

9. Then use this Connection to execute SQL to complete the database operation.

In this way, Druid - based multi-data source switching is realized , mainly through AOP+ThreadLocal to dynamically set the data source key .

5.1, DynamicDataSourceContextHolder data source switching processing class

1. A CONTEXT_HOLDER of ThreadLocal type is defined, which will provide an independent copy storage for each thread.

private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

2. The setDataSourceType method is used to set the data source type to be used by the current thread, and the type will be stored in the ThreadLocal of CONTEXT_HOLDER.

public static void setDataSourceType(String dsType)
{
    log.info("切换到{}数据源", dsType);
    CONTEXT_HOLDER.set(dsType);
}

3. getDataSourceType is used to obtain the data source type used by the current thread, which is obtained from the ThreadLocal of CONTEXT_HOLDER.

public static String getDataSourceType()
{
    return CONTEXT_HOLDER.get();
}

4. clearDataSourceType is used to clear the data source type information of the current thread.

public static void clearDataSourceType()
{
    CONTEXT_HOLDER.remove();
}

In this way, ThreadLocal can be used to share this data source type variable within a thread, and the variables of each thread are independent. 

Full code:

package com.example.multiple.config.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 数据源切换处理
 */
public class DynamicDataSourceContextHolder
{
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源的变量
     */
    public static void setDataSourceType(String dsType)
    {
        log.info("切换到{}数据源", dsType);
        CONTEXT_HOLDER.set(dsType);
    }

    /**
     * 获得数据源的变量
     */
    public static String getDataSourceType()
    {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearDataSourceType()
    {
        CONTEXT_HOLDER.remove();
    }
}

5.2, DynamicDataSource dynamic data source routing class

1. This DynamicDataSource class inherits AbstractRoutingDataSource, and implements a routing Datasource that dynamically switches data sources.

public class DynamicDataSource extends AbstractRoutingDataSource{

}

2. In the construction method, call the method of the parent class to set the default data source and all target data source Maps.

public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
{
    super.setDefaultTargetDataSource(defaultTargetDataSource);
    super.setTargetDataSources(targetDataSources);
    super.afterPropertiesSet();
}

3. The determineCurrentLookupKey method is implemented. In this method, the data source type on the current thread is obtained through the DynamicDataSourceContextHolder tool class, and then the current Lookup Key is set to this data source type.

@Override
protected Object determineCurrentLookupKey()
{
    return DynamicDataSourceContextHolder.getDataSourceType();
}

Finally, AbstractRoutingDataSource will look up the corresponding DataSource in the target data source Map according to the Lookup Key as the source for obtaining the connection. In this way, through the implementation of determineCurrentLookupKey, the DataSource type on the current thread is dynamically returned. Cooperate with DynamicDataSourceContextHolder to switch and set thread DataSource type. According to the type of data source when the thread is running, it can dynamically switch to different data sources to obtain connections. Realized the function of dynamically switching multiple data sources according to the current operating conditions.

Full code:

package com.example.multiple.config.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

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

/**
 * 动态数据源
 */
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()
    {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

5.3, DruidProperties configuration properties

Load properties from the configuration, set them to DruidDataSource, and create an available DataSource instance. There are comments on the code, so I won’t explain it here.

Full code:

package com.example.multiple.config.properties;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * druid 配置属性
 *
 */
@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.connectTimeout}")
    private int connectTimeout;

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

    @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)
    {
        /** 配置初始化大小、最小、最大 */
        datasource.setInitialSize(initialSize);
        datasource.setMaxActive(maxActive);
        datasource.setMinIdle(minIdle);

        /** 配置获取连接等待超时的时间 */
        datasource.setMaxWait(maxWait);

        /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */
        datasource.setConnectTimeout(connectTimeout);

        /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */
        datasource.setSocketTimeout(socketTimeout);

        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);

        /**
         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
         */
        datasource.setValidationQuery(validationQuery);
        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
        datasource.setTestWhileIdle(testWhileIdle);
        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
        datasource.setTestOnBorrow(testOnBorrow);
        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
        datasource.setTestOnReturn(testOnReturn);
        return datasource;
    }
}

5.4, ​​DruidConfig multi-data source core configuration class 

 1. Use @Configuration to mark this class as the core configuration class.

@Configuration
public class DruidConfig{
}

2. Register a Bean that creates the master main data source

The first step, the @ConfigurationProperties annotation loads the property configuration named "spring.datasource.druid.master".

The second step is to create a DruidDataSource instance through DruidDataSourceBuilder.

The third step is to pass the DruidDataSource instance into the dataSource method of DruidProperties.

The fourth step, DruidProperties will set various properties of DruidDataSource according to the loaded property configuration, such as the maximum number of connections, the minimum number of connections, etc.

Step 5. The dataSource method will return the DruidDataSource instance with the properties set.

Finally, the configured DruidDataSource will be used as an instance of masterDataSource Bean, so it implements the property configuration method of using Spring Boot, loads the configuration of druid.master, and sets it to DruidDataSource to create an available master data source Bean. 

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

3. Register a Bean that creates a salve from the data source. The overall steps are similar to the above, and there is no more explanation.

The role of the @ConditionalOnProperty annotation is to determine whether the Bean is created according to the given conditions. Only when spring.datasource.druid.slave.enabled=true is configured, the Bean of this slaveDataSource will be created. 

@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);
}

4. The method of setting the data source setDataSource

The first step, the method receives a Map object targetDataSources, the data source name sourceName and the data source Bean name beanName as parameters.

The second step is to obtain the DataSource Bean instance corresponding to the beanName from the Spring container through the SpringUtils tool class.

Finally, store the obtained DataSource instance into the Map of targetDataSources according to the sourceNamekey. 

public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

In this way, the DataSource is loaded through the beanName and stored in the targetDataSources using the custom sourceName as the key. The goal is to build a mapping relationship between a custom name and a data source instance, which exists in the container targetDataSources. This can realize configuration management of multiple data sources, and obtain corresponding data source instances through different sourceNames. 

5. Realize the configuration of dynamic data source

The first step is to create a Map to store the target data source, and put the Bean named masterDataSource into the Map as the main data source.

The second step is to call the setDataSource method to put the Bean named slaveDataSource into the Map, and set the Key to SLAVE.

Finally, use the primary data source instance and data source Map to create a DynamicDataSource instance and set it to Primary, which is the default data source.

@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);
}

Full code:

package com.example.multiple.config;

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

import com.example.multiple.config.datasource.DynamicDataSource;
import com.example.multiple.enums.DataSourceType;
import com.example.multiple.config.properties.DruidProperties;
import com.example.multiple.utils.SpringUtils;
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 com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;

/**
 * druid 配置多数据源
 */
@Configuration
public class DruidConfig
{
    @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);
    }

    /**
     * 设置数据源
     *
     * @param targetDataSources 备选数据源集合
     * @param sourceName 数据源名称
     * @param beanName bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

}

Six, DataSource custom multi-data source switching annotation

1. @Target and @Retention indicate that the annotation can be used on methods and classes, and can be retained until runtime.

2. @Documented indicates that the annotation will be included in javadoc.

3. @Inherited means that the annotation can be inherited by subclasses.

The annotation has only one value attribute, the type is DataSourceType enumeration, and the default value is MASTER.

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
    /**
     * 切换数据源名称
     */
    public DataSourceType value() default DataSourceType.MASTER;
}

7. The aspect class of DataSourceAspect dynamic data source 

1. @Aspect marks this class as an aspect class, @Order specifies the loading order of beans, and @Component marks this class as a Spring container for hosting.

@Aspect
@Order(1)
@Component
public class DataSourceAspect{
}

2. @Pointcut defines the cut point, here is the method or class that matches all @DataSource annotations.

@Pointcut("@annotation(com.example.multiple.annotation.DataSource)"
        + "|| @within(com.example.multiple.annotation.DataSource)")
public void dsPointCut()
{
}

3. Obtain the data source that needs to be switched

The first step is to obtain the method signature through point.getSignature() and convert it to the MethodSignature type.

The second step is to call the findAnnotation method of AnnotationUtils, take the method as the target, and obtain the @DataSource annotation on it.

The third step, if the annotation is not empty, return the annotation directly.

Finally, if there is no annotation on the method, look for the @DataSource annotation again with the class where the method is located, and return.

public DataSource getDataSource(ProceedingJoinPoint point)
{
    MethodSignature signature = (MethodSignature) point.getSignature();
    DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
    if (Objects.nonNull(dataSource))
    {
        return dataSource;
    }
    return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}

4. @Around defines the processing logic of the cut point as surround enhancement 

The first step is to call the getDataSource method to obtain the DataSource annotation required by the target method.

The second step is to determine if the annotation is not empty, call the setDataSourceType method of DynamicDataSourceContextHolder, and set the value of the annotation value (data source type) to it.

The third step is to call the proceed method of ProceedingJoinPoint to execute the target method.

Finally, in finally, call the clearDataSourceType method of DynamicDataSourceContextHolder to clear the thread-local DataSourceType.

 @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
     {
         // 销毁数据源 在执行方法之后
         DynamicDataSourceContextHolder.clearDataSourceType();
     }
 }

Full code:

package com.example.multiple.aspectj;

import com.example.multiple.annotation.DataSource;
import com.example.multiple.config.datasource.DynamicDataSourceContextHolder;
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;

/**
 * 多数据源处理
 */
@Aspect
@Order(1)
@Component
public class DataSourceAspect
{
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.example.multiple.annotation.DataSource)"
            + "|| @within(com.example.multiple.annotation.DataSource)")
    public void dsPointCut()
    {

    }

    @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
        {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    /**
     * 获取需要切换的数据源
     */
    public DataSource getDataSource(ProceedingJoinPoint point)
    {
        MethodSignature signature = (MethodSignature) point.getSignature();
        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if (Objects.nonNull(dataSource))
        {
            return dataSource;
        }

        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

8. Complete screenshot of the project

The above explains the core configuration of multiple data sources. The rest is some routine operations of integrating MyBatis. I put them on the code cloud, so I won’t write too much here. The complete project construction package is like this.

9. How to use

Use the @DataSource custom annotation on the Mapper layer or Service layer to switch to the specified data source.

@Mapper
@DataSource(DataSourceType.SLAVE)
public interface SlaveMapper {

    public List<Logger> select();

}

The result of the operation is as follows: 

10. Gitee source code 

Configure your own master-slave data source in the yml file and start the project with one click

Project address: SpringBoot integrates MyBatis to build MySQL multiple data sources

11. Summary

The above is my technical analysis of how SpringBoot integrates multiple data sources. It is also a more traditional way and more complicated. If you have any questions, welcome to discuss in the comment area!

Guess you like

Origin blog.csdn.net/HJW_233/article/details/132032716