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(ES20xx),Java,AWSの大体のリソースです
コメントはやさしくお願いいたします^^
座右の銘は、「狭き門より入れ」「願わくは、我に七難八苦を与えたまえ」です^^


コメント