Spring Boot + MyBatisでマルチデータソースで同一のMapperクラスを使用する方法

# はじめに
私が経験した案件において、
異なるデータソース、同一テーブル定義を用いたシステムを構築したので、
そのときのノウハウを共有します。

## データソース定義
ドライバは「Oracle」を使用していますが、環境に合わせて適宜修正してください。

```yaml:application.yml
spring:
  datasource:
    # データソースA
    alpha:
      jdbc-url: jdbc:oracle:thin:@localhost:1521/XEPDB1
      username: sample_a
      password: sample_a
      driverClassName: oracle.jdbc.OracleDriver
    # データソースB
    beta:
      jdbc-url: jdbc:oracle:thin:@localhost:1521/XEPDB1
      username: sample_b
      password: sample_b
      driverClassName: oracle.jdbc.OracleDriver
```

## MyBatisの設定

```java:DataSourceConfig.java
package com.example;

@Configuration
@MapperScan(basePackages = { "com.example.mapper" }, sqlSessionTemplateRef = "sqlSessionTemplate") // ①
public class DataSourceConfig {

    @Bean(name = "alphaDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.alpha")
    public DataSource alphaDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "betaDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.beta")
    public DataSource betaDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean("dynamicDataSource")
    public DynamicRoutingDataSourceResolver dynamicDataSource(@Qualifier("alphaDataSource") DataSource alphaDataSource, @Qualifier("betaDataSource") DataSource betaDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        targetDataSources.put(DataSourceType.ALPHA, alphaDataSource);
        targetDataSources.put(DataSourceType.BETA, betaDataSource);

        // データソースの動的変更
        DynamicRoutingDataSourceResolver resolver = new DynamicRoutingDataSourceResolver();
        resolver.setTargetDataSources(targetDataSources);
        resolver.setDefaultTargetDataSource(alphaDataSource);

        return resolver;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DynamicRoutingDataSourceResolver resolver) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

        // ②
        factoryBean.setDataSource(resolver);
        factoryBean.setMapperLocations("Mapper XMLのパス");
        factoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);

        return factoryBean.getObject();
    }

    // ... 省略
}
```
①： 共通使用するMapperクラスのエイリアスを設定する
②： マルチデータソースではAutoConfigureが無効になるため、手動で設定する
  ※[公式サイト](http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/)に記載があります

## データソース切替
参照データソースを判断するための区分値を定義する。

```java
public enum DataSourceType {
    ALPHA, BETA
}
```
スレッド単位で区分値を保持する。

```java:DataSourceContextHolder.java
public class DataSourceContextHolder {

    private static ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<DataSourceType>();

    public static void setDataSourceType(DataSourceType dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static DataSourceType getDataSourceType() {
        return contextHolder.get();
    }

    public static void clear() {
        contextHolder.remove();
    }
}
```
参照データソース判断用の区分値を取得する。
この区分値でDataSourceConfig.dynamicDataSourceで設定されたデータソースから、
実際に参照するデータソースを判断する。

```java:DynamicRoutingDataSourceResolver.java
public class DynamicRoutingDataSourceResolver extends AbstractRoutingDataSource {

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

## 使用方法
DataSourceContextHolder経由で、DataSourceTypeを設定しましょう。
横断処理として定義したい場合は、AOPなどを使用してデータソース切替を行ってください。

```java
DataSourceContextHolder.setDataSourceType(DataSourceType.ALPHA);
```