怖くないR2DBCシリーズ。
前回までで、この3つを見てきた。
- r2dbc-spi
- r2dbc-postgresql
- r2dbc-client
r2dbc-spiがインターフェースで、r2dbc-postgresqlがそのPostgreSQL用実装。r2dbc-clientはr2dbc-spiを使いやすくするためのクライアントライブラリー。という感じだった。
今日はもうひとつのクライアントライブラリーであるspring-data-r2dbcについて見てみる。
## spring-data-r2dbc?
r2dbc-clientはSpringには依存せずに便利機能を提供してたけど、spring-data-r2dbcはSpringを利用して便利機能を提供するクライアントライブラリーなんだろうなー。
## ORMじゃないよ
spring-data-r2dbcはORMになろうとはしてない。リアクティブなRDBアクセス用の組み立てキットみたいな感じ。好きなように必要に応じて組み立てることができるようなの。
じゃ、見ていこう。
## DatabaseClient
その中心になってるのはDatabaseClient
クラス。こんな風にしてインスタンスを取得する。この前作ったgetPostgresqlConnectionFactory()
をそのまま利用しといた:
DatabaseClient getDatabaseClient() { return DatabaseClient.create(getPostgresqlConnectionFactory()); } private PostgresqlConnectionFactory getPostgresqlConnectionFactory() { var configuration = PostgresqlConnectionConfiguration.builder() .host("localhost") .database("postgres") .username("postgres") .password("mysecretpassword") .build(); return new PostgresqlConnectionFactory(configuration); }
で、このDatabaseClient
クラスを使って色々やる。
## Generic SQL
まずはGeneric SQLから。汎用的な用途ってことかな。こんな風に書ける。
private Mono<Integer> genericInsert() { var db = getDatabaseClient(); Mono<Integer> count = db.execute() .sql("INSERT INTO weather (city, temp_lo, temp_hi) VALUES($1, $2, $3)") .bind("$1", "Osaka") .bind("$2", 5) .bind("$3", 18) .fetch() .rowsUpdated(); return count; }
fetch()
で実行。rowsUpdated()
で結果を取得ってことかな。fetch()
の戻り値の型はFetchSpec<Map<String, Object>>
になってて、このFetchSpec
インターフェースはこんな感じ。面白いなー。
public interface FetchSpec<T> { Mono<T> one(); Mono<T> first(); Flux<T> all(); Mono<Integer> rowsUpdated(); }
Selectはこんな風に書けて:
private Flux<Map<String, Object>> genericSelect1() { var db = getDatabaseClient(); Flux<Map<String, Object>> result = db.execute() .sql("SELECT city FROM weather") .fetch() .all(); return result; }
結果をごにょごにょしたい場合はfetch()
の代わりにexchange()
を使って、こうするってことなのかな?
private Flux<String> genericSelect2() { var db = getDatabaseClient(); Flux<String> result = db.execute() .sql("SELECT city FROM weather") .exchange() .flatMapMany(it -> it.extract((r, md) -> r.get(0, String.class)).all()); return result; }
ふむふむ。もうちょっとちゃんとJavadocとかソースを読んだほうが良さそう。
## Insert
今度はGenericじゃないやつ。
private Flux<String> insert() { var db = getDatabaseClient(); Flux<String> cities = db.insert() .into("weather") .value("city", "Osaka2") .value("temp_lo", 10) .value("temp_hi", 13) .exchange() .flatMapMany(it -> it.extract((r, md) -> "city:" + r.get("city", String.class) + " temp_lo:" + r.get("temp_lo", Integer.class)).all()); return cities; }
ふーむ。こっちの方がGenericのよりは読みやすいかね。
クラスを使うこともできる。ふーん。
private Flux<String> insertWithDto() { Weather weather = Weather.create("Kyoto", 1, 30); var db = getDatabaseClient(); Flux<String> cities = db.insert() .into(Weather.class) .using(weather) .exchange() .flatMapMany(it -> it.extract((r, md) -> "city:" + r.get("city", String.class)).all()); return cities; } @Table("weather") private static class Weather { static Weather create(String city, Integer tempLo, Integer tempHi) { Weather weather = new Weather(); weather.city = city; weather.tempLo = tempLo; weather.tempHi = tempHi; return weather; } public String city; @Column("temp_lo") public Integer tempLo; @Column("temp_hi") public Integer tempHi; }
## Select
Selectもこんな感じか。
private Flux<Map<String, Object>> select1() { var db = getDatabaseClient(); Flux<Map<String, Object>> rows = db.select() .from("weather") .orderBy(Sort.by(desc("temp_lo"))) .fetch() .all(); return rows; }
DTOを使うとこんな感じ。
private Flux<Weather> select2() { var db = getDatabaseClient(); Flux<Weather> rows = db.select() .from(Weather.class) .orderBy(Sort.by(desc("temp_lo"))) .fetch() .all(); return rows; }
今日はこのくらいにしとこ。・・・あれ?ここまでSpring関係なく単に便利だなーってだけじゃない???
課題は、MonoとFluxとそれ以前にStreamの使い方を僕がちゃんと分かってないとこだな。勉強しなきゃなー。
リポジトリーは、また次回。リポジトリーはSpring使ってそうよね。
## 今日のソースコード
DIとか使ってごにょっとしといた。
https://github.com/bufferings/hello-r2dbc/tree/07e521fa755f796b0ef78cc0122906dafef60509