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

まえがき: 最近の実際の開発過程で、複数のデータソースの場合にアトミック性を確保するという問題に遭遇しました。 , この問題を解決するために、最終的に分散トランザクションを採用しました。説明する前に、前回のブログで MyBatis を統合して MySQL マルチデータ ソースを事前に構築する SpringBoot のチュートリアルをセットアップしました。このブログは元のプロジェクトに基づいています。変換は次のとおりです。主にいくつかの設定項目を追加することで実装されます。

記事リンク: [千文字の長文] SpringBoot は MyBatis を統合して、MySQL の複数のデータ ソースに関する完全なチュートリアルを構築します (Gitee ソース コードが提供されています)

記事の最後には、統合された完全なコードも提供しました。

目次

1.アトミコスとは

2.XAとは

3. プロジェクト全体の構造のスクリーンショット 

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

5. mybatis-config.xml ファイルを設定します。

6. yml ファイルを変更します。

7、構成クラス

7.1、Amitikos 構成クラス

7.2. DynamicSqlSessionTemplate はデータソース設定クラスを動的に切り替えます (重要)

7.3、Druid 設定プロパティ

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

7.5、MyBatis設定クラス

8、テストを実行します

9、Gitee ソース コード 

10. まとめ


1.アトミコスとは

Amitikos は、 Javaに基づくオープンソースの分散トランザクション ソリューションであり、 XA 仕様を実装し、分散システムにトランザクション サービスを提供できます。

Amitikosの主な機能は次のとおりです。

1. 分散トランザクションをサポートして、複数のデータ ソース間でトランザクションの一貫性を確保します。分散システムでは、業務運営に複数のデータベースまたはサービスが関与する場合があります。Amitikos は複数のデータ ソースを調整し、トランザクションをまとめて送信したりロールバックしたりできるため、分散環境でのデータの一貫性が確保されます。

2.さまざまなデータベースとトランザクションAPI をサポートAtomikos はJTA仕様をサポートしておりJTAインターフェイスを介してアプリケーションと統合できます。同時に、 MySQLPostgreSQLなどのさまざまなデータベースのサポートを提供し、 RESTトランザクションもサポートします。

3.トランザクションのACID特性を保証するAmitikos は、2 フェーズ コミット プロトコルを通じて分散トランザクションがアトミック性、一貫性、分離性の特性を満たしていることを保証し、分散トランザクション状態の不整合を防ぎます。

4.高可用性とフェイルオーバーAtomikos自体は、アクティブ モードとスタンバイ モードを設定することで高可用性を提供でき、ロード バランサーと統合してフェイルオーバーと高可用性を実現することもできます。

5.管理・監視プラットフォーム「Amitikos」は、独自の管理コンソールとログ監視機能を備え、取引情報や統計データ、稼働状況などを簡単に閲覧できます。

要約すると、Amitikos は非常に強力で成熟した分散トランザクション マネージャーであり、堅牢で信頼性の高い分散システムを構築するための主要なトランザクション保証を提供します。分散シナリオでは、トランザクション処理には Amitikos が最適であると言えます。

2.XAとは

XAは分散トランザクション仕様であり、正式名はeXtendedArchitectureですこれは、分散システム内のトランザクション マネージャー ( TM)と複数のリソース マネージャー ( RM)の間のインターフェイスを定義します。これにより、複数のリソースにわたるトランザクションの調整された管理が可能になります。

主な特徴:

1. 複数のデータベース間の分散トランザクション管理をサポートします。

2. トランザクション処理は ACID の特性に準拠します。

3. TransactionManager を通じてトランザクションをスケジュールします。

4. XA データ ソース (XADataSource) を通じてデータベースのトランザクション動作を抽象化します。

5. 2 フェーズ コミット プロトコル (2PC) を通じて分散トランザクションの一貫性を確保します。

その主要な役割には以下が含まれます。

1. トランザクション マネージャー (TM): 複数のデータベースのトランザクションを調整し、グローバル トランザクションを管理します。

2. アプリケーション プログラム (AP): XA インターフェイスを介して TM と対話し、グローバル トランザクションを駆動します。

3. リソース マネージャー (RM): トランザクションの永続性を保証するデータベースの抽象化。

4. XA データ ソース: XA インターフェイスを実現し、RM をカプセル化し、データベースが TM と対話できるようにします。

XA (Amitikos など) の実装は、分散トランザクションを実現するための重要な方法の 1 つである 2 フェーズ コミットを通じて、分散トランザクションの一貫性の問題を十分に解決できます。

