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つをサポートしているとのことです。
PostgreSQL9.6で2フェーズコミットをAtomikosかbitronixを使って試してみたいと思います。
basePackagesに複数指定する
複数リポジトリを@EnableJpaRepositoriesアノテーションのbasePackages属性に{}
で囲って、カンマ区切りで複数指定ができます。
@EnableJpaRepositories( basePackages = { "jp.co.confrage.repository.primary1", "jp.co.confrage.repository.primary2" }, entityManagerFactoryRef = "primaryEntityManager", transactionManagerRef = "primaryTransactionManager" )

KHI入社して退社。今はCONFRAGEで正社員です。関西で140-170/80~120万から受け付けております^^
得意技はJS(ES6),Java,AWSの大体のリソースです
コメントはやさしくお願いいたします^^
座右の銘は、「狭き門より入れ」「願わくは、我に七難八苦を与えたまえ」です^^
コメント