複数のDBへ1つのEntityから参照する（SpringBoot+JPA）

テーブル名、構成が全く同じ複数DB（シャーディングなどしているような場合）
に対してSpringBootから参照する。


## 参考
以下を参考にさせていただきました。
**[Spring Boot] 動的データソース変更**
https://qiita.com/syukai/items/0d4bf27f82fef9965cdd
## やること
・複数のDBを参照する（登録、更新はなし）
・テーブル構成が同じなので１つのEntityを使い回す
・リクエストのクエリ文字列によって参照先DBを切り替える
## 作成／修正クラス
・Config
・DynamicRoutingDataSourceResolver
・ContextHolder
・DBの切り替え
## 修正
**Congfig**

```java:DataSourceConfig.java
package src.main.java.com.example.demo;

import java.util.Map;
import java.util.HashMap;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
@EnableJpaRepositories(
  basePackages = "jp.co.example.repositories",
  entityManagerFactoryRef = "entityManagerFactory"
)
public class DataSourceConfig {
  public HikariDataSource Node1() {
    HikariDataSource ds = new HikariDataSource();
    ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
    ds.setJdbcUrl("jdbc:mysql://XXX.XXX.XXX.XXX/node1");
    ds.setUsername("");
    ds.setPassword("");
    return ds;
  }

  public HikariDataSource Node2() {
    HikariDataSource ds = new HikariDataSource();
    ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
    ds.setJdbcUrl("jdbc:mysql://XXX.XXX.XXX.XXX/node2");
    ds.setUsername("");
    ds.setPassword("");
    return ds;
  }

  @Bean(name = "datasource")
  @Primary
  public DynamicRoutingDataSourceResolver datasource() {
    DynamicRoutingDataSourceResolver resolver = new DynamicRoutingDataSourceResolver();
    Map<Object, Object> dataSources = new HashMap<>();
    dataSources.put("node1", Node1());
    dataSources.put("node2", Node2());
    resolver.setTargetDataSources(dataSources);
    resolver.setDefaultTargetDataSource(Node1());
    return resolver;
  }

  @Bean(name = "datasource")
  public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder bulder, @Qualifier("datasource") DataSource dataSource) {
    return bulder.dataSource(dataSource).packages("jp.co.example.entity").build();
  }
}
```

**DynamicRoutingDataSourceResolver**

```java:DynamicRoutingDataSourceResolver.java
package src.main.java.com.example.demo;

import java.util.Objects;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicRoutingDataSourceResolver extends AbstractRoutingDataSource {
  @Override
  protected Object determineCurrentLookupKey() {
    if (Objects.equals("0", SchemaContextHolder.getSchemaType())) {
      return "node1";
    } else {
      return "node2";
    }
  }
}
```

**SchemaContextHolder**

```java:SchemaContextHolder.java
package src.main.java.com.example.demo;

public class SchemaContextHolder {
  private static ThreadLocal<String> contextHolder = new ThreadLocal<>();
  public static void setSchemaType(String partitionkey) { contextHolder.set(partitionkey); }
  public static String getSchemaType() { return contextHolder.get(); }
}
```

**DBの切り替え**

```java
package src.main.java.com.example.demo;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import src.main.java.com.example.demo.User;

public class DbDao implements Db {
  @PersistenceContext
  private EntityManager entityManager;

  public List<User> getUsers(String partitionkey) {
    SchemaContextHolder.setSchemaType(partitionkey);
    return entityManager.createNativeQuery("SELECT * FROM USERS;", User.class).getResultList();
  }
}
```