[1 万語の長いテキスト] SpringBoot は MyBatis を統合して、MySQL マルチデータ ソースの完全なチュートリアルを構築します (Gitee ソース コードを提供します)

はじめに: 前回のブログでは、SpringBoot を使用して複数データ ソースの操作を構築する 2 種類の方法を紹介しましたが、このブログでは、現在主流のフレームワークを参照し、複数のデータ ソースを統合する最後の方法を説明します。ブログ、統合プロセスは比較的伝統的で複雑ですが、それでも各エンティティ クラスの概念を全員に明確に説明し、プロジェクトの最後に Gitee ソース コードのアドレスを提供します。

過去のブログ:

1 つ目: SpringBoot+JPa は Oracle の複数のデータ ソースを構成します (Gitee ソース コードが提供されます)

2 番目のタイプ: SpringBoot+Mybatis は、Oracle マルチデータ ソース構成の簡単な説明を構築します (Gitee ソース コードが提供されます)。

その後の追加:

[Wanzi 長文] SpringBoot に Amitikos を統合し、マルチデータソース分散トランザクションを実現 (Gitee ソースコード提供)

目次

1. pom 依存関係をインポートする

2、yml設定ファイル

3. データソース列挙クラス 

4、スプリングツールクラス

5、構成クラス

5.1、DynamicDataSourceContextHolder データソース切り替え処理クラス

5.2、DynamicDataSource 動的データ ソース ルーティング クラス

5.3、DruidProperties 構成プロパティ

5.4、DruidConfig マルチデータ ソース コア構成クラス 

6、DataSourceカスタムマルチデータソース切り替えアノテーション

7. DataSourceAspect 動的データ ソースのアスペクト クラス 

8. プロジェクトの完全なスクリーンショット

9. 使用方法

10.Giteeのソースコード 

11. まとめ


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

2、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. データソース列挙クラス 

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

    /**
     * 从库
     */
    SLAVE
}

4、スプリングツールクラス

主な機能は、Bean インスタンスを名前で取得するための静的メソッドを提供することです。

1. BeanFactoryPostProcessor および ApplicationContextAware インターフェースを実装し、Spring コンテナーが初期化されるときに、ConfigurableListableBeanFactory および ApplicationContext のインスタンスを静的変数に保存します。

@Component は、このツール クラスが Spring コンテナーによって管理されるようにマークします。

@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware{

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

    private static ApplicationContext applicationContext;

}

2. postProcessBeanFactory メソッドは、Bean 定義がロードされてインスタンス化される前に実行され、BeanFactory インスタンスはこの時点で保存されます。

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

3. コンテキストの準備が完了すると setApplicationContext が実行され、このとき ApplicationContext インスタンスが保存されます。

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

4. getBean メソッドを提供し、名前に従って静的 BeanFactory から Bean インスタンスを取得します。

@SuppressWarnings("unchecked") は、チェックされていない変換およびパラメーター化された変数に関連する警告を抑制することを意味します。

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

完全なコード: 

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

}

5、構成クラス

まず、構成クラスとそれらの関係についての基本的な説明です。

1. DynamicDataSourceContextHolder
ThreadLocal ホルダー。現在のデータ ソースのキーを保存するために使用されます

2. DynamicDataSourceは動的データソースをカスタマイズし、内部に複数のターゲットデータソースMapを保持し、キーの設定によりデータソースを動的に切り替えることができます。

3. DruidProperties は、最大接続数、最小接続数などの Druid データ ソースの構成プロパティを読み取るために使用されます。

4. DruidConfig はDruidのマルチデータ ソース構成を実装し、マスタースレーブ2 つのデータ ソースBeanを作成しDynamicDataSource を通じてそれらを動的データ ソースにアセンブルします。

5. DataSource
アノテーションは、メソッドまたはクラスをマークして、使用するデータ ソースを指定するために使用されます。

6. DataSourceAspect
AOP アスペクトの場合、メソッドが実行される前に DataSource アノテーションを通じてデータ ソース キーが取得され、指定されたデータ ソースに切り替えるために DynamicDataSourceContextHolder に設定されます。 

ワークフローは次のとおりです

1. DruidConfig はまず複数のデータ ソースBeanを作成し、統合のためにそれらをDynamicDataSourceに渡します

2.ビジネス メソッドは、DataSourceアノテーションを通じてデータ ソースを指定します

3. メソッドが実行される前に、DataSourceAspect は DataSource アノテーションを読み取り、データ ソース キーを取得して、それを ContextHolder に設定します。 

4. ビジネスメソッドがMapperインターフェースのメソッドを呼び出すと、SqlSessionTemplateを通じてSQLが実行されます。

