PostgreSQLのリアクティブアクセスをネイティブ化できなかった

とりあえずメモ的に。
R2DBCとmicronaut+reactive-pg-clientの2通りでnative-imageを試したけど、nettyまわりでだめだったという話

R2DBC

リアクティブデータアクセスとしては本命?
R2DBC
R2DBCはセントラルリポジトリにないので、springのリポジトリを使います。

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>    
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>        
    </repositories>

R2DBCのライブラリはこれ。

        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-postgresql</artifactId>
            <version>1.0.0.M6</version>
        </dependency>

コードはこんな感じで。

import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        PostgresqlConnectionConfiguration conf = 
                PostgresqlConnectionConfiguration.builder()
                                                 .host("localhost")
                                                 .port(5432)
                                                 .database("mystat")
                                                 .username("mystat")
                                                 .password("pass").build();
        PostgresqlConnectionFactory connFact = 
                new PostgresqlConnectionFactory(conf);
        ExecutorService es = Executors.newSingleThreadExecutor();
        Scheduler sc = Schedulers.fromExecutorService(es);

        connFact.create()
                .map(conn -> conn.createStatement("select * from USERS"))
                .flatMapMany(stmt -> stmt.execute())
                .flatMap(result -> result.map((row, meta) -> 
                        String.format("handle:%s name:%s",
                    row.get("user_handle"), row.get("user_name"))))
                .subscribeOn(sc)
                .doOnTerminate(() -> es.shutdown())
                .subscribe(System.out::println);
    }
}

Schedulerにはelastic()が楽なんだろうけど、コマンドラインだと処理が終わる前にコマンドが終了して何も出力されないので、ExecutorServiceを用意してfromExecutorService(es)を使います。で、doOnTerminateshutdown()
もっといい方法があったら教えてください。
native-imageするのでJava8文法しか使えないからvarはナシ。newとかbuilderとかはvarで受けたいけど。

そしてnetty関係でダメでした。

R2DBC-client

R2DBCはちょっとラップして便利にしてくれるclientがあるので使ってみる。

        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-client</artifactId>
            <version>1.0.0.M6</version>
        </dependency>

こんな感じ。ちょっと短くなった。

import io.r2dbc.client.R2dbc;
import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        PostgresqlConnectionConfiguration conf = 
                PostgresqlConnectionConfiguration.builder()
                                                 .host("localhost")
                                                 .port(5432)
                                                 .database("mystat")
                                                 .username("mystat")
                                                 .password("pass").build();
        PostgresqlConnectionFactory connFact = 
                new PostgresqlConnectionFactory(conf);
        ExecutorService es = Executors.newSingleThreadExecutor();
        Scheduler sc = Schedulers.fromExecutorService(es);
        
        R2dbc r2dbc = new R2dbc(connFact);
        r2dbc.inTransaction(h -> 
                h.select("select * from USERS")
                        .mapRow(row -> row.get("user_name")))
                .subscribeOn(sc)
                .doOnTerminate(() -> es.shutdown())
                .subscribe(o -> System.out.println(o));
    }
}

まあ、もとのR2DBCでこけてるので、もちろんnative-imageはnettyまわりでこける。

Reactive Postgres Client

もうひとつ、PostgreSQL用にはReactive Postgres Clientというのがある。
Reactive Postgres Client | reactive-pg-client

Micronaut経由で使ってみたらnative-image対応してくれてるんでは、と試してみる。
とはいえ、Webでやるのは面倒なので、CLIとして使ってみます。
Micronaut: 11. Standalone Command Line Applications

Micoronautのプロジェクト作成コマンドmnで大枠を作ります。

$ mn create-cli-app my-cli --features postgres-reactive

build.gradleにソースレベルなどの設定を追加

sourceCompatibility="1.8"
targetCompatibility="1.8"

application.ymlにPostgreSQL関係の設定がすでに書いてあるので、適切に変更。ここで、clientの下にあるべきものたちのインデントがちゃんとしてない気がするので修正してます。

postgres:
    reactive:
        client:
            port: 5432
            host: localhost
            database: mystat
            user: mystat
            password: pass
            # maxSize: 5

修正のPRでも送るか~と思ったけど、ぺろぺろっとできる修正じゃなさそうなので保留
あとは、MyCliCommand.javaができてるはずなので、処理を書きます。

public void run() {
    // business logic here
    if (verbose) {
        System.out.println("Hi!");
    }
    data();
}

@Inject
PgPool client;

public void data() {
    client.rxQuery("select * from users")
            .map(rowSet -> {
                List<String> result = new ArrayList<>();
                PgIterator ite = rowSet.iterator();
                while(ite.hasNext()) {
                    Row row = ite.next();
                    result.add(row.getString("user_name"));
                }
                return result;
            })
            .blockingGet()
            .stream()
            .forEach(System.out::println);
}

println("Hi!")は元からある処理。
PgPoolをInjectするだけで使えます。io.reactiverse.pgclient.PgPoolではなくio.reactiverse.reactivex.pgclient.PgPoolなので注意。
しかし、R2DBCはReactorだけどReactive-pg-clientはRxJavaなので戸惑う。

そしてnative-imageしてみるけど、やはりnetty関係でだめだった。

そのうちリベンジする。