使用jOOQ 3.15和R2DBC的反应式SQL(附实例)

最近发布的jOOQ 3.15最大的新功能之一是它对通过R2DBC进行反应式查询的新支持。这一直是一个非常受欢迎的功能请求,我们最终实现了这一点。

你可以继续以你习惯的方式使用jOOQ,为你提供类型安全的、嵌入在Java、kotlin或scala中的SQL,但你的查询执行不再是阻塞的。相反,你的jOOQ [ResultQuery](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/ResultQuery.html)[Query](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/Query.html)可以在你选择的reactive-streams实现中作为一个Publisher<R>Publisher<Integer>

除了配置你的jOOQ之外,还可以用JDBC [DSLContext](https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/DSLContext.html)配置一个JDBC [java.sql.Connection](https://docs.oracle.com/en/java/javase/16/docs/api/java.sql/java/sql/Connection.html)[javax.sql.DataSource](https://docs.oracle.com/en/java/javase/16/docs/api/java.sql/javax/sql/DataSource.html),只需将其配置为R2DBC [io.r2dbc.spi.Connection](https://javadoc.io/static/io.r2dbc/r2dbc-spi/0.9.0.M2/io/r2dbc/spi/Connection.html)io.r2dbc.spi.ConnectionFactory

ConnectionFactory connectionFactory = ConnectionFactories.get(
    ConnectionFactoryOptions
        .parse("r2dbc:h2:file://localhost/~/r2dbc-test")
        .mutate()
        .option(ConnectionFactoryOptions.USER, "sa")
        .option(ConnectionFactoryOptions.PASSWORD, "")
        .build()
);

DSLContext ctx = DSL.using(connectionFactory);

另外,可以使用Spring Boot来自动配置jOOQ,就像这样:

当然,说得好:pic.twitter.com/tUgNkwzCK4

- Anghel Leonard (@anghelleonard)2021年7月15

从这个DSLContext ,你可以像往常一样建立你的查询,但不是调用通常的阻塞式execute()fetch() 方法,而是直接将查询包裹在一个Flux ,例如。假设你在你的H2上运行了jOOQ代码生成器INFORMATION_SCHEMA ,你现在可以写:

record Table(String schema, String table) {}

Flux.from(ctx
        .select(
            INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA,
            INFORMATION_SCHEMA.TABLES.TABLE_NAME)
        .from(INFORMATION_SCHEMA.TABLES))

    // Type safe mapping from Record2<String, String> to Table::new
    .map(Records.mapping(Table::new))
    .doOnNext(System.out::println)
    .subscribe();

jOOQ将从你的ConnectionFactory ,获取一个R2DBCConnection ,并在查询执行完毕后再次释放它,从而实现优化的资源管理,这在R2DBC和reactor中是有点棘手的。换句话说,上面的执行与这个手动编写的查询是对应的:

Flux.usingWhen(
        connectionFactory.create(),
        c -> c.createStatement(
                """
                SELECT table_schema, table_name
                FROM information_schema.tables
                """
             ).execute(),
        c -> c.close()
    )
    .flatMap(it -> it.map((r, m) -> 
         new Table(r.get(0, String.class), r.get(1, String.class))
    ))
    .doOnNext(System.out::println)
    .subscribe();

两者都会打印出类似下面的内容:

Table[schema=INFORMATION_SCHEMA, table=TABLE_PRIVILEGES]
Table[schema=INFORMATION_SCHEMA, table=REFERENTIAL_CONSTRAINTS]
Table[schema=INFORMATION_SCHEMA, table=TABLE_TYPES]
Table[schema=INFORMATION_SCHEMA, table=QUERY_STATISTICS]
Table[schema=INFORMATION_SCHEMA, table=TABLES]
Table[schema=INFORMATION_SCHEMA, table=SESSION_STATE]
Table[schema=INFORMATION_SCHEMA, table=HELP]
Table[schema=INFORMATION_SCHEMA, table=COLUMN_PRIVILEGES]
Table[schema=INFORMATION_SCHEMA, table=SYNONYMS]
Table[schema=INFORMATION_SCHEMA, table=SESSIONS]
Table[schema=INFORMATION_SCHEMA, table=IN_DOUBT]
Table[schema=INFORMATION_SCHEMA, table=USERS]
Table[schema=INFORMATION_SCHEMA, table=COLLATIONS]
Table[schema=INFORMATION_SCHEMA, table=SCHEMATA]
Table[schema=INFORMATION_SCHEMA, table=TABLE_CONSTRAINTS]
Table[schema=INFORMATION_SCHEMA, table=INDEXES]
Table[schema=INFORMATION_SCHEMA, table=ROLES]
Table[schema=INFORMATION_SCHEMA, table=FUNCTION_COLUMNS]
Table[schema=INFORMATION_SCHEMA, table=CONSTANTS]
Table[schema=INFORMATION_SCHEMA, table=SEQUENCES]
Table[schema=INFORMATION_SCHEMA, table=RIGHTS]
Table[schema=INFORMATION_SCHEMA, table=FUNCTION_ALIASES]
Table[schema=INFORMATION_SCHEMA, table=CATALOGS]
Table[schema=INFORMATION_SCHEMA, table=CROSS_REFERENCES]
Table[schema=INFORMATION_SCHEMA, table=SETTINGS]
Table[schema=INFORMATION_SCHEMA, table=DOMAINS]
Table[schema=INFORMATION_SCHEMA, table=KEY_COLUMN_USAGE]
Table[schema=INFORMATION_SCHEMA, table=LOCKS]
Table[schema=INFORMATION_SCHEMA, table=COLUMNS]
Table[schema=INFORMATION_SCHEMA, table=TRIGGERS]
Table[schema=INFORMATION_SCHEMA, table=VIEWS]
Table[schema=INFORMATION_SCHEMA, table=TYPE_INFO]
Table[schema=INFORMATION_SCHEMA, table=CONSTRAINTS]

注意,如果你使用的是JDBC而不是R2DBC,你可以继续以阻塞的方式使用jOOQ API与你的反应式流库,方法与上面完全一样,例如,如果你喜欢的RDBMS还不支持反应式R2DBC驱动。根据r2dbc.io,目前支持的驱动包括:

所有这些我们都用jOOQ 3.15+进行集成测试。

一个可运行的例子

去玩一下这里的例子:https://github.com/jOOQ/jOOQ/tree/main/jOOQ-examples/jOOQ-r2dbc-example

它使用下面的模式:

CREATE TABLE r2dbc_example.author (
  id INT NOT NULL AUTO_INCREMENT,
  first_name VARCHAR(100) NOT NULL,
  last_name VARCHAR(100) NOT NULL,
  
  CONSTRAINT pk_author PRIMARY KEY (id)
);

CREATE TABLE r2dbc_example.book (
  id INT NOT NULL AUTO_INCREMENT,
  author_id INT NOT NULL,
  title VARCHAR(100) NOT NULL,
  
  CONSTRAINT pk_book PRIMARY KEY (id),
  CONSTRAINT fk_book_author FOREIGN KEY (id) 
    REFERENCES r2dbc_example.author
);

并运行以下代码:

Flux.from(ctx
        .insertInto(AUTHOR)
        .columns(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
        .values("John", "Doe")
        .returningResult(AUTHOR.ID))
    .flatMap(id -> ctx
        .insertInto(BOOK)
        .columns(BOOK.AUTHOR_ID, BOOK.TITLE)
        .values(id.value1(), "Fancy Book"))
    .thenMany(ctx
        .select(
             BOOK.author().FIRST_NAME, 
             BOOK.author().LAST_NAME, 
             BOOK.TITLE)
        .from(BOOK))
    .doOnNext(System.out::println)
    .subscribe();

插入两条记录并获取连接的结果,如下所示:

+----------+---------+----------+
|FIRST_NAME|LAST_NAME|TITLE     |
+----------+---------+----------+
|John      |Doe      |Fancy Book|
+----------+---------+----------+

猜你喜欢

转载自juejin.im/post/7126368932998414373