Quarkus - 反应式 SQL 客户端

反应式 SQL 客户端有一个简单的 API,侧重于可伸缩和低损耗。 目前支持以下数据库服务器:

  • PostgreSQL

  • MariaDB/MySQL

在本指南中,您将学习如何实现一个简单的 CRUD 应用程序,这个应用程序将在 RESTful API 上暴露存储在 PostgreSQL 中的数据。

每种客户端的 Extension 和连接池类名在本文档底部找到。
如果您不熟悉 Quarkus Vert.x extension ,请先阅读 使用 Eclipse Vert.x 指南。

应用会管理 fruit 实体:

public class Fruit {

    public Long id;

    public String name;

    public Fruit() {
    }

    public Fruit(String name) {
        this.name = name;
    }

    public Fruit(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

您需要一个马上能用的 PostgreSQL 服务器来尝试示例吗?

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_test -e POSTGRES_USER=quarkus_test -e POSTGRES_PASSWORD=quarkus_test -e POSTGRES_DB=quarkus_test -p 5432:5432 postgres:10.5

安装

Reactive PostgreSQL Client extension

首先,请确保您的项目已启用 quarkus-reactive-pg-client 扩展。 如果您正在创建一个新项目,请将 extensions 参数设置为:

mvn io.quarkus:quarkus-maven-plugin:1.3.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=reactive-pg-client-quickstart \
    -Dextensions="reactive-pg-client"
cd reactive-pg-client-quickstart

如果您已经创建了一个项目,可以使用 add-extension 命令将 reactive-pg-client extension 添加到现有的 Quarkus 项目中:

./mvnw quarkus:add-extension -Dextensions="reactive-pg-client"

否则,你可以手动将这个添加到 pom.xml 文件的依赖部分:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
在本指南中,我们将使用 Reactive PostgreSQL Client 的 Axle API。 阅读 使用 Eclipse Vert.x 指南以了解callback、Mutiny、RxJava 和 Axle 的API之间的差异。 RxJava 和 Axle API 已废弃,计划移除。 建议切换到Mutiny。
Mutiny

推荐的 API 使用 Mutiny 反应式类型,如果你不熟悉它们,请先阅读 反应式入门指南

JSON 绑定

我们会通过 JSON 格式通过 HTTP 暴露 Fruit 实例。 因此,您还需要添加 quarkus-resteasy-jsonb 扩展:

./mvnw quarkus:add-extension -Dextensions="resteasy-jsonb"

如果你不想使用命令行,请手动将其添加到你的 pom.xml 文件的依赖部分:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>

当然,这只是本指南的要求,而不是任何使用 Reactive PostgreSQL Client 的应用程序都需要。

配置

Reactive PostgreSQL Client 可以使用标准的 Quarkus 数据源属性和一个反应式 URL:

src/main/resources/application.properties
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.reactive.url=postgresql://localhost:5432/quarkus_test

你可以创建 FruitResource 骨架并 @Inject 一个 io.vertx.mutiny.pgclient.PgPool 实例:

src/main/java/org/acme/vertx/FruitResource.java
@Path("fruits")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class FruitResource {

    @Inject
    io.vertx.mutiny.pgclient.PgPool client;
}

数据库表结构和种子数据

在实现 REST 接口和数据管理代码之前,我们需要配置数据库表结构。 事先插入一些数据也会更方便。

对于生产环境,我们建议使用 Flyway数据库迁移工具。 但为了开发,我们可以在启动时简单地删除重建表,然后插入几条 fruits 。

src/main/java/org/acme/vertx/FruitResource.java
    @Inject
    @ConfigProperty(name = "myapp.schema.create", defaultValue = "true") (1)
    boolean schemaCreate;

    @PostConstruct
    void config() {
        if (schemaCreate) {
            initdb();
        }
    }

    private void initdb() {
        // TODO
    }
}
您可以覆盖 application.properties 文件中的 myapp.schema.create 属性的默认值。

即将准备就绪! 要初始化开发模式中的DB,我们将使用简单的 query 方法。 它返回 Uni ,因此可以由它组成按顺序执行查询:

 client.query("DROP TABLE IF EXISTS fruits")
    .flatMap(r -> client.query("CREATE TABLE fruits (id SERIAL PRIMARY KEY, name TEXT NOT NULL)"))
    .flatMap(r -> client.query("INSERT INTO fruits (name) VALUES ('Orange')"))
    .flatMap(r -> client.query("INSERT INTO fruits (name) VALUES ('Pear')"))
    .flatMap(r -> client.query("INSERT INTO fruits (name) VALUES ('Apple')"))
    .await().indefinitely();
想知道为什么我们需要阻塞直到完成最新查询? 此代码是 @PostConstruct 方法的一部分,Quarkus 同步调用。 如果过早返回,可能导致在数据库尚未准备就绪的情况下处理请求。

就这些了! 到目前为止,我们已经看到如何配置一个 pooled client 并执行简单的查询。 我们现在准备开发数据管理代码并执行我们的 RESTful 接口。

使用

遍历查询结果

在开发模式下,数据库已经在 fruits 表中有几行记录。 要检索所有数据,我们将再次使用 query 方法:

Uni<RowSet> rowSet = client.query("SELECT id, name FROM fruits ORDER BY name ASC");

当操作完成,我们将会得到一个 RowSet ,所有行都可以在内存中缓冲。 作为一个 java.lang.Iterable<Row>, 它可以经过一个 for-each 循环:

Uni<List<Fruit>> fruits = rowSet
    .map(pgRowSet -> {
        List<Fruit> list = new ArrayList<>(pgRowSet.size());
        for (Row row : pgRowSet) {
            list.add(from(row));
        }
        return list;
    });

from 方法将一个 Row 实例转换为 Fruit 实例。 为了便于采用其他数据管理方法:

src/main/java/org/acme/vertx/Fruit.java
private static Fruit from(Row row) {
    return new Fruit(row.getLong("id"), row.getString("name"));
}

把它放在一起, Fruit.findAll 方法看起来就像:

src/main/java/org/acme/vertx/Fruit.java
    public static Uni<List<Fruit>> findAll(PgPool client) {
        return client.query("SELECT id, name FROM fruits ORDER BY name ASC")
            .map(pgRowSet -> {
                List<Fruit> list = new ArrayList<>(pgRowSet.size());
                for (Row row : pgRowSet) {
                    list.add(from(row));
                }
                return list;
            });
    }

从后端获取所有 fruits 的接口:

src/main/java/org/acme/vertx/FruitResource.java
@GET
public Uni<Response> get() {
    return Fruit.findAll(client)
            .map(Response::ok)
            .map(ResponseBuilder::build);
}

现在以 dev 模式启动夸库斯:

./mvnw compile quarkus:dev

最后,打开您的浏览器并访问 http://localhost:8080/fruits, 您应该看到:

[{"id":3,"name":"Apple"},{"id":1,"name":"Orange"},{"id":2,"name":"Pear"}]

准备查询

Reactive PostgreSQL Client 也可以准备查询和执行时替换 SQL 语句中的参数:

client.preparedQuery("SELECT name FROM fruits WHERE id = $1", Tuple.of(id))
SQL 字符串可以通过位置引用参数,使用 $1,$2,…​等。

就像简单的 query 方法一样, preparedQuery 返回一个 Uni<RowSet> 的实例。 装备了这个工具后,我们能够安全地使用用户提供的 id 来获取指定 fruit 的详细信息:

src/main/java/org/acme/vertx/Fruit.java
public static Uni<Fruit> findById(PgPool client, Long id) {
    return client.preparedQuery("SELECT id, name FROM fruits WHERE id = $1", Tuple.of(id)) (1)
            .map(RowSet::iterator) (2)
            .map(iterator -> iterator.hasNext() ? from(iterator.next()) : null); (3)
}
1 创建一个 Tuple 以保存准备的查询参数。
2 获取一个 RowSet 结果的 Iterator
3 如果发现一个实体,从 Row 创建一个 Fruit 实例。

在 JAX-RS 接口中:

src/main/java/org/acme/vertx/FruitResource.java
@GET
@Path("{id}")
public Uni<Response> getSingle(@PathParam Long id) {
    return Fruit.findById(client, id)
            .map(fruit -> fruit != null ? Response.ok(fruit) : Response.status(Status.NOT_FOUND)) (1)
            .map(ResponseBuilder::build); (2)
}
1 准备使用 Fruit 实例或 404 状态代码的 JAX-RS 响应。
2 构建并发送响应。

保存 Fruit 时适用相同的逻辑:

src/main/java/org/acme/vertx/Fruit.java
public Uni<Long> save(PgPool client) {
    return client.preparedQuery("INSERT INTO fruits (name) VALUES ($1) RETURNING (id)", Tuple.of(name))
            .map(pgRowSet -> pgRowSet.iterator().next().getLong("id"));
}

在 web 接口中,我们处理了 POST 请求:

src/main/java/org/acme/vertx/FruitResource.java
@POST
public Uni<Response> create(Fruit fruit) {
    return fruit.save(client)
            .map(id -> URI.create("/fruits/" + id))
            .map(uri -> Response.created(uri).build());
}

Result metadata

一个 RowSet 不仅在内存中保存您的数据,它还为您提供了一些有关数据本身的信息,例如:

  • 受查询影响的行数(根据查询类型插入/删除/更新/检索),