したがって、 Amitikos はXA仕様を実装することで分散トランザクション ソリューションを提供していると言えます。XA標準を定義し、Amitikos はこの標準を使用して製品を構築します。この 2 つは分散トランザクションの分野における重要な基礎です。

3. プロジェクト全体の構造のスクリーンショット 

これは、元のマルチデータ ソース フレームワークを変換しただけです。

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

<!-- atomikos分布式事务 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
    <version>2.7.14</version>
</dependency>

5. mybatis-config.xml ファイルを設定します。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 全局参数 -->
    <settings>
        <!-- 使全局的映射器启用或禁用缓存 -->
        <setting name="cacheEnabled"             value="true"   />
        <!-- 允许JDBC 支持自动生成主键 -->
        <setting name="useGeneratedKeys"         value="true"   />
        <!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 -->
        <setting name="defaultExecutorType"      value="SIMPLE" />
		<!-- 指定 MyBatis 所用日志的具体实现 -->
        <setting name="logImpl"                  value="SLF4J"  />
        <!-- 使用驼峰命名法转换字段 -->
		<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> -->
	</settings>
	
</configuration>

これがディレクトリ構造ですが、リソースディレクトリの下にmybatisディレクトリを新規作成します。

6. yml ファイルを変更します。

主な変更点は MyBatis の構成で、残りは変更されません。

# MyBatis配置
mybatis:
  # 搜索指定包别名
  typeAliasesPackage: com.example.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath:mapper/*/*.xml
  # 加载全局的配置文件
  configLocation: classpath:mybatis/mybatis-config.xml

7、構成クラス

5 つの構成クラスの機能とそれらの間の接続を簡単に整理してみましょう。

1、ドルイドのプロパティ:

これは、Druid データベース接続プールの構成属性クラスであり、各データ ソースの URL、ユーザー名、その他の情報を定義します。

2、アトミコス設定:

これは、Amitikos トランザクション マネージャーの構成クラスであり、Amitikos トランザクション マネージャー AtomikosJtaTransactionManager を作成します。

3、DruidConfig:

このクラスは、DruidProperties を使用してマスター/スレーブ データ ソース masterDataSource およびslaveDataSource を作成し、それらを AtomikosDataSourceBean でカプセル化します。

4、MyBatisConfig:

これは MyBatis の構成クラスであり、masterDataSource とslaveDataSource を使用して 2 つの SqlSessionFactory をそれぞれ作成します。

5、動的Sqlセッションテンプレート:

このクラスはSpringのSqlSessionTemplateを継承しており、DynamicSqlSessionTemplateは各種データソースに対応したSqlSessionFactoryを格納するためのMapを内部に保持している。SQL を実行する前に、現在のスレッドにバインドされているデータ ソース情報が最初に取得されます。現在のデータ ソースに従って、マップから対応する SqlSessionFactory を取得し、この SqlSessionFactory を使用して SQL を実行します。

現在のスレッドによってバインドされたデータ ソース情報は、DynamicDataSource を通じて実現されます。DynamicDataSource は、さまざまなデータ ソース構成を保存するためにマップを内部的に維持します。SQL を実行する前に、DynamicDataSource は現在のデータ ソース情報をスレッドにバインドします。その後、DynamicSqlSessionTemplate はスレッドから現在のデータ ソースを取得し、対応する SqlSessionFactory を選択して SQL を実行します。

一般に、DynamicDataSourceはデータソースの切り替えを実装してスレッドにバインドし、DynamicSqlSessionTemplateはDynamicDataSourceで切り替えたデータソースに基づいてSQLを実行するSqlSessionFactoryを動的に選択し、両者が連携して動的マルチデータソースの機能を実現します。

最後にまとめると、DruidConfig 設定に登録されている 2 つのマスター/スレーブ データ ソースは、DruidProperties 設定のリンク情報と AtomikosConfig で設定された Amitikos で管理され、MyBatisConfig がマスター/スレーブの SqlSessionFactory を作成し、DynamicSqlSessionTemplate が DynamicDataSource と連携して動的にマスター/スレーブ データを切り替えます。ソースを取得し、現在切り替えられているデータ ソースの SqlSessionFactory を取得して、トランザクション管理 + 動的な複数のデータ ソースの効果を実現します。

7.1、Amitikos 構成クラス

1. この Bean は、Begin/Commit/Rollback トランザクション用の JTA の UserTransaction インターフェースを実装する UserTransactionImp を作成します。

    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable
    {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

2. これは、JTA の TransactionManager インターフェイスを実装する Amitikos の UserTransactionManager のインスタンスです。TransactionManager は、分散トランザクションを実装するために、複数のリソース (データ ソース) へのトランザクション アクセスを調整する役割を果たします。ここでは、Amitikos トランザクション マネージャーを初期化して閉じるために、init メソッドと destroy メソッドが構成されています。

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable
    {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

3. これは、Spring の JTA トランザクション管理の実装である JtaTransactionManager のインスタンスです。これは、上記の 2 つの Bean、userTransaction と atomikosTransactionManager に依存します。userTransaction は、トランザクション管理用のインターフェイスを提供します。atomikosTransactionManager は、基礎となるトランザクション調整機能を提供します。JtaTransactionManager は 2 つを組み合わせて、Spring アプリケーションに JTA トランザクション管理を提供します。

    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable
    {
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }

したがって、これら 3 つの Bean は共同で JTA トランザクション管理を実装しており、このうち userTransaction と atomikosTransactionManager は JTA 標準インターフェースの実装を提供し、transactionManager はそれらを組み合わせて Spring アプリケーション向けのトランザクション管理サービスを提供します。

完全なコード:

package com.example.multiple.config;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

/**
 * JTA 事务配置
 *
 */
