怖くないR2DBC その3 spring-data-r2dbc

怖くないR2DBCシリーズ。

bufferings.hatenablog.com

前回までで、この3つを見てきた。

r2dbc-spiがインターフェースで、r2dbc-postgresqlがそのPostgreSQL用実装。r2dbc-clientはr2dbc-spiを使いやすくするためのクライアントライブラリー。という感じだった。

f:id:bufferings:20181118081104p:plain

今日はもうひとつのクライアントライブラリーであるspring-data-r2dbcについて見てみる。

## spring-data-r2dbc?

r2dbc-clientはSpringには依存せずに便利機能を提供してたけど、spring-data-r2dbcはSpringを利用して便利機能を提供するクライアントライブラリーなんだろうなー。

github.com

## 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