Spring BootでJPAを使用した複数データベース接続

Spring Bootを使用しているとデータベースへの接続も簡単にできますよね！
ただ簡単にできるからこそ、ちょっと変わったことをやろうとすると意外と苦労することがありますね。

今回はSpring BootでJPAを使用して複数データベースに接続する方法について書きます。

# 概要

1. Spring Initializrでモジュールの導入
2. Entitiyの定義
3. Repositoryの定義
4. Configurationの定義
5. Flywayの追加設定（補足編）

# 1. Spring Initializrでモジュールの導入
まずは[Spring Initializr](http://start.spring.io/)を使用してモジュールを作成します。
最近はIDEでプロジェクトを新規作成するときにSpring Initializrを選択できるようになっています。（私が使用しているIntelliJは対応済み）

導入したモジュールは以下。
データベースは環境に合わせて適切なものを入れてください。

* JPA
* H2 -> 複数データベース用
* HSQLDB -> 複数データベース用
* DevTools
* Lombok
* Web -> H2 console画面を触れるように。通常不要。
* Flyway -> 補足編で使用するため。通常は不要。

# 2. Entitiyの定義

それではJPAを定義していきましょう。まずはEntityです。
ポイントは**データベースごとにパッケージを分けること**です。
パッケージに分けることで、Configurationの設定をシンプルにできます。
（逆にパッケージ以外は特筆すべきことはないので、解説は省略します。）

これからメインのDBをprimary、サブのDBをsecondaryと呼び、prefixやパッケージはそれで分けていきます。

#### primary用

```
package aha.oretama.jp.entity.primary;

import ...

@Entity
@Data
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id;
  private String name;
}
```

#### secondary用

```
package aha.oretama.jp.entity.secondary;

import ...

@Entity
@Data
public class Book {
  @Id
  private String isbn;
  private String title;
}
```


# 3. Repositoryの定義

Repositoryのポイントも同じく**データベースごとにパッケージを分けること**です。

#### primary用
```
package aha.oretama.jp.repository.primary;

import ...;

public interface UserRepository extends JpaRepository<User,Integer>{
}
```

#### secondary用
```
package aha.oretama.jp.repository.secondary;

import ...

public interface BookRepository extends JpaRepository<Book,String> {
}
```

# 4. Configurationの定義

解説すべきことは多くあるので、まずはソースをご覧ください。

#### primary用

```
@Configuration
@EnableJpaRepositories(
    basePackages = "aha.oretama.jp.repository.primary",
    entityManagerFactoryRef = "primaryEntityManager",
    transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDataSourceConfiguration {

  @Bean
  @Primary
  @ConfigurationProperties(prefix = "spring.datasource.primary")
  public DataSourceProperties primaryProperties() {
    return new DataSourceProperties();
  }

  @Bean
  @Primary
  @Autowired
  public DataSource primaryDataSource(@Qualifier("primaryProperties")
      DataSourceProperties properties) {
    return properties.initializeDataSourceBuilder().build();
  }

  @Bean
  @Primary
  @Autowired
  public LocalContainerEntityManagerFactoryBean primaryEntityManager(EntityManagerFactoryBuilder builder,@Qualifier("primaryDataSource") DataSource dataSource){
    return builder.dataSource(dataSource)
        .packages("aha.oretama.jp.entity.primary")
        .persistenceUnit("primary")
        .build();
  }


  @Bean
  @Primary
  @Autowired
  public JpaTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManager") LocalContainerEntityManagerFactoryBean primaryEntityManager) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(primaryEntityManager.getObject());
    return transactionManager;
  }
}
```

#### secondary用

```
@Configuration
@EnableJpaRepositories(
    basePackages = "aha.oretama.jp.repository.secondary",
    entityManagerFactoryRef = "secondaryEntityManager",
    transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryDataSourceConfiguration {

  @Bean
  @ConfigurationProperties(prefix = "spring.datasource.secondary")
  public DataSourceProperties secondaryProperties() {
    return new DataSourceProperties();
  }

  @Bean
  @Autowired
  public DataSource secondaryDataSource(@Qualifier("secondaryProperties")
      DataSourceProperties properties) {
    return properties.initializeDataSourceBuilder().build();
  }

  @Bean
  @Autowired
  public LocalContainerEntityManagerFactoryBean secondaryEntityManager(EntityManagerFactoryBuilder builder,@Qualifier("secondaryDataSource") DataSource dataSource){
    return builder.dataSource(dataSource)
        .packages("aha.oretama.jp.entity.secondary")
        .persistenceUnit("secondary")
        .build();
  }

  @Bean
  @Autowired
  public JpaTransactionManager secondaryTransactionManager(@Qualifier("secondaryEntityManager") LocalContainerEntityManagerFactoryBean secondaryEntityManager) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(secondaryEntityManager.getObject());
    return transactionManager;
  }

}
```


以下、解説です。

### DataSourcePropertiesの設定
DataSourcePropertiesはデフォルトでBean登録されており、デフォルトのDataSourcePropertiesを使用しないようにするために、必ずprimaryDB側に`@primary`を付与する必要があります。
`@primary`がないと、複数のBeanが登録されているためにエラーとなってしまいます。

今回は`spring.datasource.primary.*`,`spring.datasource.secondary.*`を読み込むように`@ConfigurationProperties`を付与しています。

### DataSourceの設定
それぞれのDBに対するDataSourceを定義します。

同じくprimaryDB側に`@primary`をつけないとエラーになります。
`@Autowired`をつけて、引数に渡したDataSourcePropertiesをインジェクションしています。
対象クラスのBeanが複数あるので`@Qualifier`で明示しています。

### LocalContainerEntityManagerFactoryBeanの設定
EntityManagerを生成するためのクラスになります。

EntityManagerFactoryBuilderはSpring Bootが提供しているBeanをそのままインジェクションします。
`.packages(...)`で対象となるEntityが格納されたパッケージを指定することで複数データベースに対応しています。この引数はクラスの配列も受け取れるので、パッケージを分けずに、Entityのクラスを直接指定することもできます。
`.persistenceUnit(...)`は永続性ユニットの名前で、複数データベースを定義するときは永続性ユニットを区別するために必須になります。

### JpaTransactionManagerの設定
EntityManagerが２つあるため、それぞれのTransactionManagerを定義します。

JpaTransactionManagerを生成して、EntityManagerFactoryを設定すればOKです。


### クラスの設定

対象のJpaRepositoryパッケージ、EntityManagerFactory,TransactionManagerがわかるように、`@EnableJpaRepositories`で指定します。

### yml or propertiesの設定
忘れないように、DataSourcePropertiesの設定で指定したプロパティ名に接続情報を記載します。


これですべての設定が完了しました！
これで複数データベースに接続できます。

# 5. Flywayの追加設定（補足編）
これは完全に補足です。
複数データベースを定義して、primary以外のデータベースにFlywayでマイグレーションする場合は、対象のDataSourceに`@FlywayDataSource`を指定します。

```
  @Bean
  @Autowired
  @FlywayDataSource
  public DataSource secondaryDataSource(@Qualifier("secondaryProperties")
      DataSourceProperties properties) {
    return properties.initializeDataSourceBuilder().build();
  }
```

# サンプルソース
いつも通りGitHubに公開しています。
https://github.com/aha-oretama/spring-boot-multi-datasource-sample/tree/master


# 参照先
* http://docs.spring.io/spring-boot/docs/current/reference/html/howto-data-access.html
* http://docs.spring.io/spring-boot/docs/current/reference/html/howto-database-initialization.html
* http://www.baeldung.com/spring-data-jpa-multiple-databases
* http://dev.classmethod.jp/server-side/java/java-spring-boot-multiple-database/