Spring JPAで複数データベース(PostgreSQL)に接続する方法

Spring JPAで複数データベース(PostgreSQL)に接続する方法

spring boot
2.2.5.RELEASE

JPAで複数データベースに接続するけど、トランザクション管理は別々にしたいです。例えば、MyDB1を更新して、そのあとにMyDB2更新時に失敗した場合に、MyDB1をロールバックせずにコミットしておきたい。

今回は2つのデータベースともPostgreSQL9.6を使用しています。

複数データベースに接続する

1つ目のDB接続情報をprimary、もう一つのDB接続情報をsecondaryとします。

application.ymlには以下のように記述します。

spring:
  jpa:
    database: POSTGRESQL
  datasource:
    primary:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://localhost:5432/mydb1 #mydb1というデータベース
      username: postgres
      password: xxxxxxxx
    secondary:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://localhost:5432/mydb2 #mydb2というデータベース
      username: postgres
      password: yyyyyyyy

接続情報に対するクラスを定義します。接続情報を一つのクラスとして定義する為、2つのクラスを作成しておきます。

PrimaryConfig.java

package jp.co.confrage.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@Configuration
@EnableJpaRepositories(
  basePackages = "jp.co.confrage.repository.primary",
  entityManagerFactoryRef = "primaryEntityManager",
  transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryConfig {
  @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("jp.co.confrage.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;
  }
}

SecondaryConfig.java

package jp.co.confrage.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@Configuration
@EnableJpaRepositories(
  basePackages = "jp.co.confrage.repository.secondary",
  entityManagerFactoryRef = "secondaryEntityManager",
  transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryConfig {
  @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("jp.co.confrage.entity.secondary")
      .persistenceUnit("secondary")
      .build();
  }

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

これで2つのデータベースに接続することが確認できます。トランザクション管理は別になります。

サービスレベルで@Transactionalをつけると、「Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query」とエラーが出ますが、リポジトリレベルで@Transactionalとすると別トランザクション管理ができます。

DataSourceProperties

@ConfigurationProperties(prefix = “spring.datasource.primary”)アノテーション、@ConfigurationProperties(prefix = “spring.datasource.secondary”)アノテーションをメソッドに付与してDataSourcePropertiesを生成します。(prefixはapplication.ymlに合わせてください)

DataSource

DataSourcePropertiesインスタンスからDataSourceを生成します。

LocalContainerEntityManagerFactoryBean

EntityManagerを生成するクラスです。Spring Bootが提供しているEntityManagerFactoryBuilderクラスからインスタンスを生成します。

packagesメソッドでEntityのパッケージを指定します。

persistenceUnitメソッドでEntityManagerのunitNameを指定します。

JpaTransactionManager

EntityManagerに対してTransactionManagerを生成します。

2つのデータベースでトランザクション管理する2相コミット(2フェーズコミット)

データベース接続は確認できましたが、複数データベースでトランザクション管理ができるかというと、@Transactionalでは実現できず、以下ライブラリを使わないとだめなようです。

bitronixは情報量が少ないように思いますが、現在も開発されています。

Atomikosも現在も開発されています。ライブラリは他にもあるようです。Spring boot公式ではこの2つをサポートしているとのことです。

Spring Boot コア機能 - リファレンスドキュメント
Spring Boot の概要から各機能の詳細までが網羅された公式リファレンスドキュメントです。開発者が最初に読むべきドキュメントです。

PostgreSQL9.6で2フェーズコミットをAtomikosかbitronixを使って試してみたいと思います。

basePackagesに複数指定する

複数リポジトリを@EnableJpaRepositoriesアノテーションのbasePackages属性に{}で囲って、カンマ区切りで複数指定ができます。

@EnableJpaRepositories(
  basePackages = {
    "jp.co.confrage.repository.primary1",
    "jp.co.confrage.repository.primary2"
  },
  entityManagerFactoryRef = "primaryEntityManager",
  transactionManagerRef = "primaryTransactionManager"
)

コメント

株式会社CONFRAGE ITソリューション事業部をもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む

タイトルとURLをコピーしました