@Configuration
public class AtomikosConfig
{
    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable
    {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable
    {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable
    {
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }
}

7.2. DynamicSqlSessionTemplate はデータソース設定クラスを動的に切り替えます (重要)

前回の記事では動的データソース切り替えクラスDynamicDataSourceもありましたので、まずはそれらの違いを説明します。

1. DynamicDataSource: データソースを動的に切り替える機能を実装するクラスです。AbstractRoutingDataSource を継承し、determineCurrentLookupKey() メソッドにデータ ソースを切り替えるロジックを追加し、ThreadLocal 変数のデータ ソース キーに従って使用するデータ ソースを動的に設定します。これは、データ ソースを動的に切り替えるための核心です。

2. DynamicSqlSessionTemplate: データソースに応じて SqlSessionFactory を選択する機能を実現するクラスです。さまざまなデータ ソースに対応する SqlSessionFactory を格納するマップを維持します。SqlSessionFactory を取得するときは、まず DynamicDataSourceContextHolder を呼び出して現在のスレッドのデータ ソース キーを取得し、次にマップから対応する SqlSessionFactory を取得します。

3. 関係: DynamicSqlSessionTemplate は、DynamicDataSource によって提供されるデータ ソース キーに依存し、キーに従って SqlSessionFactory を動的に選択します。ただし、データ ソースを切り替えるロジックを単独で処理することはできません。つまり、DynamicDataSource に依存しますが、2 つの関数は異なります。

4. 目的:両者を組み合わせることで同一スレッドを実現し、データソースに応じて異なるSqlSessionFactoryやデータベースを切り替えることができます。データ ソースの動的な切り替えを実現するだけでなく、データ ソースに基づいて SqlSessionFactory を選択し、同じスレッドで複数のデータベースを使用する効果を実現します。

概要: DynamicDataSource はデータ ソースの切り替えを実装し、DynamicSqlSessionTemplate は切り替えられたデータ ソースに基づいて SqlSessionFactory を選択します。この 2 つは相互に依存して、真の動的なマルチデータ ソースを実現します。 

これは主要なコードであり、Mybatis 構成で SqlSessionFactory を構築する場合、Amitikos と DynamicDataSource によって管理される複数のマスター/スレーブ データ ソースがコンストラクターに渡されます。

public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
{
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys)
{
    this.targetSqlSessionFactorys = targetSqlSessionFactorys;
}

完全なコード:

package com.example.multiple.config.datasource;

import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.*;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.dao.support.PersistenceExceptionTranslator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

import static java.lang.reflect.Proxy.newProxyInstance;
import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
import static org.mybatis.spring.SqlSessionUtils.*;


/**
 * 自定义SqlSessionTemplate,动态切换数据源
 */
public class DynamicSqlSessionTemplate extends SqlSessionTemplate
{
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;
    private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;
    private SqlSessionFactory defaultTargetSqlSessionFactory;

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
    {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType)
    {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(
                sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                                     PersistenceExceptionTranslator exceptionTranslator)
    {
        super(sqlSessionFactory, executorType, exceptionTranslator);
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
                new Class[] { SqlSession.class }, new SqlSessionInterceptor());
        this.defaultTargetSqlSessionFactory = sqlSessionFactory;
    }

    public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys)
    {
        this.targetSqlSessionFactorys = targetSqlSessionFactorys;
    }

    public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory)
    {
        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
    }