5. SqlSessionTemplate 内で使用される DataSource は DynamicDataSource です。

6. 接続を取得する前に、DynamicDataSource はまず、determineCurrentLookupKey メソッドを呼び出します。

7. detectCurrentLookupKey は、ContextHolder からデータ ソース Key を取得し、使用するターゲット データ ソースを決定します。

8. DynamicDataSource はContextHolderのキーに従ってConnection を取得し、対応するデータ ソースにルーティングしてSQLを実行します

9. 次に、この接続を使用して SQL を実行し、データベース操作を完了します。

このようにして、 Druidベースのマルチデータ ソースの切り替えが、主にAOP+ThreadLocalを通じてデータ ソースキーを動的に設定することによって実現されます

5.1、DynamicDataSourceContextHolder データソース切り替え処理クラス

1. ThreadLocal タイプの CONTEXT_HOLDER が定義され、各スレッドに独立したコピー ストレージが提供されます。

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

2. setDataSourceType メソッドは、現在のスレッドで使用されるデータ ソース タイプを設定するために使用され、そのタイプは CONTEXT_HOLDER の ThreadLocal に格納されます。

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

3. getDataSourceType は、現在のスレッドで使用されるデータ ソース タイプを取得するために使用されます。これは、CONTEXT_HOLDER の ThreadLocal から取得されます。

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

4. clearDataSourceType は、現在のスレッドのデータ ソース タイプ情報をクリアするために使用されます。

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

このように、ThreadLocal を使用してこのデータ ソース タイプの変数をスレッド内で共有することができ、各スレッドの変数は独立しています。 

完全なコード:

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 動的データ ソース ルーティング クラス

1. このDynamicDataSourceクラスは、AbstractRoutingDataSourceを継承し、データソースを動的に切り替えるルーティングDatasourceを実装します。

public class DynamicDataSource extends AbstractRoutingDataSource{

}

2. 構築メソッドで、親クラスのメソッドを呼び出して、デフォルト データ ソースとすべてのターゲット データ ソース マップを設定します。

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

3. detectCurrentLookupKey メソッドが実装されています。このメソッドでは、現在のスレッドのデータ ソース タイプが DynamicDataSourceContextHolder ツール クラスを通じて取得され、現在のルックアップ キーがこのデータ ソース タイプに設定されます。

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

最後に、AbstractRoutingDataSource は、接続を取得するためのソースとしてルックアップ キーに従って、ターゲット データ ソース マップ内の対応する DataSource を検索します。このように、determineCurrentLookupKey の実装を通じて、現在のスレッドの DataSource タイプが動的に返されます。DynamicDataSourceContextHolder と連携してスレッドの DataSource タイプを切り替えて設定します。スレッドの実行時にデータ ソースの種類に応じて、別のデータ ソースに動的に切り替えて接続を取得できます。現在の動作状況に応じて複数のデータソースを動的に切り替える機能を実現しました。

完全なコード:

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 構成プロパティ

構成からプロパティをロードし、DruidDataSource に設定し、使用可能な DataSource インスタンスを作成します。コードにはコメントがあるため、ここでは説明しません。

完全なコード:

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 マルチデータ ソース コア構成クラス 

 1. @Configuration を使用して、このクラスをコア構成クラスとしてマークします。

@Configuration
public class DruidConfig{
}

2. マスタメインデータソースを作成するBeanを登録する

最初のステップである @ConfigurationProperties アノテーションは、「spring.datasource.druid.master」という名前のプロパティ構成を読み込みます。

2 番目のステップは、DruidDataSourceBuilder を通じて DruidDataSource インスタンスを作成することです。

3 番目のステップは、DruidDataSource インスタンスを DruidProperties の dataSource メソッドに渡すことです。

4 番目のステップである DruidProperties は、最大接続数、最小接続数など、読み込まれたプロパティ構成に従って DruidDataSource のさまざまなプロパティを設定します。

ステップ 5. dataSource メソッドは、プロパティが設定された DruidDataSource インスタンスを返します。

最後に、設定した DruidDataSource を masterDataSource Bean のインスタンスとして使用するため、Spring Boot を使用したプロパティ設定メソッドを実装し、druid.master の設定を読み込み、DruidDataSource に設定して利用可能なマスター データ ソース Bean を作成します。 

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

3. データ ソースからスレーブを作成する Bean を登録する 全体的な手順は上記と同様なので、これ以上の説明は省略します。

@ConditionalOnProperty アノテーションの役割は、Bean が指定された条件に従って作成されるかどうかを判断することです。spring.datasource.druid.slave.enabled=true が設定されている場合のみ、このslaveDataSourceのBeanが作成されます。 

@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. データソースの設定方法 setDataSource

最初のステップでは、メソッドはマップ オブジェクト targetDataSources、データ ソース名 sourceName、およびデータ ソース Bean 名 beanName をパラメータとして受け取ります。

2 番目のステップは、SpringUtils ツール クラスを介して Spring コンテナから BeanName に対応する DataSource Bean インスタンスを取得することです。

最後に、取得した DataSource インスタンスを、sourceNamekey に従って targetDataSources の Map に格納します。 

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

このようにして、DataSource は beanName を通じてロードされ、カスタム sourceName をキーとして使用して targetDataSources に保存されます。目標は、カスタム名とコンテナー targetDataSources に存在するデータ ソース インスタンスの間にマッピング関係を構築することです。これにより、複数のデータ ソースの構成管理を実現し、異なる sourceName を通じて対応するデータ ソース インスタンスを取得できます。 

5. 動的データソースの構成を実現する

最初のステップは、ターゲット データ ソースを格納するマップを作成し、masterDataSource という名前の Bean をメイン データ ソースとしてマップに配置することです。

2 番目のステップは、setDataSource メソッドを呼び出して、slaveDataSource という名前の Bean をマップに配置し、キーを SLAVE に設定することです。

最後に、プライマリ データ ソース インスタンスとデータ ソース マップを使用して DynamicDataSource インスタンスを作成し、それをデフォルトのデータ ソースであるプライマリに設定します。

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

完全なコード:

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

}

6、DataSourceカスタムマルチデータソース切り替えアノテーション

1. @Target と @Retention は、アノテーションがメソッドとクラスで使用でき、実行時まで保持できることを示します。

2. @Documented は、注釈が javadoc に含まれることを示します。

3. @Inherited は、アノテーションがサブクラスに継承できることを意味します。

注釈には value 属性が 1 つだけあり、タイプは DataSourceType 列挙型で、デフォルト値は MASTER です。

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

7. DataSourceAspect 動的データ ソースのアスペクト クラス 

1. @Aspect はこのクラスをアスペクト クラスとしてマークし、@Order は Bean のロード順序を指定し、@Component はこのクラスを Spring コンテナー ホスティング用にマークします。

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

2. @Pointcut はカット ポイントを定義します。これは、すべての @DataSource アノテーションに一致するメソッドまたはクラスです。

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

3. 切り替える必要があるデータソースを取得します

最初のステップは、point.getSignature() を通じてメソッド シグネチャを取得し、それを MethodSignature 型に変換することです。

2 番目のステップは、AnnotationUtils の findAnnotation メソッドを呼び出し、そのメソッドをターゲットとして取得し、そのメソッドに対する @DataSource アノテーションを取得することです。

3 番目のステップでは、注釈が空でない場合は、注釈を直接返します。

最後に、メソッドにアノテーションがない場合は、メソッドが配置されているクラスで @DataSource アノテーションを再度探して戻ります。

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 はカットポイントの処理ロジックをサラウンドエンハンスメントとして定義します 

最初のステップは、getDataSource メソッドを呼び出して、ターゲット メソッドに必要な DataSource アノテーションを取得することです。

2 番目のステップでは、注釈が空でないかどうかを判断し、DynamicDataSourceContextHolder の setDataSourceType メソッドを呼び出し、注釈値 (データ ソース タイプ) の値をそれに設定します。

3 番目のステップでは、ProceedingJoinPoint の進行メソッドを呼び出して、ターゲット メソッドを実行します。

最後に、finally で、DynamicDataSourceContextHolder の clearDataSourceType メソッドを呼び出して、スレッドローカルの 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();
     }
 }

完全なコード:

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. プロジェクトの完全なスクリーンショット

上記は複数のデータソースのコア構成を説明しました. 残りは MyBatis を統合するための日常的な操作です. これらはコード クラウドに置いているので、ここではあまり書きません. 完全なプロジェクト構築パッケージは次のとおりです.

9. 使用方法

マッパー レイヤーまたはサービス レイヤーで @DataSource カスタム アノテーションを使用して、指定したデータ ソースに切り替えます。

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

    public List<Logger> select();

}

操作の結果は次のようになります。 

10.Giteeのソースコード 

yml ファイルで独自のマスター/スレーブ データ ソースを構成し、ワンクリックでプロジェクトを開始します

プロジェクトのアドレス: SpringBoot は MyBatis を統合して MySQL の複数のデータ ソースを構築します

11. まとめ

上記は、SpringBoot が複数のデータ ソースをどのように統合するかについての私の技術分析です。これはより伝統的な方法でもあり、より複雑です。ご質問がある場合は、コメント エリアで議論してください。

おすすめ

転載: blog.csdn.net/HJW_233/article/details/132032716