やりたいこと
単一のWebFluxのアプリケーションで、Spring Data R2DBCを使って複数のデータベースに接続してSQLを実行したい。
環境準備
データベース
準備が簡単なので、Docker Composeを使ってデータベースを2つ起動しています。
version: '3.7' services: db1: image: 'postgres' ports: - "15432:5432" environment: - POSTGRES_HOST_AUTH_METHOD=trust db2: image: 'postgres' ports: - "15433:5432" environment: - POSTGRES_HOST_AUTH_METHOD=trust
テーブル
アプリケーションで使用するテーブルをそれぞれのデータベースに作成してあげます。
db1
create table hoge ( id char(36) not null, name varchar(100), primary key (id) );
db2
create table fuga ( id serial not null, name varchar(100), primary key (id) );
アプリケーションの実装
Spring Bootバージョン
Spring Bootは現時点で最新のバージョンを使って試してみます。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.4</version> <relativePath /> <!-- lookup parent from repository --> </parent>
データベースの接続を切り替えるための実装
Configurationの準備
db1とdb2に接続するためのConfigurationを準備します。
Configurationには@EnableR2dbcRepositories
アノテーションを追加し、この接続設定を使うベースパッケージをbasePackages
に指定します。
また、指定したベースパッケージ配下で利用するR2dbcEntityTemplate
のBean名をentityOperationsRef
に指定します。
これらを設定してあげるだけで、パッケージ単位に利用するR2dbcEntityTemplate
が自動的に切り替わるので、複数のデータベースへのアクセスが可能になります。
@Configuration @EnableR2dbcRepositories( basePackages = ["siosio.multipler2dbc.db1"], entityOperationsRef = "db1EntityTemplate" ) class Db1Config { @Bean @ConfigurationProperties(prefix = "db1") fun db1Properties(): R2dbcProperties = R2dbcProperties() @Bean fun db1ConnectionFactory(): ConnectionFactory = db1Properties().toConnectionFactory() @Bean fun db1EntityTemplate() = R2dbcEntityTemplate(db1ConnectionFactory()) } @Configuration @EnableR2dbcRepositories( basePackages = ["siosio.multipler2dbc.db2"], entityOperationsRef = "db2EntityTemplate" ) class Db2Config { @Bean @ConfigurationProperties(prefix = "db2") fun db2Properties(): R2dbcProperties = R2dbcProperties() @Bean fun db2ConnectionFactory(): ConnectionFactory = db2Properties().toConnectionFactory() @Bean fun db2EntityTemplate() = R2dbcEntityTemplate(db2ConnectionFactory()) } fun R2dbcProperties.toConnectionFactory(): ConnectionFactory { val connectionFactory = ConnectionFactoryOptions.parse(url) .mutate() .apply { username?.let { option(ConnectionFactoryOptions.USER, it) } password?.let { option(ConnectionFactoryOptions.PASSWORD, it) } }.build().let { ConnectionFactories.get(it) } val poolConfiguration = ConnectionPoolConfiguration .builder(connectionFactory) .maxSize(pool.maxSize) .initialSize(pool.initialSize) .build() return ConnectionPool(poolConfiguration) }
接続先の設定
db1
とdb2
に対する接続先の情報をproperties(yml)に設定します。
今回は、db1
とdb2
としてConfigurationクラスにプレフィックスと指定しているので、設定値はdb1.
またはdb2.
から始まります。
プレフィクス以降は、R2dbcProperties
に対して設定可能なキーを指定してあげます。
db1.url=r2dbc:postgresql://localhost:15432/postgres db1.username=postgres db2.url=r2dbc:postgresql://localhost:15433/postgres db2.username=postgres
データベースに対するアクセスする側の実装
それぞれのデータベースに対してアクセスするには、Configurationで指定したパッケージ配下にデータベースに対する処理を実装するだけとなります。 今回の例では、次ののように実装するだけで自動的に接続先が切り替わります。
db1
に対する処理は、siosio.multipler2dbc.db1
パッケージ配下に実装します。db2
に対する処理は、siosio.multipler2dbc.db2
パッケージ配下に実装します。
あとは、単一データベース接続のときと同じようにデータベースアクセスするだけですね。1
db1への処理
package siosio.multipler2dbc.db1 // importは省略 @Component class Db1Handler(private val hogeRepository: HogeRepository) { suspend fun post(request: ServerRequest) : ServerResponse { val hoge = hogeRepository.save(Hoge(UUID.randomUUID().toString(), "hoge")).awaitSingle() return ServerResponse.ok().bodyValueAndAwait(mapOf("id" to hoge.id)) } } @Repository interface HogeRepository: ReactiveCrudRepository<Hoge, String> //create table hoge (id char(36) not null, name varchar(100), primary key(id)) data class Hoge( @Id private val id: String, val name: String ) : Persistable<String> { override fun isNew(): Boolean = true override fun getId() = id }
db2への処理
package siosio.multipler2dbc.db2 // importは省略 @Component class Db2Handler(private val fugaRepository: FugaRepository) { suspend fun post(request: ServerRequest): ServerResponse { val fuga = fugaRepository.save(Fuga(name = "fuga")).awaitSingle() return ServerResponse.ok().bodyValueAndAwait(mapOf("id" to fuga.id)) } } @Repository interface FugaRepository : ReactiveCrudRepository<Fuga, Int> //create table fuga (id serial not null, name varchar(100), primary key(id)) data class Fuga( @Id val id: Int? = null, val name: String )
動作確認
curlでそれぞれのエンドポイントを叩くと、エラーなど起きずにデータベースの処理結果のIDが返ってくることが確認できます。 これで、複数データベースへの接続確認はOKですね。
$ curl -X POST http://localhost:8080/db1 {"id":"6c1d2c95-d36f-4b1d-acea-366f6a45e240"} $ curl -X POST http://localhost:8080/db2 {"id":9}
ソースコード
使用したサンプルプロジェクト全体は、以下から確認できます。 github.com