    @Override
    public SqlSessionFactory getSqlSessionFactory()
    {
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys
                .get(DynamicDataSourceContextHolder.getDataSourceType());
        if (targetSqlSessionFactory != null)
        {
            return targetSqlSessionFactory;
        }
        else if (defaultTargetSqlSessionFactory != null)
        {
            return defaultTargetSqlSessionFactory;
        }
        return this.sqlSessionFactory;
    }

    @Override
    public Configuration getConfiguration()
    {
        return this.getSqlSessionFactory().getConfiguration();
    }

    public ExecutorType getExecutorType()
    {
        return this.executorType;
    }

    public PersistenceExceptionTranslator getPersistenceExceptionTranslator()
    {
        return this.exceptionTranslator;
    }

    /**
     * {@inheritDoc}
     */
    public <T> T selectOne(String statement)
    {
        return this.sqlSessionProxy.<T> selectOne(statement);
    }

    /**
     * {@inheritDoc}
     */
    public <T> T selectOne(String statement, Object parameter)
    {
        return this.sqlSessionProxy.<T> selectOne(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, String mapKey)
    {
        return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey)
    {
        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds)
    {
        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement)
    {
        return this.sqlSessionProxy.<E> selectList(statement);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement, Object parameter)
    {
        return this.sqlSessionProxy.<E> selectList(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)
    {
        return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("rawtypes")
    public void select(String statement, ResultHandler handler)
    {
        this.sqlSessionProxy.select(statement, handler);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("rawtypes")
    public void select(String statement, Object parameter, ResultHandler handler)
    {
        this.sqlSessionProxy.select(statement, parameter, handler);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("rawtypes")
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler)
    {
        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
    }

    /**
     * {@inheritDoc}
     */
    public int insert(String statement)
    {
        return this.sqlSessionProxy.insert(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int insert(String statement, Object parameter)
    {
        return this.sqlSessionProxy.insert(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public int update(String statement)
    {
        return this.sqlSessionProxy.update(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int update(String statement, Object parameter)
    {
        return this.sqlSessionProxy.update(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public int delete(String statement)
    {
        return this.sqlSessionProxy.delete(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int delete(String statement, Object parameter)
    {
        return this.sqlSessionProxy.delete(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <T> T getMapper(Class<T> type)
    {
        return getConfiguration().getMapper(type, this);
    }

    /**
     * {@inheritDoc}
     */
    public void commit()
    {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void commit(boolean force)
    {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void rollback()
    {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void rollback(boolean force)
    {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void close()
    {
        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void clearCache()
    {
        this.sqlSessionProxy.clearCache();
    }

    /**
     * {@inheritDoc}
     */
    public Connection getConnection()
    {
        return this.sqlSessionProxy.getConnection();
    }

    /**
     * {@inheritDoc}
     *
     * @since 1.0.2
     */
    public List<BatchResult> flushStatements()
    {
        return this.sqlSessionProxy.flushStatements();
    }

    /**
     * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
     * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
     * the {@code PersistenceExceptionTranslator}.
     */
    private class SqlSessionInterceptor implements InvocationHandler
    {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            final SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
                    DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator);
            try
            {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory()))
                {
                    sqlSession.commit(true);
                }
                return result;
            }
            catch (Throwable t)
            {
                Throwable unwrapped = unwrapThrowable(t);
                if (DynamicSqlSessionTemplate.this.exceptionTranslator != null
                        && unwrapped instanceof PersistenceException)
                {
                    Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
                            .translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null)
                    {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            }
            finally
            {
                closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
            }
        }
    }
}

7.3、Druid 設定プロパティ

一部のパラメーターは yml ファイルから読み取られ、特定のコメント コードがマークされるため、ここではこれ以上の説明は行いません。

完全なコード:

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

    public int getInitialSize()
    {
        return initialSize;
    }

    public void setInitialSize(int initialSize)
    {
        this.initialSize = initialSize;
    }

    public int getMinIdle()
    {
        return minIdle;
    }

    public void setMinIdle(int minIdle)
    {
        this.minIdle = minIdle;
    }

    public int getMaxActive()
    {
        return maxActive;
    }

    public void setMaxActive(int maxActive)
    {
        this.maxActive = maxActive;
    }

    public int getMaxWait()
    {
        return maxWait;
    }

    public void setMaxWait(int maxWait)
    {
        this.maxWait = maxWait;
    }

    public int getConnectTimeout()
    {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout)
    {
        this.connectTimeout = connectTimeout;
    }

    public int getSocketTimeout()
    {
        return socketTimeout;
    }

    public void setSocketTimeout(int socketTimeout)
    {
        this.socketTimeout = socketTimeout;
    }

    public int getTimeBetweenEvictionRunsMillis()
    {
        return timeBetweenEvictionRunsMillis;
    }

    public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis)
    {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public int getMinEvictableIdleTimeMillis()
    {
        return minEvictableIdleTimeMillis;
    }

    public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis)
    {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public int getMaxEvictableIdleTimeMillis()
    {
        return maxEvictableIdleTimeMillis;
    }

    public void setMaxEvictableIdleTimeMillis(int maxEvictableIdleTimeMillis)
    {
        this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis;
    }

    public String getValidationQuery()
    {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery)
    {
        this.validationQuery = validationQuery;
    }

    public boolean isTestWhileIdle()
    {
        return testWhileIdle;
    }

    public void setTestWhileIdle(boolean testWhileIdle)
    {
        this.testWhileIdle = testWhileIdle;
    }

    public boolean isTestOnBorrow()
    {
        return testOnBorrow;
    }

    public void setTestOnBorrow(boolean testOnBorrow)
    {
        this.testOnBorrow = testOnBorrow;
    }

    public boolean isTestOnReturn()
    {
        return testOnReturn;
    }

    public void setTestOnReturn(boolean testOnReturn)
    {
        this.testOnReturn = testOnReturn;
    }
}

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

以前に構築したマルチデータ ソース プロジェクトと比較した主な変更点は次のとおりです。

1. Druid データ ソース プロパティを構築します。

    protected Properties build(Environment env, String prefix)
    {
        Properties prop = new Properties();
        prop.put("url", env.getProperty(prefix + "url"));
        prop.put("username", env.getProperty(prefix + "username"));
        prop.put("password", env.getProperty(prefix + "password"));
        prop.put("initialSize", druidProperties.getInitialSize());
        prop.put("minIdle", druidProperties.getMinIdle());
        prop.put("maxActive", druidProperties.getMaxActive());
        prop.put("maxWait", druidProperties.getMaxWait());
        prop.put("timeBetweenEvictionRunsMillis", druidProperties.getTimeBetweenEvictionRunsMillis());
        prop.put("minEvictableIdleTimeMillis", druidProperties.getMinEvictableIdleTimeMillis());
        prop.put("maxEvictableIdleTimeMillis", druidProperties.getMaxEvictableIdleTimeMillis());
        prop.put("validationQuery", druidProperties.getValidationQuery());
        prop.put("testWhileIdle", druidProperties.isTestWhileIdle());
        prop.put("testOnBorrow", druidProperties.isTestOnBorrow());
        prop.put("testOnReturn", druidProperties.isTestOnReturn());
        return prop;
    }

 2. AtomikosDataSourceBean のデータ ソース インスタンスを作成します。

具体的なロジック:

1. build()メソッドを呼び出して、Druidデータソースの属性構成を構築します

2. AtomikosDataSourceBeanオブジェクトds作成します

3. XAデータ ソースの完全なクラス名をDruidXADataSource として設定しますつまり、Druid を接続プールとして使用します。

4.最大接続数、最小接続数などの接続プール制限構成を追加します。

5.データ ソースの名前であるuniqueResourceNameを設定します。

6. Druid属性XA属性設定します

7.構築されたAtomikosDataSourceBeanインスタンスを返します

AtomikosDataSourceBeanは、Amitikosが提供するXAデータ ソース実装であり、共通接続プールのデータ ソースをカプセル化し、分散トランザクションの機能を持ちます。ここではDruid が基礎となる接続プールとして使用され、XAトランザクション管理はAtomikosを通じて実行されます

    protected DataSource getDataSource(Environment env, String prefix, String dataSourceName)
    {
        Properties prop = build(env, prefix);
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        // 添加连接池限制
        ds.setMaxPoolSize(50);
        ds.setMinPoolSize(5);
        ds.setBorrowConnectionTimeout(60);
        ds.setUniqueResourceName(dataSourceName);
        ds.setXaProperties(prop);
        return ds;
    }

3. マスター/スレーブ データ ソース Bean を作成します。

@DependsOn アノテーションは、Bean がtransactionManager Bean に依存していることを示します。

@ConfigurationProperties アノテーションは、「spring.datasource.druid.master」というプレフィックスが付いた構成をロードします。

 getDataSource メソッドは、DruidDataSourceFactory を使用して Druid データ ソースを作成し、パラメーターを構成します。

    @Bean
    @DependsOn({"transactionManager"})
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(Environment env)
    {
        String prefix = "spring.datasource.druid.master.";
        return getDataSource(env, prefix, MASTER);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @DependsOn({"transactionManager"})
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(Environment env)
    {
        String prefix = "spring.datasource.druid.slave.";
        return getDataSource(env, prefix, SLAVE);
    }

完全なコード:

package com.example.multiple.config;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.*;
import javax.sql.DataSource;

import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.atomikos.jdbc.AtomikosDataSourceBean;
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.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.core.env.Environment;

@Configuration
public class DruidConfig
{
    public static final String MASTER = DataSourceType.MASTER.name();

    public static final String SLAVE = DataSourceType.SLAVE.name();

    @Autowired
    private DruidProperties druidProperties;

    @Bean
    @DependsOn({"transactionManager"})
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(Environment env)
    {
        String prefix = "spring.datasource.druid.master.";
        return getDataSource(env, prefix, MASTER);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @DependsOn({"transactionManager"})
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(Environment env)
    {
        String prefix = "spring.datasource.druid.slave.";
        return getDataSource(env, prefix, SLAVE);
    }

    protected DataSource getDataSource(Environment env, String prefix, String dataSourceName)
    {
        Properties prop = build(env, prefix);
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        // 添加连接池限制
        ds.setMaxPoolSize(50);
        ds.setMinPoolSize(5);
        ds.setBorrowConnectionTimeout(60);
        ds.setUniqueResourceName(dataSourceName);
        ds.setXaProperties(prop);
        return ds;
    }

    protected Properties build(Environment env, String prefix)
    {
        Properties prop = new Properties();
        prop.put("url", env.getProperty(prefix + "url"));
        prop.put("username", env.getProperty(prefix + "username"));
        prop.put("password", env.getProperty(prefix + "password"));
        prop.put("initialSize", druidProperties.getInitialSize());
        prop.put("minIdle", druidProperties.getMinIdle());
        prop.put("maxActive", druidProperties.getMaxActive());
        prop.put("maxWait", druidProperties.getMaxWait());
        prop.put("timeBetweenEvictionRunsMillis", druidProperties.getTimeBetweenEvictionRunsMillis());
        prop.put("minEvictableIdleTimeMillis", druidProperties.getMinEvictableIdleTimeMillis());
        prop.put("maxEvictableIdleTimeMillis", druidProperties.getMaxEvictableIdleTimeMillis());
        prop.put("validationQuery", druidProperties.getValidationQuery());
        prop.put("testWhileIdle", druidProperties.isTestWhileIdle());
        prop.put("testOnBorrow", druidProperties.isTestOnBorrow());
        prop.put("testOnReturn", druidProperties.isTestOnReturn());
        return prop;
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(MASTER, masterDataSource);
        setDataSource(targetDataSources, SLAVE, "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)
        {
        }
    }

}


7.5、MyBatis設定クラス

1. 定数 - マッパーインターフェイスのスキャンパスを設定します。

static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

2. MyBatis のタイプ エイリアスを自動的に設定するために、指定されたパッケージ パスをスキャンしてパッケージ内のすべてのクラスを取得します。

具体的なロジック:

1. ResourcePatternResolverを通じて、指定されたパッケージ パスにあるすべてのクラスファイル リソースをスキャンします

2.クラスリソースについて、MetadataReaderを使用してクラス情報を読み取り、完全なクラス名を取得します。

3. Class. forNameを通じてクラスを取得しgetPackage()およびgetName ()を呼び出してパッケージ名を取得します。

4.スキャンされたすべてのパッケージ名をリストに保存します

5.重複を避けるために、 Setを使用してパッケージ名の重複を排除します。

6.パッケージ名の配列を文字列に結合し、それをtypeAliasesPackageに割り当てます

7.スキャンできるパッケージがない場合は 、例外がスローされます。

このようにして、指定されたパッケージ パスにあるすべてのクラスをスキャンでき、それらのパッケージ名が自動的に収集され、MyBatistypeAliasesPackageに設定されます

MyBatis は、これらのパッケージ名のクラスをタイプ エイリアスとして自動的に登録します。完全修飾クラス名を記述することなく、Mapperマッピングファイルでクラス名を直接使用できます。この方法では、構成が大幅に簡素化され、より柔軟になるため、新しいサブパッケージまたはクラスを追加するときに、typeAliases構成を手動で保守する必要がありません。

    public static String setTypeAliasesPackage(String typeAliasesPackage)
    {
        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
        List<String> allResult = new ArrayList<String>();
        try
        {
            for (String aliasesPackage : typeAliasesPackage.split(","))
            {
                List<String> result = new ArrayList<String>();
                aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                        + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
                Resource[] resources = resolver.getResources(aliasesPackage);
                if (resources != null && resources.length > 0)
                {
                    MetadataReader metadataReader = null;
                    for (Resource resource : resources)
                    {
                        if (resource.isReadable())
                        {
                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
                            try
                            {
                                result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
                            }
                            catch (ClassNotFoundException e)
                            {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                if (result.size() > 0)
                {
                    HashSet<String> hashResult = new HashSet<String>(result);
                    allResult.addAll(hashResult);
                }
            }
            if (allResult.size() > 0)
            {
                typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
            }
            else
            {
                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return typeAliasesPackage;
    }

2. MyBatis の Mapper インターフェイスの場所を分析し、文字列の場所を Resource リソース オブジェクトに変換するために使用されます。

具体的なロジック:

1. ANT スタイルのリソース パスと一致する PathMatchingResourcePatternResolver オブジェクトを作成します。

2. 指定された MapperLocations 配列に従って、文字列パスを 1 つずつ解析します。

3. getResources() を使用して、パスに対応する Resource リソース配列を取得します。

4. 解決されたすべてのリソース リソースをリストに追加します。

5. 最後に、Resource 配列に変換されて返されます。

この利点は、MyBatis 設定ファイルで「classpath*:com/my/mappers/**/*.xml」のようなワイルドカード パスを使用できることです。

public Resource[] resolveMapperLocations(String[] mapperLocations)
    {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        List<Resource> resources = new ArrayList<Resource>();
        if (mapperLocations != null)
        {
            for (String mapperLocation : mapperLocations)
            {
                try
                {
                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                }
                catch (IOException e)
                {
                    // ignore
                }
            }
        }
        return resources.toArray(new Resource[resources.size()]);
    }

3. SqlSessionFactory を作成します。

具体的なロジック:

1.Environment 環境変数から関連する構成を取得します。  

typeAliasesPackage: タイプ エイリアス パッケージ    

mapperLocations: マッパーインターフェイスの場所    

configLocation: MyBatis グローバル構成ファイル

2. typeAliasesPackage をスキャンして分析し、エイリアスを自動的に設定するために使用されるパッケージ名の配列に変換します。

3. SpringBootVFS を追加し、MyBatis の VFS 拡張インターフェイスを統合します。

4. SqlSessionFactoryBean のインスタンスを作成します。

5. データソース DataSource を設定します。

6. typeAliasesPackage を設定します。

7. MapperLocations を解析して Resource 配列にします。

8. MyBatis グローバル構成ファイルの場所を設定します。

9. getObject() メソッドを呼び出して、SqlSessionFactory インスタンスを取得します。

    public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception
    {
        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
        String mapperLocations = env.getProperty("mybatis.mapperLocations");
        String configLocation = env.getProperty("mybatis.configLocation");
        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);

        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        return sessionFactory.getObject();
    }

4. @Qualifier を介してAtomikosDataSourceBeanのマスター/スレーブ データ ソース インスタンスをDruidConfig 構成に挿入し、マスター/スレーブ データ ソースに対応する SqlSessionFactory インスタンスを作成します。

    @Bean(name = "sqlSessionFactoryMaster")
    public SqlSessionFactory sqlSessionFactoryMaster(Environment env, @Qualifier("masterDataSource") DataSource dataSource) throws Exception
    {
        return createSqlSessionFactory(env, dataSource);
    }

    @Bean(name = "sqlSessionFactorySlave")
    public SqlSessionFactory sqlSessionFactorySlave(Environment env, @Qualifier("slaveDataSource") DataSource dataSource) throws Exception
    {
        return createSqlSessionFactory(env, dataSource);
    }

5. データソースの動的切り替えをサポートできるDynamicSqlSessionTemplateのBeanを作成します。

具体的なロジック:

1. マスターとスレーブの 2 つの SqlSessionFactory を受信します。

2. HashMap を作成します。キーはデータ ソースの名前、値は SqlSessionFactory です。

3. 新しい DynamicSqlSessionTemplate インスタンスを作成し、それをメイン ライブラリの SqlSessionFactory に渡します。

4. setTargetSqlSessionFactorys を通じて複数の SqlSessionFactory を設定します。

これにより、マスター SqlSessionFactory とスレーブ SqlSessionFactory を含む動的な SqlSessionTemplate が作成されます。

    @Bean(name = "sqlSessionTemplate")
    public DynamicSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryMaster") SqlSessionFactory factoryMaster,
                                                        @Qualifier("sqlSessionFactorySlave") SqlSessionFactory factorySlave) throws Exception
    {
        Map<Object, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
        sqlSessionFactoryMap.put(DruidConfig.MASTER, factoryMaster);
        sqlSessionFactoryMap.put(DruidConfig.SLAVE, factorySlave);

        DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster);
        customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
        return customSqlSessionTemplate;
    }

完全なコード: 

package com.example.multiple.config;

import com.example.multiple.config.datasource.DynamicSqlSessionTemplate;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.*;

/**
 * Mybatis支持*匹配扫描包
 *
 * @author ruoyi
 */
@Configuration
public class MyBatisConfig
{
    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    public static String setTypeAliasesPackage(String typeAliasesPackage)
    {
        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
        List<String> allResult = new ArrayList<String>();
        try
        {
            for (String aliasesPackage : typeAliasesPackage.split(","))
            {
                List<String> result = new ArrayList<String>();
                aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                        + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
                Resource[] resources = resolver.getResources(aliasesPackage);
                if (resources != null && resources.length > 0)
                {
                    MetadataReader metadataReader = null;
                    for (Resource resource : resources)
                    {
                        if (resource.isReadable())
                        {
                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
                            try
                            {
                                result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
                            }
                            catch (ClassNotFoundException e)
                            {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                if (result.size() > 0)
                {
                    HashSet<String> hashResult = new HashSet<String>(result);
                    allResult.addAll(hashResult);
                }
            }
            if (allResult.size() > 0)
            {
                typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
            }
            else
            {
                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return typeAliasesPackage;
    }

    public Resource[] resolveMapperLocations(String[] mapperLocations)
    {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        List<Resource> resources = new ArrayList<Resource>();
        if (mapperLocations != null)
        {
            for (String mapperLocation : mapperLocations)
            {
                try
                {
                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                }
                catch (IOException e)
                {
                    // ignore
                }
            }
        }
        return resources.toArray(new Resource[resources.size()]);
    }

    public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception
    {
        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
        String mapperLocations = env.getProperty("mybatis.mapperLocations");
        String configLocation = env.getProperty("mybatis.configLocation");
        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
        VFS.addImplClass(SpringBootVFS.class);

        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        return sessionFactory.getObject();
    }

    @Bean(name = "sqlSessionFactoryMaster")
    public SqlSessionFactory sqlSessionFactoryMaster(Environment env, @Qualifier("masterDataSource") DataSource dataSource) throws Exception
    {
        return createSqlSessionFactory(env, dataSource);
    }

    @Bean(name = "sqlSessionFactorySlave")
    public SqlSessionFactory sqlSessionFactorySlave(Environment env, @Qualifier("slaveDataSource") DataSource dataSource) throws Exception
    {
        return createSqlSessionFactory(env, dataSource);
    }

    @Bean(name = "sqlSessionTemplate")
    public DynamicSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryMaster") SqlSessionFactory factoryMaster,
                                                        @Qualifier("sqlSessionFactorySlave") SqlSessionFactory factorySlave) throws Exception
    {
        Map<Object, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
        sqlSessionFactoryMap.put(DruidConfig.MASTER, factoryMaster);
        sqlSessionFactoryMap.put(DruidConfig.SLAVE, factorySlave);

        DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster);
        customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
        return customSqlSessionTemplate;
    }
}

8、テストを実行します

私は単にサービス クラスを作成し、挿入の最後にゼロ例外を手動でスローしました。

    @Transactional(rollbackFor = Exception.class)
    public void handle(){
        Log log = new Log();
        log.setContent("主数据源");
        masterMapper.insert(log);
        Logger logger = new Logger();
        logger.setContent("从数据库源");
        slaveMapper.insert(logger);
        int a = 1/0;
    }

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

データベースの状況:

すべてロールバックされました、問題ありません。

9、Gitee ソース コード 

ソースコードアドレス: SpringBoot は Atomikos を統合してマルチデータソース分散トランザクションを実現します

10. まとめ

以上が、SpringBoot が Amitikos を統合してマルチデータソース分散トランザクションを実現することについての私の現在の運用プロセスと個人的な理解です。

おすすめ

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