  • 列名称。

Let’s use this to support removal of fruits in the database:

src/main/java/org/acme/vertx/Fruit.java
public static Uni<Boolean> delete(PgPool client, Long id) {
    return client.preparedQuery("DELETE FROM fruits WHERE id = $1", Tuple.of(id))
            .map(pgRowSet -> pgRowSet.rowCount() == 1); (1)
}
1 检查元数据以确定水果是否确实被删除。

Web 接口中的 HTTP DELETE 方法:

src/main/java/org/acme/vertx/FruitResource.java
@DELETE
@Path("{id}")
public Uni<Response> delete(@PathParam Long id) {
    return Fruit.delete(client, id)
            .map(deleted -> deleted ? Status.NO_CONTENT : Status.NOT_FOUND)
            .map(status -> Response.status(status).build());
}

使用 GETPOSTDELETE 方法后,我们现在可以创建一个最小的网页来尝试使用 RESTful 接口。 我们将使用 jQuery 来简化与后端的交互:

<!doctype html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Reactive PostgreSQL Client - Quarkus</title>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"
            integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script type="application/javascript" src="fruits.js"></script>
</head>
<body>

<h1>Fruits API Testing</h1>

<h2>All fruits</h2>
<div id="all-fruits"></div>

<h2>Create Fruit</h2>
<input id="fruit-name" type="text">
<button id="create-fruit-button" type="button">Create</button>
<div id="create-fruit"></div>

</body>
</html>

在 Javascript 代码中,我们需要一个函数来刷新水果列表,如果:

  • 页面已加载,或

  • 添加 fruit,或

  • fruit 已删除。

function refresh() {
    $.get('/fruits', function (fruits) {
        var list = '';
        (fruits || []).forEach(function (fruit) { (1)
            list = list
                + '<tr>'
                + '<td>' + fruit.id + '</td>'
                + '<td>' + fruit.name + '</td>'
                + '<td><a href="#" onclick="deleteFruit(' + fruit.id + ')">Delete</a></td>'
                + '</tr>'
        });
        if (list.length > 0) {
            list = ''
                + '<table><thead><th>Id</th><th>Name</th><th></th></thead>'
                + list
                + '</table>';
        } else {
            list = "No fruits in database"
        }
        $('#all-fruits').html(list);
    });
}

function deleteFruit(id) {
    $.ajax('/fruits/' + id, {method: 'DELETE'}).then(refresh);
}

$(document).ready(function () {

    $('#create-fruit-button').click(function () {
        var fruitName = $('#fruit-name').val();
        $.post({
            url: '/fruits',
            contentType: 'application/json',
            data: JSON.stringify({name: fruitName})
        }).then(refresh);
    });

    refresh();
});
1 当数据库为空时, fruits 参数没有定义。

全部完成! 访问 http://localhost:8080/resours.html 并读取/创建/删除一些水果。

Database Clients 详细信息

数据库 扩展名 池类名称

PostgreSQL

quarkus-reactive-pg-client

io.vertx.mutiny.pgclient.PgPool

MariaDB/MySQL

quarkus-reactive-mysql-client

io.vertx.mutiny.mysqlclient.MySQLPool

配置参考

通用数据源

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

The kind of database we will connect to (e.g. h2, postgresql…​).

string

Whether or not an health check is published in case the smallrye-health extension is present. This is a global setting and is not specific to a datasource.

boolean

true

Whether or not datasource metrics are published in case the smallrye-metrics extension is present. This is a global setting and is not specific to a datasource. NOTE: This is different from the "jdbc.enable-metrics" property that needs to be set on the JDBC datasource level to enable collection of metrics for that datasource.

boolean

false

The datasource username

string

The datasource password

string

The credentials provider name

string

The credentials provider type. It is the @Named value of the credentials provider bean. It is used to discriminate if multiple CredentialsProvider beans are available. For Vault it is: vault-credentials-provider. Not necessary if there is only one credentials provider available.

string

int

20

Additional named datasources

Type

Default

The kind of database we will connect to (e.g. h2, postgresql…​).

string

string

string

string

The credentials provider type. It is the @Named value of the credentials provider bean. It is used to discriminate if multiple CredentialsProvider beans are available. For Vault it is: vault-credentials-provider. Not necessary if there is only one credentials provider available.

string

int

20

反应式数据源

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

If we create a Reactive datasource for this datasource.

boolean

true

The datasource URL.

string

The datasource pool maximum size.

int

MariaDB/MySQL

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

Whether prepared statements should be cached on the client side.

boolean

string

Collation for connections.

string

PostgreSQL

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

Whether prepared statements should be cached on the client side.

boolean

The maximum number of inflight database commands that can be pipelined.

int

quarkus.pro 是基于 quarkus.io 的非官方中文翻译站 ,最后更新 2020/04 。
沪ICP备19006215号-8
QQ交流群:1055930959
微信群: