[Texto largo de Wanzi] SpringBoot integra MyBatis para crear un tutorial completo sobre fuentes de datos múltiples MySQL (se proporciona el código fuente de Gitee)

Prólogo: en mi blog anterior, presenté dos tipos de cómo usar SpringBoot para construir múltiples operaciones de fuentes de datos. En este blog, me refiero al marco principal actual y explico la última forma de integrar múltiples fuentes de datos en forma de un blog., El proceso de integración es relativamente tradicional y complicado, pero aún así explicaré claramente la idea de cada clase de entidad a todos y proporcionaré la dirección del código fuente de Gitee al final del proyecto.

Blogs anteriores:

El primero: SpringBoot+Jpa configura múltiples fuentes de datos de Oracle (se proporciona el código fuente de Gitee)

El segundo tipo: SpringBoot + Mybatis crea una breve descripción de la configuración de fuente de datos múltiples de Oracle (se proporciona el código fuente de Gitee)

Adiciones posteriores:

[Texto largo de Wanzi] SpringBoot integra Atomikos para realizar transacciones distribuidas de fuentes de datos múltiples (proporcione el código fuente de Gitee)

Tabla de contenido

1. Importar dependencias de pom

Dos, archivo de configuración yml

3. Clase de enumeración de fuente de datos 

Cuatro, clase de herramienta Spring

Cinco, clase de configuración

5.1, clase de procesamiento de cambio de fuente de datos DynamicDataSourceContextHolder

5.2, clase de enrutamiento de fuente de datos dinámica DynamicDataSource

5.3, propiedades de configuración de DruidProperties

5.4, ​​​​clase de configuración central de fuente de datos múltiples DruidConfig 

Seis, anotación de cambio de fuente de datos múltiples personalizada de DataSource

7. La clase de aspecto de la fuente de datos dinámica DataSourceAspect 

8. Captura de pantalla completa del proyecto.

9. Cómo utilizar

10. Código fuente de Gitee 

11. Resumen


1. Importar dependencias de pom

<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>

Dos, archivo de configuración yml

# 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. Clase de enumeración de fuente de datos 

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

    /**
     * 从库
     */
    SLAVE
}

Cuatro, clase de herramienta Spring

La función principal es proporcionar un método estático para obtener instancias de Bean por nombre.

1. Implemente las interfaces BeanFactoryPostProcessor y ApplicationContextAware. Cuando se inicialice el contenedor Spring, guarde las instancias de ConfigurableListableBeanFactory y ApplicationContext en variables estáticas.

@Component marca esta clase de herramienta para que la administre el contenedor Spring.

@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware{

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

    private static ApplicationContext applicationContext;

}

2. El método postProcessBeanFactory se ejecutará antes de cargar la definición de Bean, pero se creará una instancia, y la instancia de BeanFactory se guardará en este momento.

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

3. setApplicationContext se ejecutará una vez que se complete la preparación del contexto y la instancia de ApplicationContext se guardará en este momento.

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

4. Proporcione el método getBean para obtener la instancia de Bean de BeanFactory estática según el nombre.

@SuppressWarnings("unchecked") significa suprimir las advertencias relacionadas con transformaciones no marcadas y variables parametrizadas.

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

Código completo: 

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

}

Cinco, clase de configuración

Primero, una introducción básica a las clases de configuración y la relación entre ellas.

1. DynamicDataSourceContextHolder
ThreadLocal titular, utilizado para almacenar la clave de la fuente de datos actual .

2. DynamicDataSource personaliza la fuente de datos dinámica, contiene múltiples mapas de fuentes de datos de destino y puede cambiar dinámicamente la fuente de datos configurando la clave.

3. DruidProperties se utiliza para leer las propiedades de configuración de la fuente de datos Druid, como el número máximo de conexiones, el número mínimo de conexiones, etc.

4. DruidConfig implementa la configuración de fuente de datos múltiples de Druid , crea dos beans de fuente de datos , maestro y esclavo , y luego los ensambla en una fuente de datos dinámica a través de DynamicDataSource.

5. La
anotación DataSource se utiliza para marcar un método o clase para especificar qué fuente de datos usar.

6. Para el
aspecto AOP de DataSourceAspect, la clave de la fuente de datos se obtiene a través de la anotación DataSource antes de que se ejecute el método y se establece en DynamicDataSourceContextHolder para cambiar a la fuente de datos especificada. 

El flujo de trabajo es :

1. DruidConfig primero crea varios beans de fuente de datos y los entrega a DynamicDataSource para su integración.

2. El método empresarial especifica la fuente de datos mediante la anotación DataSource .

3. Antes de ejecutar el método, DataSourceAspect lee la anotación DataSource, obtiene la clave de la fuente de datos y la establece en ContextHolder. 

4. Cuando el método comercial llama al método de la interfaz Mapper, SQL se ejecutará a través de SqlSessionTemplate.

5. El origen de datos utilizado dentro de SqlSessionTemplate es DynamicDataSource.

6. Antes de obtener la conexión, DynamicDataSource primero llamará al método determineCurrentLookupKey.

7. determineCurrentLookupKey obtiene la clave de la fuente de datos de ContextHolder y determina la fuente de datos de destino que se utilizará.

8. Según la clave en ContextHolder , DynamicDataSource obtiene Connection de él y lo enruta a la fuente de datos correspondiente para ejecutar SQL .

9. Luego use esta conexión para ejecutar SQL y completar la operación de la base de datos.

De esta manera, se realiza la conmutación de fuentes de datos múltiples basada en Druid , principalmente a través de AOP + ThreadLocal para configurar dinámicamente la clave de fuente de datos .

5.1, clase de procesamiento de cambio de fuente de datos DynamicDataSourceContextHolder

1. Se define un CONTEXT_HOLDER de tipo ThreadLocal, que proporcionará un almacenamiento de copia independiente para cada hilo.

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

2. El método setDataSourceType se utiliza para establecer el tipo de fuente de datos que utilizará el hilo actual, y el tipo se almacenará en ThreadLocal de CONTEXT_HOLDER.

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

3. getDataSourceType se utiliza para obtener el tipo de fuente de datos utilizado por el hilo actual, que se obtiene del ThreadLocal de CONTEXT_HOLDER.

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

4. clearDataSourceType se utiliza para borrar la información del tipo de fuente de datos del hilo actual.

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

De esta manera, ThreadLocal se puede utilizar para compartir esta variable de tipo de fuente de datos dentro de un hilo, y las variables de cada hilo son independientes. 

Código completo:

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, clase de enrutamiento de fuente de datos dinámica DynamicDataSource

1. Esta clase DynamicDataSource hereda AbstractRoutingDataSource e implementa una fuente de datos de enrutamiento que cambia dinámicamente las fuentes de datos.

public class DynamicDataSource extends AbstractRoutingDataSource{

}

2. En el método de construcción, llame al método de la clase principal para establecer la fuente de datos predeterminada y todos los mapas de fuentes de datos de destino.

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

3. Se implementa el método determineCurrentLookupKey. En este método, el tipo de fuente de datos en el hilo actual se obtiene a través de la clase de herramienta DynamicDataSourceContextHolder, y luego la clave de búsqueda actual se establece en este tipo de fuente de datos.

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

Finalmente, AbstractRoutingDataSource buscará el DataSource correspondiente en el mapa de origen de datos de destino de acuerdo con la clave de búsqueda como origen para obtener la conexión. De esta manera, mediante la implementación de determineCurrentLookupKey, se devuelve dinámicamente el tipo de fuente de datos en el hilo actual. Coopere con DynamicDataSourceContextHolder para cambiar y configurar el tipo de fuente de datos del subproceso. Según el tipo de fuente de datos cuando se ejecuta el hilo, puede cambiar dinámicamente a diferentes fuentes de datos para obtener conexiones. Realizó la función de cambiar dinámicamente múltiples fuentes de datos de acuerdo con las condiciones operativas actuales.

Código completo:

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, propiedades de configuración de DruidProperties

Cargue propiedades desde la configuración, configúrelas en DruidDataSource y cree una instancia de DataSource disponible. Hay comentarios sobre el código, por lo que no explicaré mucho aquí.

Código completo:

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, ​​​​clase de configuración central de fuente de datos múltiples DruidConfig 

 1. Utilice @Configuration para marcar esta clase como la clase de configuración principal.

@Configuration
public class DruidConfig{
}

2. Registre un Bean que cree la fuente de datos principal maestra

El primer paso, la anotación @ConfigurationProperties carga la configuración de propiedad denominada "spring.datasource.druid.master".

El segundo paso es crear una instancia de DruidDataSource a través de DruidDataSourceBuilder.

El tercer paso es pasar la instancia de DruidDataSource al método dataSource de DruidProperties.

El cuarto paso, DruidProperties establecerá varias propiedades de DruidDataSource de acuerdo con la configuración de propiedades cargadas, como la cantidad máxima de conexiones, la cantidad mínima de conexiones, etc.

Paso 5. El método dataSource devolverá la instancia de DruidDataSource con las propiedades establecidas.

Finalmente, el DruidDataSource configurado se usará como una instancia de masterDataSource Bean, por lo que implementa el método de configuración de propiedad de usar Spring Boot, carga la configuración de druid.master y la establece en DruidDataSource para crear un Bean de fuente de datos maestro disponible. 

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

3. Registre un Bean que cree un ungüento a partir de la fuente de datos. Los pasos generales son similares a los anteriores y no hay más explicaciones.

La función de la anotación @ConditionalOnProperty es determinar si el Bean se crea de acuerdo con las condiciones dadas. Solo cuando se configura spring.datasource.druid.slave.enabled=true, se creará el Bean de este SlaveDataSource. 

@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. El método para configurar la fuente de datos setDataSource

En el primer paso, el método recibe un objeto Map targetDataSources, el nombre de la fuente de datos sourceName y el nombre del Bean de la fuente de datos beanName como parámetros.

El segundo paso es obtener la instancia de DataSource Bean correspondiente al beanName del contenedor Spring a través de la clase de herramienta SpringUtils.

Finalmente, almacene la instancia de DataSource obtenida en el mapa de targetDataSources de acuerdo con 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();
        }
    }

De esta manera, el origen de datos se carga a través de beanName y se almacena en targetDataSources utilizando el nombre de origen personalizado como clave. El objetivo es crear una relación de mapeo entre un nombre personalizado y una instancia de fuente de datos, que existe en el contenedor targetDataSources. Esto puede realizar la gestión de la configuración de múltiples fuentes de datos y obtener las instancias de fuentes de datos correspondientes a través de diferentes nombres de fuente. 

5. Realizar la configuración de la fuente de datos dinámica.

El primer paso es crear un mapa para almacenar la fuente de datos de destino y colocar el Bean llamado masterDataSource en el mapa como fuente de datos principal.

El segundo paso es llamar al método setDataSource para colocar el Bean llamado SlaveDataSource en el mapa y establecer la clave en SLAVE.

Finalmente, use la instancia de fuente de datos principal y el mapa de fuente de datos para crear una instancia de DynamicDataSource y configúrela como Primaria, que es la fuente de datos predeterminada.

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

Código completo:

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

}

Seis, anotación de cambio de fuente de datos múltiples personalizada de DataSource

1. @Target y @Retention indican que la anotación se puede utilizar en métodos y clases, y se puede conservar hasta el tiempo de ejecución.

2. @Documented indica que la anotación se incluirá en javadoc.

3. @Inherited significa que las subclases pueden heredar la anotación.

La anotación tiene solo un atributo de valor, el tipo es enumeración DataSourceType y el valor predeterminado es MASTER.

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

7. La clase de aspecto de la fuente de datos dinámica DataSourceAspect 

1. @Aspect marca esta clase como una clase de aspecto, @Order especifica el orden de carga de los beans y @Component marca esta clase como un contenedor Spring para alojamiento.

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

2. @Pointcut define el punto de corte, aquí está el método o clase que coincide con todas las anotaciones @DataSource.

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

3. Obtenga la fuente de datos que debe cambiarse

El primer paso es obtener la firma del método a través de point.getSignature() y convertirla al tipo MethodSignature.

El segundo paso es llamar al método findAnnotation de AnnotationUtils, tomar el método como objetivo y obtener la anotación @DataSource en él.

El tercer paso, si la anotación no está vacía, devuelve la anotación directamente.

Finalmente, si no hay ninguna anotación en el método, busque nuevamente la anotación @DataSource con la clase donde se encuentra el método y regrese.

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 define la lógica de procesamiento del punto de corte como mejora envolvente 

El primer paso es llamar al método getDataSource para obtener la anotación DataSource requerida por el método de destino.

El segundo paso es determinar si la anotación no está vacía, llamar al método setDataSourceType de DynamicDataSourceContextHolder y establecerle el valor de la anotación (tipo de fuente de datos).

El tercer paso es llamar al método de proceder de ProceedingJoinPoint para ejecutar el método de destino.

Finalmente, finalmente, llame al método clearDataSourceType de DynamicDataSourceContextHolder para borrar el DataSourceType local del subproceso.

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

Código completo:

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. Captura de pantalla completa del proyecto.

Lo anterior explica la configuración central de múltiples fuentes de datos. El resto son algunas operaciones de rutina para integrar MyBatis. Las puse en la nube de código, así que no escribiré demasiado aquí. El paquete completo de construcción del proyecto es así.

9. Cómo utilizar

Utilice la anotación personalizada @DataSource en la capa Mapper o en la capa de Servicio para cambiar a la fuente de datos especificada.

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

    public List<Logger> select();

}

El resultado de la operación es el siguiente: 

10. Código fuente de Gitee 

Configure su propia fuente de datos maestro-esclavo en el archivo yml e inicie el proyecto con un clic

Dirección del proyecto: SpringBoot integra MyBatis para construir múltiples fuentes de datos MySQL

11. Resumen

Lo anterior es mi análisis técnico de cómo SpringBoot integra múltiples fuentes de datos. También es una forma más tradicional y más complicada. Si tiene alguna pregunta, ¡bienvenido a discutir en el área de comentarios!

Supongo que te gusta

Origin blog.csdn.net/HJW_233/article/details/132032716
Recomendado
Clasificación