# 1.10.珷玞：Jooq

> “故美玉蕴於碔砆。”——《四子讲德论》

* 项目地址：<https://github.com/silentbalanceyh/vertx-zero-example/>（子项目：**up-athena**）
* 工具地址：<https://pan.baidu.com/s/1u3XayfvUZh8bOMMfK4zQhg> 提取码: d1qy

    Zero的支持表格如下：

| 版本         | Zero 0.6.2 | Zero 0.7.x |
| ---------- | ---------- | ---------- |
| Vert.x     | 3.9.9      | 4.1.x      |
| Jooq       | 3.10.8     | 3.15.x     |
| vertx-jooq | 2.4.1      | 6.3.x      |

> 新版都是支持最新的，所以根据您选择的版本下载不同的工具

![](/files/34RghFOXkQ4rXceobFW6)

## 「壹」环境准备

### 1.1. 引入Jooq

    选择Jooq框架的主要目的如下：

* 和原生SQL的DDL语句结合得比较紧，在做动态建模的时候更容易使用面向对象的方式执行元数据操作（参考`zero-atom` 项目），包括视图创建、表更改、字段增删等。
* Jooq近似于一个ORM框架，可以在开发过程中很方便实现面向对象模式的CRUD操作，并且让开发人员不用去关心底层SQL，但它提供了SQL模式的思路来实现数据库访问，比很多ORM框架更加灵活。
*

Jooq具有代码生成功能，对于最基本的增删查改等操作，开发人员可避免在项目过程中书写Domain/Dao/Service等重复性代码，这些代码可以直接使用`jooq-codegen` 工具生成。

    Zero中的Jooq设计整体如下图：

![](/files/mG3imwicPjlcZKLv9h9f)

    Zero框架中，Jooq是以插件模式引入到系统内部，如果项目不需要访问数据库，该模块的整体功能如下：

* 和Vert.x协同提供通用的CRUD编程接口（同步和异步双版本），实现面向对象的无缝编程。
* 提供查询分析引擎，使用Json格式的语法实现复杂的SQL查询，支持大部分常用的聚集功能。
* 可接入Redis或其他缓存接口，实现AOP层的缓存支持，内置使用Cache-Aside模式。
* 使用单`Class<?>`构造`UxJooq`统一访问接口，在生成的Dao基础之上不需要引入额外的类来完成数据库访问操作。

    为什么要封装Jooq？既然Jooq已经自带了所有核心级别的CRUD操作，那么Zero对它的封装是基于什么目的呢，这也许是很多读者不太容易理解的点，这样的做法是不是有点 **重复造轮子** 的行为？其实相反，Zero对Jooq的封装是基于实际业务场景的一种补充：

1. Jooq和Vert.x并没有强相关性，在开发过程中，让开发人员结合Jooq和Vert.x进行编程会有一定的难度，封装过后的API底层是基于Jooq和Vert.x，这样开发人员就不用关心CRUD的技术细节，可实现这层操作的无缝对接。
2. Zero中提供了强大的查询分析引擎，可构造各种动态SQL以及复杂查询，并且这种查询分析引擎语法使用JSON数据实现，并且提供了特殊的API处理底层数据类型的兼容。
3. Zero提供了三层缓存，L1的数据库级缓存、L2的业务级缓存、L3的HTTP缓存，在数据库缓存中，开发人员不需要再额外开发缓存逻辑，OOB中提供了Redis的Cache-Aside缓存架构，可处理高并发访问。

### 1.2. 准备步骤

    这一小节我带着大家一起看看Zero中如何准备Jooq的基本环境，准备步骤完成后，我们再来讲解Jooq中的核心编程接口，参考`up-athena` 项目。

#### 1.2.1. 创建数据表

    使用如下SQL语句初始化您的数据库，默认数据库名`DB_ETERNAL`：

```sql
-- script/database/database-rinit.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 删除原来的数据库
DROP DATABASE IF EXISTS DB_ETERNAL;
CREATE DATABASE IF NOT EXISTS DB_ETERNAL DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_bin;
```

    写好语句保存到文件，然后执行下边脚本，输入密码、则可初始化一个空库：

```shell
#!/usr/bin/env bash
# script/database/database-rinit.sh
/usr/local/mysql/bin/mysql -u root -P 3306 -h 127.0.0.1 -p < database-reinit.sql
echo "[OX] 重建 DB_ETERNAL 数据库成功!";
```

    创建数据表有两种方式，在我们生产环境项目中，使用的是`liquibase`创建数据表，当然您也可以手工创建，在数据库中执行如下代码：

```sql
-- script/database/database-demo.sql
-- liquibase formatted sql

-- changeset Lang:ox-tabular-1
-- 列表数据表专用
DROP TABLE IF EXISTS X_TABULAR;
CREATE TABLE IF NOT EXISTS X_TABULAR
(
    `KEY`        VARCHAR(36) COMMENT '「key」- 列表主键',
    `NAME`       VARCHAR(255) COMMENT '「name」- 列表名称',
    `CODE`       VARCHAR(255) COMMENT '「code」- 列表编号',
    `TYPE`       VARCHAR(255) COMMENT '「type」- 列表类型',
    `ICON`       VARCHAR(255) COMMENT '「icon」- 列表图标',
    `SORT`       INTEGER COMMENT '「sort」- 排序信息',
    `COMMENT`    TEXT COMMENT '「comment」- 备注信息',
    `APP_ID`     VARCHAR(255) COMMENT '「appId」- 关联的应用程序ID',

    -- 特殊字段
    `ACTIVE`     BIT         DEFAULT NULL COMMENT '「active」- 是否启用',
    `SIGMA`      VARCHAR(32) DEFAULT NULL COMMENT '「sigma」- 统一标识',
    `METADATA`   TEXT COMMENT '「metadata」- 附加配置',
    `LANGUAGE`   VARCHAR(8)  DEFAULT NULL COMMENT '「language」- 使用的语言',

    -- Auditor字段
    `CREATED_AT` DATETIME COMMENT '「createdAt」- 创建时间',
    `CREATED_BY` VARCHAR(36) COMMENT '「createdBy」- 创建人',
    `UPDATED_AT` DATETIME COMMENT '「updatedAt」- 更新时间',
    `UPDATED_BY` VARCHAR(36) COMMENT '「updatedBy」- 更新人',
    PRIMARY KEY (`KEY`)
);

-- changeset Lang:ox-tabular-2
ALTER TABLE X_TABULAR
    ADD UNIQUE (`APP_ID`, `TYPE`, `CODE`); -- 每一个应用内的 app - type - code 维持唯一
ALTER TABLE X_TABULAR
    ADD UNIQUE (`SIGMA`, `TYPE`, `CODE`);

ALTER TABLE X_TABULAR
    ADD INDEX IDXM_X_TABULAR_APP_ID_TYPE_ACTIVE (`APP_ID`, `TYPE`, `ACTIVE`);
ALTER TABLE X_TABULAR
    ADD INDEX IDXM_X_TABULAR_SIGMA_TYPE_ACTIVE (`SIGMA`, `TYPE`, `ACTIVE`);
```

> 为了兼容Oracle，所有的SQL关键字以及表名字段名全部统一使用大小写敏感的大写，虽然在Zero中这个动作不是必须，不过推荐使用此种方式处理。

#### 1.2.2. 代码生成

    访问文首链接地址下载所有工具，将工具放在`script/code`目录下（**重要** ），修改配置文件`config/zero-jooq.xml`中的部分核心配置，参考下边的片段部分：

```xml
    <!-- 数据库配置 -->
<jdbc>
    <driver>com.mysql.cj.jdbc.Driver</driver>
    <url>
        <![CDATA[ jdbc:mysql://ox.engine.cn:3306/DB_ETERNAL]]>
    </url>
    <username>root</username>
    <password>????????</password>
</jdbc>
        <!-- 数据表配置 -->
<inputSchema>DB_ETERNAL</inputSchema>
<includes>(^(X_).*)</includes><!-- 表模式前缀 -->
        <!-- 领域模型包名 -->
<target>
<packageName>cn.vertxup.demo.domain</packageName>
<directory>../../src/main/java</directory>
</target>
```

    修改了上述配置后，运行工具脚本，生成对应的领域模型、Dao层代码。脚本运行后，您的项目目录`src/main/java` 中将会有新代码生成。

#### 1.2.3. 配置

    在您的`src/main/resources`目录中准备下边三个资源配置文件（Zero中的Jooq相关核心配置）：

**vertx.yml**

```yaml
zero:
  lime: jooq
  vertx:
    instance:
      - name: athena-demo
        options:
          # Event loop default executing: 120s
          # Worker executing: 1200s -> 20 min
          maxEventLoopExecuteTime: 300_000_000_000
          maxWorkerExecuteTime: 1200_000_000_000
```

**vertx-jooq.yml**

```yaml
# ------------------- 数据库存储 ----------------------
jooq:
  provider:
    driverClassName: "com.mysql.cj.jdbc.Driver"
    username: root
    password: "????"
    hostname: "ox.engine.cn"
    instance: DB_ETERNAL
    jdbcUrl: "jdbc:mysql://ox.engine.cn:3306/DB_ETERNAL"
```

> 注意此处生成代码时使用的是固定数据库，从空库到有表的库转换，且为固定名称 `DB_ETERNAL`。

**vertx-inject.yml**

```yaml
# Database，静态数据库访问专用，访问：DB_ORIGIN_X 元数据库
jooq: io.vertx.tp.plugin.jooq.JooqInfix
```

    最后在Maven中配置MySQL的依赖：

```xml

<dependencies>
    <dependency>
        <!-- 新版 -->
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <!-- 版本可省略，自带了 -->
    </dependency>
</dependencies>
```

### 1.3. 连接测试

    接下来使用JUnit执行下边代码，测试一下连接：

```java
package cn.vertxup.demo;

import cn.vertxup.demo.domain.tables.daos.XTabularDao;
import cn.vertxup.demo.domain.tables.pojos.XTabular;
import io.vertx.core.Future;
import io.vertx.ext.unit.TestContext;
import io.vertx.quiz.JooqBase;
import io.vertx.up.unity.Ux;
import org.junit.Assert;
import org.junit.Test;

import java.util.UUID;

/**
 * @author <a href="http://www.origin-x.cn">lang</a>
 */
public class JqTc extends JooqBase {

    @Test
    public void testInsert(final TestContext context) {
        this.tcAsync(context, this.insertDemo(), actual -> {
            System.out.println(actual);
            Assert.assertNotNull(actual);
        });
    }

    private Future<XTabular> insertDemo() {
        final XTabular tabular = new XTabular();
        tabular.setKey(UUID.randomUUID().toString());
        tabular.setCode("TEST.CODE");
        tabular.setName("测试代码");
        // 插入数据
        return Ux.Jooq.on(XTabularDao.class).insertAsync(tabular);
    }
}
```

    运行测试用例，您将看到如下输出（检查数据库中的数据，您也可以看到新插入的数据信息），如此，Jooq的配置就完成了。     **注意** ：这里使用的XTabular对象是jooq生成的模型对象而不是表对象，生成对象中有两个同名类：`cn.vertxup.demo.domain.tables.pojos.XTabular` 和`cn.vertxup.demo.domain.tables.XTabular`，我们使用的是第一个带有`pojos`包名的模型对象，这点经常在环境中容易搞错。

```shell
XTabular (968a5f94-5514-42ad-adbe-eaef4ec6e297, 测试代码, TEST.CODE, \
    null, null, null, null, null, null, null, null, null, null, null, null, null)
```

## 「贰」常用操作

    根据第一章节的引导，您的环境配置就已经彻底完成了，那么本章节我们来看看基本的CRUD操作，通过对本章节的学习，让读者理解Zero中如何实现数据库的基本CRUD，Zero中提供了八大类的常用数据库操作：

* 添加/批量添加
* 更新/批量更新
* 删除/批量删除/按添加删除
* 保存：添加 + 更新
* 存在丢失检查
* 搜索
* 各种查找编程接口
* 聚集/分组

    Zero中操作数据库的核心对象为`io.vertx.up.uca.jooq.UxJooq`，它的实例化方式如下：

```java
/*
 * 其中 XTabularDao 为 jooq-codegen 生成
 * Zero 中并不限制用户实例化 UxJooq 的方式，它的构造函数是 public 的，但使用下边的代码是标准方式，
 * 它会启用 Zero 内部的对象池，保证多次实例化时不生成多余的数据库访问对象，在模型级别实现了“单件模式”
 * 同一个模型只会生成唯一的一个`UxJooq`对象，所以推荐读者使用下边代码实例化 UxJooq 对象。
 **/
UxJooq jooq=Ux.Jooq.on(XTabularDao.class)
```

    为了提高读者的辨识度，后边所有的示例代码中定义的变量如下：

|       变量名称 |       数据类型 | 含义                  |
| ---------: | ---------: | ------------------- |
|          t |        泛型T | 生成的实体类型             |
|       list |  `List<T>` | 生成的实体列表集合类型         |
|        key |       泛型ID | 主键类型，可以是字符串，可以是整数   |
|   criteria | JsonObject | 查询条件                |
|      query | JsonObject | 带分页、排序、列过滤的查询引擎完整条件 |
|    jobject | JsonObject | 可序列化的Json单条数据       |
|     jarray |  JsonArray | 可序列化的Json多条数据       |
| collection |       集合类型 | 可以是Set也可以是List      |
|      array |        \[] | 数组类型，也可以是变参类似 T...  |
|       pojo |     String | POJO模式中的文件名（支持映射）   |
|      field |     String | 字段名称                |
|       aggr |     String | 聚集字段名称              |
|      value |     Object | 字段传入值               |

    初次接触这份基础编程接口时，很多读者会觉得数量繁多不太容易记住，但实际上这是在实际项目中使用过后的一份总结，为了方便开发人员，对很多编程接口进行了扩展，主要扩展点如下：

* 由于很多遗留系统使用的字段名和期望字段名不匹配，如旧系统使用的是：`sname`，而客户端的新请求在迁移过程中使用的是：`name` ，这种情况下，很多开发人员不得不修订基础字段，更有甚者会修改数据库中的列名，而Zero框架中不需要这样做，Zero中引入了一层映射层，可以通过`pojo` 的配置文件将输入的数据直接映射到实体(`T`) 信息中，这样开发人员就不会为了字段的变更而烦恼，简单说，输入数据的字段、POJO模型的字段直接解耦，防止二者不匹配的情况。
*

Vert.x中最常用的数据结构是JsonObject/JsonArray，为了让开发人员可以不去思考序列化的细节，所以Zero引入了默认序列化机制，调用这种类型的编程接口，只要定义了Dao类型，开发人员可以直接将数据丢给Jooq来完成数据库的访问，这种场景下甚至不需要开发人员去构造生成的领域模型。

* 数据库访问是在遗留系统和Vert.x的纯异步系统中演化而来的，所以在提供编程的API时，所有的接口都有同步、异步两个版本，带`Async` 关键字的就是异步版本，而同步版本在某些场景中依然实用。

    为了辅助开发人员记忆和使用，参考下边的规则来理解每一种操作扩展过后的API含义。

1. 第一个维度是同步和异步，主要分**两种**：带`Async`的是异步版本。
2. 第二个维度是输入，主要分**三种**：领域模型、Json数据、带映射层（Pojo）的Json数据。
3. 第三个维度是返回值，主要分**四种**：`T、List<T>、JsonObject、JsonArray`。

### 2.1. 基础增删改

#### 2.1.1. 新增

    **新增**接口的骨架代码：

```java

    // --> 返回值：T / Future<T>
    // 新增：T -> T
    Ux.Jooq.on(XTabularDao.class).insert(t);
    Ux.Jooq.on(XTabularDao.class).insertAsync(t);
    // 新增：JsonObject -> T
    Ux.Jooq.on(XTabularDao.class).insert(jobject);
    Ux.Jooq.on(XTabularDao.class).insertAsync(jobject);
    // 新增：JsonObject + pojo -> T
    Ux.Jooq.on(XTabularDao.class).insert(jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).insertAsync(jobject,pojo);

    // --> 返回值：List<T> / Future<List<T>>
    // 新增：List<T> -> List<T>
    Ux.Jooq.on(XTabularDao.class).insert(list);
    Ux.Jooq.on(XTabularDao.class).insertAsync(list);
    // 新增：JsonArray -> List<T>
    Ux.Jooq.on(XTabularDao.class).insert(jarray);
    Ux.Jooq.on(XTabularDao.class).insertAsync(jarray);
    // 新增：JsonArray + pojo -> List<T>
    Ux.Jooq.on(XTabularDao.class).insert(jarray,pojo);
    Ux.Jooq.on(XTabularDao.class).insertAsync(jarray,pojo);

    // --> 返回值：JsonObject / Future<JsonObject>
    // 新增：T -> JsonObject
    Ux.Jooq.on(XTabularDao.class).insertJ(t);
    Ux.Jooq.on(XTabularDao.class).insertJAsync(t);
    // 新增：JsonObject -> JsonObject
    Ux.Jooq.on(XTabularDao.class).insertJ(jobject);
    Ux.Jooq.on(XTabularDao.class).insertJAsync(jobject);
    // 新增：JsonObject + pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).insertJ(jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).insertJAsync(jobject,pojo);

    // --> 返回值：JsonArray / Future<JsonArray>
    // 新增：List<T> -> JsonArray
    Ux.Jooq.on(XTabularDao.class).insertJ(list);
    Ux.Jooq.on(XTabularDao.class).insertJAsync(list);
    // 新增：JsonArray -> JsonArray
    Ux.Jooq.on(XTabularDao.class).insertJ(jarray);
    Ux.Jooq.on(XTabularDao.class).insertJAsync(jarray);
    // 新增：JsonArray + pojo -> JsonArray
    Ux.Jooq.on(XTabularDao.class).insertJ(jarray,pojo);
    Ux.Jooq.on(XTabularDao.class).insertJAsync(jarray,pojo);      
```

>

新增接口只有一点需要说明，如果传入的实体、JsonObject数据本身没有主键值，那么Zero会使用UUID的方式为主键赋值，生成一个新的主键，并且在返回的数据中会带上该主键信息，为了配合前端开发，Zero中推荐所有主键使用属性名`key` 而不是使用传统常用的`id`，当然如果开发人员定义了自己的主键，那么Zero会从生成的jooq代码中自动识别。

    将上述代码统计一下，可得到下边的表格，其中`领域模型T、Json数据、带Pojo映射层`为新增接口的入参搭配。

|              |                  返回值 | 领域模型 T    | Json数据     | 带Pojo映射层 |
| -----------: | -------------------: | --------- | ---------- | -------- |
|       insert |                    T | T         |            |          |
|              |                    T |           | JsonObject |          |
|              |                    T |           | JsonObject | String   |
|              |            `List<T>` | `List<T>` |            |          |
|              |            `List<T>` |           | JsonArray  |          |
|              |            `List<T>` |           | JsonArray  | String   |
|      insertJ |           JsonObject | T         |            |          |
|              |           JsonObject |           | JsonObject |          |
|              |           JsonObject |           | JsonObject | String   |
|              |            JsonArray | `List<T>` |            |          |
|              |            JsonArray |           | JsonArray  |          |
|              |            JsonArray |           | JsonArray  | String   |
|  insertAsync |          `Future<T>` | T         |            |          |
|              |          `Future<T>` |           | JsonObject |          |
|              |          `Future<T>` |           | JsonObject | String   |
|              |    `Future<List<T>>` | `List<T>` |            |          |
|              |    `Future<List<T>>` |           | JsonArray  |          |
|              |    `Future<List<T>>` |           | JsonArray  | String   |
| insertJAsync | `Future<JsonObject>` | T         |            |          |
|              | `Future<JsonObject>` |           | JsonObject |          |
|              | `Future<JsonObject>` |           | JsonObject | String   |
|              |  `Future<JsonArray>` | `List<T>` |            |          |
|              |  `Future<JsonArray>` |           | JsonArray  |          |
|              |  `Future<JsonArray>` |           | JsonArray  | String   |

#### 2.1.2. 搜索

    **搜索**接口的骨架代码：

> 先写查询接口的教程，主要原因是后续编程接口都会牵涉带条件查询，在查询过程中让读者对Zero中的查询引擎有所了解，然后再慢慢来深入到查询引擎部分。

```java
    // 「搜索」
    // --> 返回值：JsonObject / Future<JsonObject>
    // 搜索：带分页、排序、列过滤、条件的查询引擎专用接口
    Ux.Jooq.on(XTabularDao.class).search(query);
    Ux.Jooq.on(XTabularDao.class).searchAsync(query);
    // 搜索：带分页、排序、列过滤、条件的查询引擎专用接口，支持POJO模式
    Ux.Jooq.on(XTabularDao.class).search(query,pojo);
    Ux.Jooq.on(XTabularDao.class).searchAsync(query,pojo);         
```

    搜索是读取数据中最简单的接口，因为它只包含了两种模式：带POJO映射和不带POJO映射，带POJO映射的模式中，输入和输出的字段名都是调用POJO映射之前的字段名，只有内部的领域模型可能不是该名称，这样整个数据转换过程对开发人员都是透明的。

**输入格式**

    Zero中的查询引擎详细语法在后边的查询引擎部分详细讲解，此处不再加以说明。

**输出格式**

    搜索接口的输出在Zero中使用下边这种固定格式：

```json
{
    "count": 0,
    "list": []
}
```

|   字段名 | 数据类型   | 业务含义                |
| ----: | ------ | ------------------- |
| count | 整数值    | 总数据量                |
|  list | Json数组 | 当前页数据（带分页参数） / 完整数据 |

#### 2.1.3. 读取

    **读取**接口的骨架代码：

```java
    // 「集合返回」返回值是多条记录
    // --> 返回值：List<T> / Future<List<T>>
    // 查找：无参 -> List<T> 
    Ux.Jooq.on(XTabularDao.class).fetchAll();
    Ux.Jooq.on(XTabularDao.class).fetchAllAsync();
    // 查找：field = value -> List<T> ，不支持POJO模式
    Ux.Jooq.on(XTabularDao.class).fetch(field,value);
    Ux.Jooq.on(XTabularDao.class).fetchAsync(field,value);
    // 查找：全条件 -> List<T>，全条件查找
    Ux.Jooq.on(XTabularDao.class).fetch(criteria);
    Ux.Jooq.on(XTabularDao.class).fetchAsync(criteria);
    // 查找：全条件带POJO -> List<T>
    Ux.Jooq.on(XTabularDao.class).fetch(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).fetchAsync(criteria,pojo);

    // 「集合返回」协变类型
    // --> 返回值：List<T> / Future<List<T>>
    // 快速查询：In，第二参为 JsonArray
    Ux.Jooq.on(XTabularDao.class).fetchIn(field,jarray);
    Ux.Jooq.on(XTabularDao.class).fetchInAsync(field,jarray);
    // 快速查询：In，第二参为 List, Set 或其他 Collection
    Ux.Jooq.on(XTabularDao.class).fetchIn(field,collection);
    Ux.Jooq.on(XTabularDao.class).fetchInAsync(field,collection);
    // 快速查询：In，第二参为 [] 类型或变参
    Ux.Jooq.on(XTabularDao.class).fetchIn(field,array);
    Ux.Jooq.on(XTabularDao.class).fetchInAsync(field,array);

    // 「集合返回」协变类型
    // --> 返回值：List<T> / Future<List<T>>
    // 快速查询：AND, criteria -> List<T>
    Ux.Jooq.on(XTabularDao.class).fetchAnd(criteria);
    Ux.Jooq.on(XTabularDao.class).fetchAndAsync(criteria);
    // 快速查询：AND，带Pojo模式, criteria + pojo -> List<T>
    Ux.Jooq.on(XTabularDao.class).fetchAnd(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).fetchAndAsync(criteria,pojo);
    // 快速查询：Or, criteria -> List<T>
    Ux.Jooq.on(XTabularDao.class).fetchOr(criteria);
    Ux.Jooq.on(XTabularDao.class).fetchOrAsync(criteria);
    // 快速查询：Or，带Pojo模式, criteria + pojo -> List<T>
    Ux.Jooq.on(XTabularDao.class).fetchOr(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).fetchOrAsync(criteria,pojo);

    // ----- 下边是Json版本
    // --> 返回值：JsonArray / Future<JsonArray>
    // 「集合返回」返回值是多条记录
    // 查找：无参 -> JsonArray
    Ux.Jooq.on(XTabularDao.class).fetchJAll();
    Ux.Jooq.on(XTabularDao.class).fetchJAllAsync();
    // 查找：pojo -> JsonArray
    Ux.Jooq.on(XTabularDao.class).fetchJAll(pojo);
    Ux.Jooq.on(XTabularDao.class).fetchJAllAsync(pojo);

    // 「集合返回」协变类型，In类型中的JsonArray返回不支持外置的pojo映射
    // --> 返回值：JsonArray / Future<JsonArray>
    // 快速查询：In，第二参为 JsonArray
    Ux.Jooq.on(XTabularDao.class).fetchJIn(field,jarray);
    Ux.Jooq.on(XTabularDao.class).fetchJInAsync(field,jarray);
    // 快速查询：In，第二参为 List, Set 或其他 Collection
    Ux.Jooq.on(XTabularDao.class).fetchJIn(field,collection);
    Ux.Jooq.on(XTabularDao.class).fetchJInAsync(field,collection);
    // 快速查询：In，第二参为 [] 类型或变参
    Ux.Jooq.on(XTabularDao.class).fetchJIn(field,array);
    Ux.Jooq.on(XTabularDao.class).fetchJInAsync(field,array);

    // 「集合返回」协变类型
    // --> 返回值：JsonArray / Future<JsonArray>
    // 快速查询：AND, criteria -> JsonArray
    Ux.Jooq.on(XTabularDao.class).fetchJAnd(criteria);
    Ux.Jooq.on(XTabularDao.class).fetchJAndAsync(criteria);
    // 快速查询：AND，带Pojo模式, criteria + pojo -> JsonArray
    Ux.Jooq.on(XTabularDao.class).fetchJAnd(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).fetchJAndAsync(criteria,pojo);
    // 快速查询：Or, criteria -> JsonArray
    Ux.Jooq.on(XTabularDao.class).fetchJOr(criteria);
    Ux.Jooq.on(XTabularDao.class).fetchJOrAsync(criteria);
    // 快速查询：Or，带Pojo模式, criteria + pojo -> JsonArray
    Ux.Jooq.on(XTabularDao.class).fetchJOr(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).fetchJOrAsync(criteria,pojo);

    // 「单记录返回」返回值是唯一记录
    // --> 返回值：T / Future<T>
    // 查找：按主键读取 key -> T
    Ux.Jooq.on(XTabularDao.class).fetchById(key);
    Ux.Jooq.on(XTabularDao.class).fetchByIdAsync(key);
    // 查找：单字段查找 field, value -> T
    Ux.Jooq.on(XTabularDao.class).fetchOne(field,value);
    Ux.Jooq.on(XTabularDao.class).fetchOneAsync(field,value);
    // 查找：全条件查找（强制And）criteria -> T
    Ux.Jooq.on(XTabularDao.class).fetchOne(condition);
    Ux.Jooq.on(XTabularDao.class).fetchOneAsync(condition);
    // 查找：全条件查找（Pojo模式）criteria + pojo -> T
    Ux.Jooq.on(XTabularDao.class).fetchOne(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).fetchOneAsync(criteria,pojo);

    // 「单记录返回」Json版本 key -> JsonObject
    // --> 返回值：JsonObject / Future<JsonObject>
    Ux.Jooq.on(XTabularDao.class).fetchJById(key);
    Ux.Jooq.on(XTabularDao.class).fetchJByIdAsync(key);
    // 查找：单字段查找 field, value -> JsonObject
    Ux.Jooq.on(XTabularDao.class).fetchJOne(field,value);
    Ux.Jooq.on(XTabularDao.class).fetchJOneAsync(field,value);
    // 查找：全条件查找（强制And）criteria -> JsonObject
    Ux.Jooq.on(XTabularDao.class).fetchJOne(condition);
    Ux.Jooq.on(XTabularDao.class).fetchJOneAsync(condition);
    // 查找：全条件查找（Pojo模式）criteria + pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).fetchJOne(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).fetchJOneAsync(criteria,pojo);   
```

    Zero中的读取接口内容最多，但实际上也可以分类来记忆，它和Insert唯一的不同点是入参的搭配有些差异：

|                 |                  返回值 | 字段kv/主键            | criteria条件    | 带Pojo映射层 |
| --------------: | -------------------: | ------------------ | ------------- | -------- |
|        fetchAll |            `List<T>` |                    |               |          |
|       fetchJAll |          `JsonArray` |                    | String        |          |
|   fetchAllAsync |    `Future<List<T>>` |                    |               |          |
|  fetchJAllAsync |  `Future<JsonArray>` |                    | String        |          |
|           fetch |            `List<T>` |                    | String,Object |          |
|                 |            `List<T>` | JsonObject         |               |          |
|       `List<T>` |           JsonObject | String             |               |          |
|          fetchJ |          `JsonArray` | String,Object      |               |          |
|                 |          `JsonArray` | JsonObject         |               |          |
|     `JsonArray` |           JsonObject | String             |               |          |
|      fetchAsync |    `Future<List<T>>` | String,Object      |               |          |
|                 |    `Future<List<T>>` | JsonObject         |               |          |
|                 |    `Future<List<T>>` | JsonObject         | String        |          |
|     fetchJAsync |  `Future<JsonArray>` | String,Object      |               |          |
|                 |  `Future<JsonArray>` | JsonObject         |               |          |
|                 |  `Future<JsonArray>` | JsonObject         | String        |          |
|         fetchIn |            `List<T>` | String, Collection |               |          |
|                 |            `List<T>` | String, Object...  |               |          |
|                 |            `List<T>` | String, JsonArray  |               |          |
|        fetchJIn |          `JsonArray` | String, Collection |               |          |
|                 |          `JsonArray` | String, Object...  |               |          |
|                 |          `JsonArray` | String,JsonArray   |               |          |
|    fetchInAsync |    `Future<List<T>>` | String, Collection |               |          |
|                 |    `Future<List<T>>` | String, Object...  |               |          |
|                 |    `Future<List<T>>` | String, JsonArray  |               |          |
|   fetchJInAsync |  `Future<JsonArray>` | String, Collection |               |          |
|                 |  `Future<JsonArray>` | String, Object...  |               |          |
|                 |  `Future<JsonArray>` | String, JsonArray  |               |          |
|        fetchAnd |            `List<T>` | JsonObject         |               |          |
|                 |            `List<T>` | JsonObject         | String        |          |
|       fetchJAnd |          `JsonArray` | JsonObject         |               |          |
|                 |          `JsonArray` | JsonObject         | String        |          |
|   fetchAndAsync |    `Future<List<T>>` | JsonObject         |               |          |
|                 |    `Future<List<T>>` | JsonObject         | String        |          |
|  fetchJAndAsync |  `Future<JsonArray>` | JsonObject         |               |          |
|                 |  `Future<JsonArray>` | JsonObject         | String        |          |
|         fetchOr |            `List<T>` | JsonObject         |               |          |
|                 |            `List<T>` | JsonObject         | String        |          |
|        fetchJOr |          `JsonArray` | JsonObject         |               |          |
|                 |          `JsonArray` | JsonObject         | String        |          |
|    fetchOrAsync |    `Future<List<T>>` | JsonObject         |               |          |
|                 |    `Future<List<T>>` | JsonObject         | String        |          |
|   fetchJOrAsync |  `Future<JsonArray>` | JsonObject         |               |          |
|                 |  `Future<JsonArray>` | JsonObject         | String        |          |
|       fetchById |                    T | key, Object        |               |          |
|  fetchByIdAsync |          `Future<T>` | key, Object        |               |          |
|      fetchJById |           JsonObject | key, Object        |               |          |
| fetchJByIdAsync | `Future<JsonObject>` | key, Object        |               |          |
|        fetchOne |                    T | key, Object        |               |          |
|                 |                    T | JsonObject         |               |          |
|                 |                    T | JsonObject         | String        |          |
|       fetchJOne |           JsonObject | key, Object        |               |          |
|                 |           JsonObject | JsonObject         |               |          |
|                 |           JsonObject | JsonObject         | String        |          |
|   fetchOneAsync |          `Future<T>` | key, Object        |               |          |
|                 |          `Future<T>` | JsonObject         |               |          |
|                 |          `Future<T>` | JsonObject         | String        |          |
|  fetchJOneAsync | `Future<JsonObject>` | key, Object        |               |          |
|                 | `Future<JsonObject>` | JsonObject         |               |          |
|                 | `Future<JsonObject>` | JsonObject         | String        |          |

    编程接口的扩展在于是否使用**领域模型T**、**字段级查询**、**映射层使用**，这里再强调一下此处三个知识点的核心使用场景：

* **领域模型**：在Java语言中，可以用`class` 来定义一个领域模型，定义过后基于JavaBean的规范，可使用不同的API对该模型中的数据进行访问；但在Vert.x编程中，很多地方都不使用Java中的对象，取而代之的是简化过后的`JsonObject` 和`JsonArray` ，于是就出现了是否使用领域模型的API分离。
* **字段级查询**：程序访问数据库的过程中，**单条件** 一直是一个高频场景，不论是单条件单值还是单条件多值，在编程过程中都是业务系统的核心，所以Zero提供了快速的字段查询功能，如`fetchIn` 和`fetch` 中的`(field, value)`方法签名模式，方便开发人员直接查询相关数据。
* **映射层**：映射层是一个附加功能，主要目的是做接口兼容，不论是旧系统和新系统做对接，还是新系统和旧系统做对接，两边的系统都不可能绝对统一，为了保证数据字段的灵活性，对两边的 **模型属性名**进行解耦，于是Zero提供了**映射层** 的机制，使得集成开发变得更加简单和流畅。

#### 2.1.4. 更新

    **更新**接口的骨架代码：

```java
    // 更新
    // --> 返回值：T / Future<T>
    // 直接更新：T -> T
    Ux.Jooq.on(XTabularDao.class).update(t);
    Ux.Jooq.on(XTabularDao.class).updateAsync(t);
    // 直接更新：JsonObject -> T
    Ux.Jooq.on(XTabularDao.class).update(jobject);
    Ux.Jooq.on(XTabularDao.class).updateAsync(jobject);
    // 直接更新：JsonObject + pojo -> T
    Ux.Jooq.on(XTabularDao.class).update(jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).updateAsync(jobject,pojo);

    // --> 返回值：List<T> / Future<List<T>>
    // 批量更新，其中 list 的类型为 java.util.List<T>
    Ux.Jooq.on(XTabularDao.class).update(list);
    Ux.Jooq.on(XTabularDao.class).updateAsync(list);
    // 更新：JsonArray -> List<T>
    Ux.Jooq.on(XTabularDao.class).update(jarray);
    Ux.Jooq.on(XTabularDao.class).updateAsync(jarray);
    // 更新：JsonArray + pojo -> List<T>
    Ux.Jooq.on(XTabularDao.class).update(jarray,pojo);
    Ux.Jooq.on(XTabularDao.class).updateAsync(jarray,pojo);

    // 带条件的更新
    // --> 返回值：T / Future<T>
    // 更新：按 id 更新, T -> T
    Ux.Jooq.on(XTabularDao.class).update(key,t);
    Ux.Jooq.on(XTabularDao.class).updateAsync(key,t);
    // 更新：按 id 更新, JsonObject -> T
    Ux.Jooq.on(XTabularDao.class).update(key,jobject);
    Ux.Jooq.on(XTabularDao.class).updateAsync(key,jobject);
    // 更新：按 id 更新, JsonObject + pojo -> T
    Ux.Jooq.on(XTabularDao.class).update(key,jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).updateAsync(key,jobject,pojo);

    // --> 返回值：T / Future<T>
    // 更新：按条件更新 JsonObject, T -> T
    Ux.Jooq.on(XTabularDao.class).update(criteria,t);
    Ux.Jooq.on(XTabularDao.class).updateAsync(criteria,t);
    // 更新：按条件更新 JsonObject, JsonObject -> T        
    Ux.Jooq.on(XTabularDao.class).update(criteria,jobject);
    Ux.Jooq.on(XTabularDao.class).updateAsync(criteria,jobject);

    // --> 返回值：T / Future<T>
    // 更新：按条件更新（带POJO模式）, JsonObject, T, pojo -> T
    Ux.Jooq.on(XTabularDao.class).update(criteria,t,pojo);
    Ux.Jooq.on(XTabularDao.class).updateAsync(criteria,t,pojo);
    // 更新：按条件更新（带POJO模式）, JsonObject, JsonObject, pojo -> T
    Ux.Jooq.on(XTabularDao.class).update(criteria,jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).updateAsync(criteria,jobject,pojo);

    // ----- 下边是Json版本
    // --> 返回值：JsonObject / Future<JsonObject>
    // 直接更新：T -> JsonObject
    Ux.Jooq.on(XTabularDao.class).updateJ(t);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(t);
    // 直接更新：JsonObject -> JsonObject
    Ux.Jooq.on(XTabularDao.class).updateJ(jobject);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(jobject);
    // 直接更新：JsonObject + pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).updateJ(jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(jobject,pojo);

    // --> 返回值：JsonArray / Future<JsonArray>
    // 批量更新，其中 list 的类型为 java.util.List<T>
    Ux.Jooq.on(XTabularDao.class).updateJ(list);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(list);
    // 更新：JsonArray -> JsonArray
    Ux.Jooq.on(XTabularDao.class).updateJ(jarray);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(jarray);
    // 更新：JsonArray + pojo -> JsonArray
    Ux.Jooq.on(XTabularDao.class).updateJ(jarray,pojo);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(jarray,pojo);

    // --> 返回值：JsonObject / Future<JsonObject>
    // 带条件的更新
    // 更新：按 id 更新, T -> JsonObject
    Ux.Jooq.on(XTabularDao.class).updateJ(key,t);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(key,t);
    // 更新：按 id 更新, JsonObject -> JsonObject
    Ux.Jooq.on(XTabularDao.class).updateJ(key,jobject);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(key,jobject);
    // 更新：按 id 更新, JsonObject + pojo -> T
    Ux.Jooq.on(XTabularDao.class).updateJ(key,jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(key,jobject,pojo);

    // --> 返回值：JsonObject / Future<JsonObject>
    // 更新：按条件更新 JsonObject, T -> JsonObject
    Ux.Jooq.on(XTabularDao.class).updateJ(criteria,t);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(criteria,t);
    // 更新：按条件更新 JsonObject, JsonObject -> JsonObject        
    Ux.Jooq.on(XTabularDao.class).updateJ(criteria,jobject);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(criteria,jobject);

    // --> 返回值：JsonObject / Future<JsonObject>
    // 更新：按条件更新（带POJO模式）, JsonObject, T, pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).updateJ(criteria,t,pojo);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(criteria,t,pojo);
    // 更新：按条件更新（带POJO模式）, JsonObject, JsonObject, pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).updateJ(criteria,jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).updateJAsync(criteria,jobject,pojo);
```

    **更新**接口的二维表如下：

|              |                  返回值 | 领域模型 T    | Json数据     | 带Pojo映射层 | 更新条件     |
| -----------: | -------------------: | --------- | ---------- | -------- | -------- |
|       update |                    T | T         |            |          |          |
|              |                    T |           | JsonObject |          |          |
|              |                    T |           | JsonObject | String   |          |
|              |                    T | T         |            |          | key      |
|              |                    T |           | JsonObject |          | key      |
|              |                    T |           | JsonObject | String   | key      |
|              |                    T | T         |            |          | criteria |
|              |                    T |           | JsonObject |          | criteria |
|              |                    T | T         |            | String   | criteria |
|              |                    T |           | JsonObject | String   | criteria |
|              |            `List<T>` | `List<T>` |            |          |          |
|              |            `List<T>` |           | JsonArray  |          |          |
|              |            `List<T>` |           | JsonArray  | String   |          |
|      updateJ |           JsonObject | T         |            |          |          |
|              |           JsonObject |           | JsonObject |          |          |
|              |           JsonObject |           | JsonObject | String   |          |
|              |           JsonObject | T         |            |          | key      |
|              |           JsonObject |           | JsonObject |          | key      |
|              |           JsonObject |           | JsonObject | String   | key      |
|              |           JsonObject | T         |            |          | criteria |
|              |           JsonObject |           | JsonObject |          | criteria |
|              |           JsonObject | T         |            | String   | criteria |
|              |           JsonObject |           | JsonObject | String   | criteria |
|              |            JsonArray | `List<T>` |            |          |          |
|              |            JsonArray |           | JsonArray  |          |          |
|              |            JsonArray |           | JsonArray  | String   |          |
|  updateAsync |          `Future<T>` | T         |            |          |          |
|              |          `Future<T>` |           | JsonObject |          |          |
|              |          `Future<T>` |           | JsonObject | String   |          |
|              |          `Future<T>` | T         |            |          | key      |
|              |          `Future<T>` |           | JsonObject |          | key      |
|              |          `Future<T>` |           | JsonObject | String   | key      |
|              |          `Future<T>` | T         |            |          | criteria |
|              |          `Future<T>` |           | JsonObject |          | criteria |
|              |          `Future<T>` | T         |            | String   | criteria |
|              |          `Future<T>` |           | JsonObject | String   | criteria |
|              |    `Future<List<T>>` | `List<T>` |            |          |          |
|              |    `Future<List<T>>` |           | JsonArray  |          |          |
|              |    `Future<List<T>>` |           | JsonArray  | String   |          |
| updateJAsync | `Future<JsonObject>` | T         |            |          |          |
|              | `Future<JsonObject>` |           | JsonObject |          |          |
|              | `Future<JsonObject>` |           | JsonObject | String   |          |
|              | `Future<JsonObject>` | T         |            |          | key      |
|              | `Future<JsonObject>` |           | JsonObject |          | key      |
|              | `Future<JsonObject>` |           | JsonObject | String   | key      |
|              | `Future<JsonObject>` | T         |            |          | criteria |
|              | `Future<JsonObject>` |           | JsonObject |          | criteria |
|              | `Future<JsonObject>` | T         |            | String   | criteria |
|              | `Future<JsonObject>` |           | JsonObject | String   | criteria |
|              |  `Future<JsonArray>` | `List<T>` |            |          |          |
|              |  `Future<JsonArray>` |           | JsonArray  |          |          |
|              |  `Future<JsonArray>` |           | JsonArray  | String   |          |

#### 2.1.5. 合并

    **合并**操作和其他操作有本质的区别，其区别点就在于语义上的不同，**合并** 操作具有语义：“按某个条件进行合并”，所以合并操作不支持`method(T)`或`method(list)` 这种方法直接合并。Zero中的Jooq合并基本规则如下：

1. 编程接口必须提供参数**按什么条件**合并，目前支持`criteria, key`和查找函数`finder`三种，查找函数`finder` 主要用于批量合并时判断两条记录是否具有同样语义。
2. 合并内部会调用核心CRUD接口，主要包含了INSERT和UPDATE两种操作，如果可以找到记录则执行更新，反之找不到记录执行插入。

    **合并**接口的骨架代码：

```java
// 「单条合并」
// --> 返回值：T / Future<T>
// 合并：按 id 合并, T -> T
Ux.Jooq.on(XTabularDao.class).upsert(key,t);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(key,t);
    // 合并：按 id 和 Json 合并，JsonObject -> T
    Ux.Jooq.on(XTabularDao.class).upsert(key,jobject);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(key,jobject);
    // 合并：按 id 和 Json 合并，JsonObject + pojo -> T
    Ux.Jooq.on(XTabularDao.class).upsert(key,jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(key,jobject,pojo);

    // --> 返回值：T / Future<T>
    // 合并：按条件合并，T -> T
    Ux.Jooq.on(XTabularDao.class).upsert(criteria,t);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(criteria,t);
    // 合并：按条件和 Json 合并，JsonObject -> T
    Ux.Jooq.on(XTabularDao.class).upsert(criteria,jobject);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(criteria,jobject);

    // --> 返回值：T / Future<T>
    // 合并：按条件合并（POJO模式），T + pojo -> T
    Ux.Jooq.on(XTabularDao.class).upsert(criteria,t,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(criteria,t,pojo);
    // 合并：按条件和 Json 合并（POJO模式），JsonObject + pojo -> T
    Ux.Jooq.on(XTabularDao.class).upsert(criteria,jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(criteria,jobject,pojo);

    // 「单条合并」Json版本
    // --> 返回值：JsonObject / Future<JsonObject>
    // 合并：按 id 合并，T -> JsonObject
    Ux.Jooq.on(XTabularDao.class).upsertJ(key,t);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(key,t);
    // 合并：按 id 和 Json 合并，JsonObject -> JsonObject
    Ux.Jooq.on(XTabularDao.class).upsertJ(key,jobject);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(key,jobject);
    // 合并：按 id 和 Json 合并，JsonObject + pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).upsertJ(key,jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(key,jobject,pojo);

    // --> 返回值：JsonObject / Future<JsonObject>
    // 合并：按条件合并，T -> JsonObject
    Ux.Jooq.on(XTabularDao.class).upsertJ(criteria,t);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(criteria,t);
    // 合并：按条件和 Json 合并，JsonObject -> JsonObject
    Ux.Jooq.on(XTabularDao.class).upsertJ(criteria,jobject);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(criteria,jobject);

    // --> 返回值：JsonObject / Future<JsonObject>
    // 合并：按条件合并（POJO模式），T + pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).upsertJ(criteria,t,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(criteria,t,pojo);
    // 合并：按条件和 Json 合并（POJO模式），JsonObject + pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).upsertJ(criteria,jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(criteria,jobject,pojo);

    // 「批量合并」
    // --> 返回值：List<T> / Future<List<T>>
    // 合并：按条件函数finder, List<T> -> List<T>
    Ux.Jooq.on(XTabularDao.class).upsert(criteria,list,finder);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(criteria,list,finder);
    // 合并：按条件函数finder, JsonArray -> List<T>
    Ux.Jooq.on(XTabularDao.class).upsert(criteria,jarray,finder);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(criteria,jarray,finder);

    // --> 返回值：List<T> / Future<List<T>>
    // 合并：按条件函数finder（POJO模式）, List<T> -> List<T>
    Ux.Jooq.on(XTabularDao.class).upsert(criteria,list,finder,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(criteria,list,finder,pojo);
    // 合并：按条件函数finder（POJO模式）, JsonArray -> List<T>
    Ux.Jooq.on(XTabularDao.class).upsert(criteria,jarray,finder,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertAsync(criteria,jarray,finder,pojo);

    // 「批量合并」Json版本
    // --> 返回值：JsonArray / Future<JsonArray>
    // 合并：按条件函数finder, List<T> -> JsonArray
    Ux.Jooq.on(XTabularDao.class).upsertJ(criteria,list,finder);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(criteria,list,finder);
    // 合并：按条件函数finder, JsonArray -> JsonArray
    Ux.Jooq.on(XTabularDao.class).upsertJ(criteria,jarray,finder);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(criteria,jarray,finder);

    // --> 返回值：JsonArray / Future<JsonArray>
    // 合并：按条件函数finder（POJO模式）, List<T> -> JsonArray
    Ux.Jooq.on(XTabularDao.class).upsertJ(criteria,list,finder,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(criteria,list,finder,pojo);
    // 合并：按条件函数finder（POJO模式）, JsonArray -> JsonArray
    Ux.Jooq.on(XTabularDao.class).upsertJ(criteria,jarray,finder,pojo);
    Ux.Jooq.on(XTabularDao.class).upsertJAsync(criteria,jarray,finder,pojo);
```

    **合并**接口的二维表如下：

|              |                  返回值 | 领域模型 T    | Json数据     | 带Pojo映射层 | 更新条件     |
| -----------: | -------------------: | --------- | ---------- | -------- | -------- |
|       upsert |                    T | T         |            |          | key      |
|              |                    T |           | JsonObject |          | key      |
|              |                    T |           | JsonObject | String   | key      |
|              |                    T | T         |            |          | criteria |
|              |                    T | T         |            | String   | criteria |
|              |                    T |           | JsonObject |          | criteria |
|              |                    T |           | JsonObject | String   | criteria |
|              |            `List<T>` | `List<T>` |            |          | criteria |
|              |            `List<T>` | `List<T>` |            | String   | criteria |
|              |            `List<T>` |           | JsonArray  |          | criteria |
|              |            `List<T>` |           | JsonArray  | String   | criteria |
|  upsertAsync |          `Future<T>` | T         |            |          | key      |
|              |          `Future<T>` |           | JsonObject |          | key      |
|              |          `Future<T>` |           | JsonObject | String   | key      |
|              |          `Future<T>` | T         |            |          | criteria |
|              |          `Future<T>` | T         |            | String   | criteria |
|              |          `Future<T>` |           | JsonObject |          | criteria |
|              |          `Future<T>` |           | JsonObject | String   | criteria |
|              |    `Future<List<T>>` | `List<T>` |            |          | criteria |
|              |    `Future<List<T>>` | `List<T>` |            | String   | criteria |
|              |    `Future<List<T>>` |           | JsonArray  |          | criteria |
|              |    `Future<List<T>>` |           | JsonArray  | String   | criteria |
|      upsertJ |           JsonObject | T         |            |          | key      |
|              |           JsonObject |           | JsonObject |          | key      |
|              |           JsonObject |           | JsonObject | String   | key      |
|              |           JsonObject | T         |            |          | criteria |
|              |           JsonObject | T         |            | String   | criteria |
|              |           JsonObject |           | JsonObject |          | criteria |
|              |           JsonObject |           | JsonObject | String   | criteria |
|              |            JsonArray | `List<T>` |            |          | criteria |
|              |            JsonArray | `List<T>` |            | String   | criteria |
|              |            JsonArray |           | JsonArray  |          | criteria |
|              |            JsonArray |           | JsonArray  | String   | criteria |
| upsertJAsync | `Future<JsonObject>` | T         |            |          | key      |
|              | `Future<JsonObject>` |           | JsonObject |          | key      |
|              | `Future<JsonObject>` |           | JsonObject | String   | key      |
|              | `Future<JsonObject>` | T         |            |          | criteria |
|              | `Future<JsonObject>` | T         |            | String   | criteria |
|              | `Future<JsonObject>` |           | JsonObject |          | criteria |
|              | `Future<JsonObject>` |           | JsonObject | String   | criteria |
|              |  `Future<JsonArray>` | `List<T>` |            |          | criteria |
|              |  `Future<JsonArray>` | `List<T>` |            | String   | criteria |
|              |  `Future<JsonArray>` |           | JsonArray  |          | criteria |
|              |  `Future<JsonArray>` |           | JsonArray  | String   | criteria |

#### 2.1.6. 删除

    **删除**接口的骨架代码：

```java
// 「单记录删除」
// --> 返回值：T / Future<T>
// 删除：T -> T
Ux.Jooq.on(XTabularDao.class).delete(t);
    Ux.Jooq.on(XTabularDao.class).deleteAsync(t);
    // 删除：JsonObject -> T
    Ux.Jooq.on(XTabularDao.class).delete(jobject);
    Ux.Jooq.on(XTabularDao.class).deleteAsync(jobject);
    // 删除：JsonObject + pojo -> T
    Ux.Jooq.on(XTabularDao.class).delete(jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).deleteAsync(jobject,pojo);

    // 「单记录删除」Json版
    // --> 返回值：JsonObject / Future<JsonObject>
    // 删除：T -> JsonObject
    Ux.Jooq.on(XTabularDao.class).deleteJ(t);
    Ux.Jooq.on(XTabularDao.class).deleteJAsync(t);
    // 删除：JsonObject -> JsonObject
    Ux.Jooq.on(XTabularDao.class).deleteJ(jobject);
    Ux.Jooq.on(XTabularDao.class).deleteJAsync(jobject);
    // 删除：JsonObject + pojo -> JsonObject
    Ux.Jooq.on(XTabularDao.class).deleteJ(jobject,pojo);
    Ux.Jooq.on(XTabularDao.class).deleteJAsync(jobject,pojo);

    // 「批量删除」
    // --> 返回值：List<T> / Future<List<T>>
    // 直接删除：其中 list 的类型为 java.util.List<T>
    Ux.Jooq.on(XTabularDao.class).delete(list);
    Ux.Jooq.on(XTabularDao.class).deleteAsync(list);
    // 删除：JsonArray -> List<T>
    Ux.Jooq.on(XTabularDao.class).delete(jarray);
    Ux.Jooq.on(XTabularDao.class).deleteAsync(jarray);
    // 删除：JsonArray + pojo -> List<T>
    Ux.Jooq.on(XTabularDao.class).delete(jarray,pojo);
    Ux.Jooq.on(XTabularDao.class).deleteAsync(jarray,pojo);

    // 「批量删除」Json版
    // --> 返回值：JsonArray / Future<JsonArray>
    // 直接删除：其中 list 的类型为 java.util.List<T>
    Ux.Jooq.on(XTabularDao.class).deleteJ(list);
    Ux.Jooq.on(XTabularDao.class).deleteJAsync(list);
    // 删除：JsonArray -> JsonArray
    Ux.Jooq.on(XTabularDao.class).deleteJ(jarray);
    Ux.Jooq.on(XTabularDao.class).deleteJAsync(jarray);
    // 删除：JsonArray + pojo -> JsonArray
    Ux.Jooq.on(XTabularDao.class).deleteJ(jarray,pojo);
    Ux.Jooq.on(XTabularDao.class).deleteJAsync(jarray,pojo);

    // 「批量删除」
    // --> 返回值：Boolean / Future<Boolean>
    // 删除：按 id 删除，key... -> boolean
    Ux.Jooq.on(XTabularDao.class).deleteById(key...);
    Ux.Jooq.on(XTabularDao.class).deleteByIdAsync(key...);
    // 删除：按 id 删除，Collection -> boolean
    Ux.Jooq.on(XTabularDao.class).deleteById(keys);
    Ux.Jooq.on(XTabularDao.class).deleteByIdAsync(keys);
    // 删除：按条件删除，criteria -> boolean
    Ux.Jooq.on(XTabularDao.class).deleteBy(criteria);
    Ux.Jooq.on(XTabularDao.class).deleteByAsync(criteria);
    // 删除：按条件删除，criteria + pojo -> boolean
    Ux.Jooq.on(XTabularDao.class).deleteBy(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).deleteByAsync(criteria,pojo);   
```

    **删除**接口的二维表如下：

|                 |                  返回值 | 领域模型 T    | Json数据     | 带Pojo映射层 | 删除条件     |
| --------------: | -------------------: | --------- | ---------- | -------- | -------- |
|          delete |                    T | T         |            |          |          |
|                 |                    T |           | JsonObject |          |          |
|                 |                    T |           | JsonObject | String   |          |
|                 |            `List<T>` | `List<T>` |            |          |          |
|                 |            `List<T>` |           | JsonArray  |          |          |
|                 |            `List<T>` |           | JsonArray  | String   |          |
|     deleteAsync |          `Future<T>` | T         |            |          |          |
|                 |          `Future<T>` |           | JsonObject |          |          |
|                 |          `Future<T>` |           | JsonObject | String   |          |
|                 |    `Future<List<T>>` | `List<T>` |            |          |          |
|                 |    `Future<List<T>>` |           | JsonArray  |          |          |
|                 |    `Future<List<T>>` |           | JsonArray  | String   |          |
|         deleteJ |           JsonObject | T         |            |          |          |
|                 |           JsonObject |           | JsonObject |          |          |
|                 |           JsonObject |           | JsonObject | String   |          |
|                 |            JsonArray | `List<T>` |            |          |          |
|                 |            JsonArray |           | JsonArray  |          |          |
|                 |            JsonArray |           | JsonArray  | String   |          |
|    deleteJAsync | `Future<JsonObject>` | T         |            |          |          |
|                 | `Future<JsonObject>` |           | JsonObject |          |          |
|                 | `Future<JsonObject>` |           | JsonObject | String   |          |
|                 |  `Future<JsonArray>` | `List<T>` |            |          |          |
|                 |  `Future<JsonArray>` |           | JsonArray  |          |          |
|                 |  `Future<JsonArray>` |           | JsonArray  | String   |          |
|      deleteById |              Boolean |           |            |          | key      |
|                 |              Boolean |           |            |          | keys     |
| deleteByIdAsync |    `Future<Boolean>` |           |            |          | key      |
|                 |    `Future<Boolean>` |           |            |          | keys     |
|        deleteBy |              Boolean |           | JsonObject |          | criteria |
|                 |              Boolean |           | JsonObject | String   | criteria |
| deleteByIdAsync |    `Future<Boolean>` |           | JsonObject |          | criteria |
|                 |    `Future<Boolean>` |           | JsonObject | String   | criteria |

#### 2.1.7. 检查

    **检查**接口的骨架代码：

* 存在检查：存在 = true，不存在 = false。
* 丢失检查：存在 = false，不存在 = true。

```java
// 「检查」存在检查
// --> 返回值：Boolean / Future<Boolean>
// 检查：按 id 检查
Ux.Jooq.on(XTabularDao.class).existById(key);
    Ux.Jooq.on(XTabularDao.class).existByIdAsync(key);
    // 检查：按 条件 检查
    Ux.Jooq.on(XTabularDao.class).exist(criteria);
    Ux.Jooq.on(XTabularDao.class).existAsync(criteria);
    // 检查：按 条件 检查（POJO模式）
    Ux.Jooq.on(XTabularDao.class).exist(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).existAsync(criteria,pojo);

    // 「检查」丢失检查
    // --> 返回值：Boolean / Future<Boolean>
    // 检查：按 id 检查
    Ux.Jooq.on(XTabularDao.class).missById(key);
    Ux.Jooq.on(XTabularDao.class).missByIdAsync(key);
    // 检查：按 条件 检查
    Ux.Jooq.on(XTabularDao.class).miss(criteria);
    Ux.Jooq.on(XTabularDao.class).missAsync(criteria);
    // 检查：按 条件 检查（POJO模式）
    Ux.Jooq.on(XTabularDao.class).miss(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).missAsync(criteria,pojo);      
```

    **检查**接口的二维表如下：

|                |               返回值 | Json数据     | 带Pojo映射层 | 检查条件     |
| -------------: | ----------------: | ---------- | -------- | -------- |
|      existById |           Boolean |            |          | key      |
| existByIdAsync | `Future<Boolean>` |            |          | key      |
|       missById |           Boolean |            |          | key      |
|  missByIdAsync | `Future<Boolean>` |            |          | key      |
|          exist |           Boolean | JsonObject |          | criteria |
|                |           Boolean | JsonObject | String   | criteria |
|           miss |           Boolean | JsonObject |          | criteria |
|                |           Boolean | JsonObject | String   | criteria |
|     existAsync | `Future<Boolean>` | JsonObject |          | criteria |
|                | `Future<Boolean>` | JsonObject | String   | criteria |
|      missAsync | `Future<Boolean>` | JsonObject |          | criteria |
|                | `Future<Boolean>` | JsonObject | String   | criteria |

    检查接口会经常用于一些表单提交过程中的**验证环节**，比如检查用户名是否存在、检查邮箱是否存在、检查当前手机是否是一个新手机等等。

#### 2.1.8. 分组

    **分组**接口的骨架代码：

```java
// 「分组」
// --> 返回值：ConcurrentMap<String,List<T>> / Future<ConcurrentMap<String,List<T>>>
// 分组：不带条件单字段 -> Map
Ux.Jooq.on(XTabularDao.class).group(field);
    Ux.Jooq.on(XTabularDao.class).groupAsync(field);
    // 分组：带条件单字段 -> Map
    Ux.Jooq.on(XTabularDao.class).group(criteria,field);
    Ux.Jooq.on(XTabularDao.class).groupAsync(criteria,field);

    // 「分组」Json版本
    // --> 返回值：ConcurrentMap<String,JsonArray> / Future<ConcurrentMap<String,JsonArray>>
    // 分组：不带条件单字段 -> Map
    Ux.Jooq.on(XTabularDao.class).groupJ(field);
    Ux.Jooq.on(XTabularDao.class).groupJAsync(field);
    // 分组：带条件单字段 -> Map
    Ux.Jooq.on(XTabularDao.class).groupJ(criteria,field);
    Ux.Jooq.on(XTabularDao.class).groupJAsync(criteria,field); 
```

> 分组不支持POJO的直接模式，只能通过`on`方法绑定。

    **分组**接口的二维表如下：

|             |                                       返回值 | 输入数据              | 分组条件  |
| ----------: | ----------------------------------------: | ----------------- | ----- |
|       group |           `ConcurrentMap<String,List<T>>` | String            | field |
|             |           `ConcurrentMap<String,List<T>>` | JsonObject,String | field |
|  groupAsync |   `Future<ConcurrentMap<String,List<T>>>` | String            | field |
|             |   `Future<ConcurrentMap<String,List<T>>>` | JsonObject,String | field |
|      groupJ |         `ConcurrentMap<String,JsonArray>` | String            | field |
|             |         `ConcurrentMap<String,JsonArray>` | JsonObject,String | field |
| groupJAsync | `Future<ConcurrentMap<String,JsonArray>>` | String            | field |
|             | `Future<ConcurrentMap<String,JsonArray>>` | JsonObject,String | field |

### 2.2. 聚集函数

#### 2.2.1. 计数

    **计数**接口的骨架代码：

```java
// 「不分组」
// --> 返回值：Long / Future<Long>
// 计数：全部：-> Long
Ux.Jooq.on(XTabularDao.class).countAll();
    Ux.Jooq.on(XTabularDao.class).countAllAsync();
    // 计数：按条件：criteria -> Long
    Ux.Jooq.on(XTabularDao.class).count(criteria);
    Ux.Jooq.on(XTabularDao.class).countAsync(criteria);
    // 计数：按条件：criteria + pojo -> Long
    Ux.Jooq.on(XTabularDao.class).count(criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).countAsync(criteria,pojo);

    // 「分组」单个组
    // --> 返回值：ConcurrentMap<String,Long> / Future<ConcurrentMap<String,Long>>
    // 计数：不带条件：field -> Map
    Ux.Jooq.on(XTabularDao.class).countBy(field);
    Ux.Jooq.on(XTabularDao.class).countByAsync(field);
    // 计数：带条件：criteria, field -> Map
    Ux.Jooq.on(XTabularDao.class).countBy(criteria,field);
    Ux.Jooq.on(XTabularDao.class).countByAsync(criteria,field);

    // 「分组」多个组
    // --> 返回值：JsonArray / Future<JsonArray>
    // 计数：不带条件：field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).countBy(fields);
    Ux.Jooq.on(XTabularDao.class).countByAsync(fields);
    // 计数：带条件：criteria, field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).countBy(criteria,fields);
    Ux.Jooq.on(XTabularDao.class).countByAsync(criteria,fields);
```

    **计数**接口的二维表如下：

> 此处Map数据结构为`ConcurrentMap<String,Long>`！

|               |                 返回值 | 输入数据                 | 带Pojo映射层 | 分组条件     |
| ------------: | ------------------: | -------------------- | -------- | -------- |
|      countAll |                Long |                      |          |          |
| countAllAsync |      `Future<Long>` |                      |          |          |
|         count |                Long | JsonObject           |          |          |
|               |                Long | JsonObject           | String   |          |
|    countAsync |      `Future<Long>` | JsonObject           |          |          |
|               |      `Future<Long>` | JsonObject           | String   |          |
|       countBy |               `Map` | String               |          | field    |
|               |               `Map` | JsonObject,String    |          | field    |
|               |           JsonArray | String\[]            |          | field\[] |
|               |           JsonArray | JsonObject,String\[] |          | field\[] |
|  countByAsync |       `Future<Map>` | String               |          | field    |
|               |       `Future<Map>` | JsonObject,String    |          | field    |
|               | `Future<JsonArray>` | String\[]            |          | field\[] |
|               | `Future<JsonArray>` | JsonObject,String\[] |          | field\[] |

**响应格式**

    基本的计数操作在底层都是执行类似`COUNT`的SQL语法，对大部分场景而言它已经足够使用了，而上述接口中如果是 **单字段分组** 计数，那么响应结果就是`ConcurrentMap<String,Long>`格式，其中键的值就是当前组名。在Zero的Jooq编程接口中，**多字段分组** 格式采用了`JsonArray` 的数据类型，其中每一组包含了分组字段名和固定字段`count`的值。例如下边语句：

```sql
SELECT COUNT(*)
FROM S_USER
GROUP BY USERNAME, EMAIL
```

假设字段对应数据如：

|       列名 |      字段名 | 含义  |
| -------: | -------: | --- |
| USERNAME | username | 用户名 |
|    EMAIL |    email | 邮件  |

    那么最终返回的多组格式如：

```json
[
    {
        "username": "Lang",
        "email": "lang.yu@hpe.com",
        "count": 22
    },
    {
        "username": "Lang",
        "email": "silentbalanceyh@126.com",
        "count": 21
    },
    ...
]
```

    这种模式下，分组时无法计算具有业务意义的键值，所以使用了数组格式来存储每一组的数据，如上述例子中虽然两组的用户名都是`Lang` ，但由于`email`地址不同，所以会生成两条用户名相同的记录。

    不仅如此，下边所有的聚集函数格式和计数都类似，只是分组过后的字段名称有些差异，简单总结一下聚集函数部分的编程接口：

> 此处`op`表示聚集函数专用名称，如`sum、max、min、avg、count`等。

1. 所有的聚集函数都分为两种编程接口：`op`和`opBy`，前者用于统计，返回结果为单结果集，后者用于分组统计，返回结果为上述分组接口。
2. 只有`op`类型的接口支持POJO模式，查询条件中可以直接引用**映射层**，而`opBy`接口不支持POJO模式访问。

#### 2.2.2. 求和

    **求和**接口的骨架代码：

```java
// 「不分组」
// --> 返回值：BigDecimal / Future<BigDecimal>
// 求和：-> BigDecimal
Ux.Jooq.on(XTabularDao.class).sum(field);
    Ux.Jooq.on(XTabularDao.class).sumAsync(field);
    // 求和：带条件：criteria -> BigDecimal
    Ux.Jooq.on(XTabularDao.class).sum(field,criteria);
    Ux.Jooq.on(XTabularDao.class).sumAsync(field,criteria);
    // 求和：带条件：criteria + pojo -> BigDecimal
    Ux.Jooq.on(XTabularDao.class).sum(field,criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).sumAsync(field,criteria,pojo);

    // 「分组」单个组
    // --> 返回值：ConcurrentMap<String,BigDecimal> / Future<ConcurrentMap<String,BigDecimal>>
    // 求和：不带条件：field -> Map
    Ux.Jooq.on(XTabularDao.class).sumBy(aggr,field);
    Ux.Jooq.on(XTabularDao.class).sumByAsync(aggr,field);
    // 求和：带条件：criteria, field -> Map
    Ux.Jooq.on(XTabularDao.class).sumBy(aggr,criteria,field);
    Ux.Jooq.on(XTabularDao.class).sumByAsync(aggr,criteria,field);

    // 「分组」多个组
    // --> 返回值：JsonArray / Future<JsonArray>
    // 求和：不带条件：field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).sumBy(aggr,fields);
    Ux.Jooq.on(XTabularDao.class).sumByAsync(aggr,fields);
    // 求和：带条件：criteria, field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).sumBy(aggr,criteria,fields);
    Ux.Jooq.on(XTabularDao.class).sumByAsync(aggr,criteria,fields);
```

    **求和**接口的二维表如下：

> 此处Map数据结构为`ConcurrentMap<String,BigDecimal>`，`aggr`则是被聚集字段。

|            |                  返回值 | 输入数据                      | Pojo映射层 | 分组条件     |
| ---------: | -------------------: | ------------------------- | ------- | -------- |
|        sum |           BigDecimal | aggr                      |         | field    |
|            |           BigDecimal | aggr,JsonObject           |         | field    |
|            |           BigDecimal | aggr,JsonObject           | String  | field    |
|   sumAsync | `Future<BigDecimal>` | aggr                      |         | field    |
|            | `Future<BigDecimal>` | aggr,JsonObject           |         | field    |
|            | `Future<BigDecimal>` | aggr,JsonObject           | String  | field    |
|      sumBy |                `Map` | aggr,String               |         | field    |
|            |                `Map` | aggr,JsonObject,String    |         | field    |
|            |          `JsonArray` | aggr,String\[]            |         | field\[] |
|            |          `JsonArray` | aggr,JsonObject,String\[] |         | field\[] |
| sumByAsync |        `Future<Map>` | aggr,String               |         | field    |
|            |        `Future<Map>` | aggr,JsonObject,String    |         | field    |
|            |  `Future<JsonArray>` | aggr,String\[]            |         | field\[] |
|            |  `Future<JsonArray>` | aggr,JsonObject,String\[] |         | field\[] |

#### 2.2.3. 求平均

    **求平均**接口的骨架代码：

```java
// 「不分组」
// --> 返回值：BigDecimal / Future<BigDecimal>
// 求平均：-> BigDecimal
Ux.Jooq.on(XTabularDao.class).avg(field);
    Ux.Jooq.on(XTabularDao.class).avgAsync(field);
    // 求平均：带条件：criteria -> BigDecimal
    Ux.Jooq.on(XTabularDao.class).avg(field,criteria);
    Ux.Jooq.on(XTabularDao.class).avgAsync(field,criteria);
    // 求平均：带条件：criteria + pojo -> BigDecimal
    Ux.Jooq.on(XTabularDao.class).avg(field,criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).avgAsync(field,criteria,pojo);

    // 「分组」单个组
    // --> 返回值：ConcurrentMap<String,BigDecimal> / Future<ConcurrentMap<String,BigDecimal>>
    // 求平均：不带条件：field -> Map
    Ux.Jooq.on(XTabularDao.class).avgBy(aggr,field);
    Ux.Jooq.on(XTabularDao.class).avgByAsync(aggr,field);
    // 求平均：带条件：criteria, field -> Map
    Ux.Jooq.on(XTabularDao.class).avgBy(aggr,criteria,field);
    Ux.Jooq.on(XTabularDao.class).avgByAsync(aggr,criteria,field);

    // 「分组」多个组
    // --> 返回值：JsonArray / Future<JsonArray>
    // 求平均：不带条件：field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).avgBy(aggr,fields);
    Ux.Jooq.on(XTabularDao.class).avgByAsync(aggr,fields);
    // 求平均：带条件：criteria, field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).avgBy(aggr,criteria,fields);
    Ux.Jooq.on(XTabularDao.class).avgByAsync(aggr,criteria,fields);
```

    **求平均**接口的二维表如下：

> 此处Map数据结构为`ConcurrentMap<String,BigDecimal>`，`aggr`则是被聚集字段。

|            |                  返回值 | 输入数据                      | Pojo映射层 | 分组条件     |
| ---------: | -------------------: | ------------------------- | ------- | -------- |
|        avg |           BigDecimal | aggr                      |         | field    |
|            |           BigDecimal | aggr,JsonObject           |         | field    |
|            |           BigDecimal | aggr,JsonObject           | String  | field    |
|   avgAsync | `Future<BigDecimal>` | aggr                      |         | field    |
|            | `Future<BigDecimal>` | aggr,JsonObject           |         | field    |
|            | `Future<BigDecimal>` | aggr,JsonObject           | String  | field    |
|      avgBy |                `Map` | aggr,String               |         | field    |
|            |                `Map` | aggr,JsonObject,String    |         | field    |
|            |          `JsonArray` | aggr,String\[]            |         | field\[] |
|            |          `JsonArray` | aggr,JsonObject,String\[] |         | field\[] |
| avgByAsync |        `Future<Map>` | aggr,String               |         | field    |
|            |        `Future<Map>` | aggr,JsonObject,String    |         | field    |
|            |  `Future<JsonArray>` | aggr,String\[]            |         | field\[] |
|            |  `Future<JsonArray>` | aggr,JsonObject,String\[] |         | field\[] |

#### 2.2.4. 最大值

    **最大值**接口的骨架代码：

```java
// 「不分组」
// --> 返回值：BigDecimal / Future<BigDecimal>
// 最大值：-> BigDecimal
Ux.Jooq.on(XTabularDao.class).max(field);
    Ux.Jooq.on(XTabularDao.class).maxAsync(field);
    // 最大值：带条件：criteria -> BigDecimal
    Ux.Jooq.on(XTabularDao.class).max(field,criteria);
    Ux.Jooq.on(XTabularDao.class).maxAsync(field,criteria);
    // 最大值：带条件：criteria + pojo -> BigDecimal
    Ux.Jooq.on(XTabularDao.class).max(field,criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).maxAsync(field,criteria,pojo);

    // 「分组」单个组
    // --> 返回值：ConcurrentMap<String,BigDecimal> / Future<ConcurrentMap<String,BigDecimal>>
    // 最大值：不带条件：field -> Map
    Ux.Jooq.on(XTabularDao.class).maxBy(aggr,field);
    Ux.Jooq.on(XTabularDao.class).maxByAsync(aggr,field);
    // 最大值：带条件：criteria, field -> Map
    Ux.Jooq.on(XTabularDao.class).maxBy(aggr,criteria,field);
    Ux.Jooq.on(XTabularDao.class).maxByAsync(aggr,criteria,field);

    // 「分组」多个组
    // --> 返回值：JsonArray / Future<JsonArray>
    // 最大值：不带条件：field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).maxBy(aggr,fields);
    Ux.Jooq.on(XTabularDao.class).maxByAsync(aggr,fields);
    // 最大值：带条件：criteria, field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).maxBy(aggr,criteria,fields);
    Ux.Jooq.on(XTabularDao.class).maxByAsync(aggr,criteria,fields);
```

    **最大值**接口的二维表如下：

> 此处Map数据结构为`ConcurrentMap<String,BigDecimal>`，`aggr`则是被聚集字段。

|            |                  返回值 | 输入数据                      | Pojo映射层 | 分组条件     |
| ---------: | -------------------: | ------------------------- | ------- | -------- |
|        max |           BigDecimal | aggr                      |         | field    |
|            |           BigDecimal | aggr,JsonObject           |         | field    |
|            |           BigDecimal | aggr,JsonObject           | String  | field    |
|   maxAsync | `Future<BigDecimal>` | aggr                      |         | field    |
|            | `Future<BigDecimal>` | aggr,JsonObject           |         | field    |
|            | `Future<BigDecimal>` | aggr,JsonObject           | String  | field    |
|      maxBy |                `Map` | aggr,String               |         | field    |
|            |                `Map` | aggr,JsonObject,String    |         | field    |
|            |          `JsonArray` | aggr,String\[]            |         | field\[] |
|            |          `JsonArray` | aggr,JsonObject,String\[] |         | field\[] |
| maxByAsync |        `Future<Map>` | aggr,String               |         | field    |
|            |        `Future<Map>` | aggr,JsonObject,String    |         | field    |
|            |  `Future<JsonArray>` | aggr,String\[]            |         | field\[] |
|            |  `Future<JsonArray>` | aggr,JsonObject,String\[] |         | field\[] |

#### 2.2.5. 最小值

    **最小值**接口的骨架代码：

```java
// 「不分组」
// --> 返回值：BigDecimal / Future<BigDecimal>
// 最小值：-> BigDecimal
Ux.Jooq.on(XTabularDao.class).min(field);
    Ux.Jooq.on(XTabularDao.class).minAsync(field);
    // 最小值：带条件：criteria -> BigDecimal
    Ux.Jooq.on(XTabularDao.class).min(field,criteria);
    Ux.Jooq.on(XTabularDao.class).minAsync(field,criteria);
    // 最小值：带条件：criteria + pojo -> BigDecimal
    Ux.Jooq.on(XTabularDao.class).min(field,criteria,pojo);
    Ux.Jooq.on(XTabularDao.class).minAsync(field,criteria,pojo);

    // 「分组」单个组
    // --> 返回值：ConcurrentMap<String,BigDecimal> / Future<ConcurrentMap<String,BigDecimal>>
    // 最小值：不带条件：field -> Map
    Ux.Jooq.on(XTabularDao.class).minBy(aggr,field);
    Ux.Jooq.on(XTabularDao.class).minByAsync(aggr,field);
    // 最小值：带条件：criteria, field -> Map
    Ux.Jooq.on(XTabularDao.class).minBy(aggr,criteria,field);
    Ux.Jooq.on(XTabularDao.class).minByAsync(aggr,criteria,field);

    // 「分组」多个组
    // --> 返回值：JsonArray / Future<JsonArray>
    // 最小值：不带条件：field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).minBy(aggr,fields);
    Ux.Jooq.on(XTabularDao.class).minByAsync(aggr,fields);
    // 最小值：带条件：criteria, field -> JsonArray
    Ux.Jooq.on(XTabularDao.class).minBy(aggr,criteria,fields);
    Ux.Jooq.on(XTabularDao.class).minByAsync(aggr,criteria,fields);
```

    **最小值**接口的二维表如下：

> 此处Map数据结构为`ConcurrentMap<String,BigDecimal>`，`aggr`则是被聚集字段。

|            |                  返回值 | 输入数据                      | Pojo映射层 | 分组条件     |
| ---------: | -------------------: | ------------------------- | ------- | -------- |
|        min |           BigDecimal | aggr                      |         | field    |
|            |           BigDecimal | aggr,JsonObject           |         | field    |
|            |           BigDecimal | aggr,JsonObject           | String  | field    |
|   minAsync | `Future<BigDecimal>` | aggr                      |         | field    |
|            | `Future<BigDecimal>` | aggr,JsonObject           |         | field    |
|            | `Future<BigDecimal>` | aggr,JsonObject           | String  | field    |
|      minBy |                `Map` | aggr,String               |         | field    |
|            |                `Map` | aggr,JsonObject,String    |         | field    |
|            |          `JsonArray` | aggr,String\[]            |         | field\[] |
|            |          `JsonArray` | aggr,JsonObject,String\[] |         | field\[] |
| minByAsync |        `Future<Map>` | aggr,String               |         | field    |
|            |        `Future<Map>` | aggr,JsonObject,String    |         | field    |
|            |  `Future<JsonArray>` | aggr,String\[]            |         | field\[] |
|            |  `Future<JsonArray>` | aggr,JsonObject,String\[] |         | field\[] |

    Zero中整个数据库部分的基本操作接口到此就告一段落，其实从整个编程接口中可以看到对开发人员还是比较友好的，虽然接口繁多，但整体命名规范方便记忆（大部分接口使用了函数重载模式），使得本身使用起来比较简单——再借用IDE的代码智能感知，使用过程就变得十分流畅了。

## 「叁」查询引擎

    Zero中的**查询引擎**语法使用Json数据格式，它主要分为两种，前文中提到的`criteria`和`query` ，一种是查询格式，一种是全格式，其中`query` 包含了`criteria`格式。

    `criteria`的格式在Zero的Jooq中是本章要讲解的查询引擎基本语法，而`query`（包含了排序、列过滤、分页功能）的完整格式如下：

```json
{
    "cirteria": {
    },
    "pager": {
        "page": 1,
        "size": 10
    },
    "sorter": [
        "name,DESC"
    ],
    "projection": [
    ]
}
```

    四个属性的含义如下：

|        属性名 | 类型         | 备注    |
| ---------: | ---------- | ----- |
|   criteria | JsonObject | 查询条件  |
|      pager | JsonObject | 分页参数  |
|     sorter | JsonArray  | 排序参数  |
| projection | JsonArray  | 列过滤参数 |

### 3.1. 基本语法

    查询语法本身是一个Json树的格式，支持嵌套，整个查询树的每一个节点使用`column=value`的方式，节点主要包含三种类型。

|   节点 | 格式              |
| ---: | --------------- |
| 直接节点 | column = value  |
| 嵌套节点 | column = {}     |
| 连接节点 | "" = true/false |

    上述查询条件中，根据`column`的格式不同，可演变成不同条件，每一个`column=value`的语法格式如下：

```properties
"field,op":"value"
```

    其中：

1. `field`是属性名。
2. `op`是操作符。
3. `value`是当前查询条件的属性值。

#### 3.1.1. op操作列表

    接下来以示例字段为例看看不同的op构成的最终查询条件，假设系统中有如下模型：

|      属性名 | 数据类型 | 列名       |
| -------: | ---- | -------- |
|     name | 字符串  | NAME     |
|    email | 字符串  | EMAIL    |
| password | 字符串  | PASSWORD |
|      age | 数值   | AGE      |
|   active | 逻辑值  | ACTIVE   |

    目前Zero中支持的所有`op`列表如下：

| 操作符 | 格式                    | 含义      | SQL等价                |
| --: | --------------------- | ------- | -------------------- |
|   < | "age,<":20            | 小于某个值   | AGE < 20             |
|  <= | "age,<=":20           | 小于等于某个值 | AGE < 20             |
|   > | "age,>":16            | 大于某个值   | AGE >= 16            |
|  >= | "age,>=":16           | 大于等于某个值 | AGE >= 16            |
|   = | "age,=":12 或 "age":12 | 等于某个值   | AGE = 12             |
|  <> | "name,<>":"LANG"      | 不等于     | NAME <> 'LANG'       |
|  !n | "name,!n":"随意"        | 不为空     | NAME IS NOT NULL     |
|   n | "name,n":"随意"         | 为空      | NAME IS NULL         |
|   t | "active,t":"随意"       | 等于TRUE  | NAME = TRUE          |
|   f | "active,f":"随意"       | 等于FALSE | NAME = FALSE         |
|   i | "name,i":\["A","B"]   | 在某些值内   | NAME IN ('A','B')    |
|  !i | "name,!i":\["C","D"]  | 不在某些值内  | NAME NOT IN('A','B') |
|   s | "name,s":"Lang"       | 以某个值开始  | NAME LIKE 'Lang%'    |
|   e | "name,e":"Lang"       | 以某个值结束  | NAME LIKE '%Lang'    |
|   c | "name,c":"Lang"       | 模糊匹配    | NAME LIKE '%Lang%'   |

#### 3.1.2. 连接节点

    上述`op`是**直接节点**专用的`column`中的操作符语法（等价于SQL中的`AND`和`OR`），**嵌套节点** 则直接在`criteria`基础上递归，本小节讲解一下\*\* 连接节点**的语法。连接节点使用了编程中的一个禁忌**空键\*\*，使用它代替连接节点的目的如：

1. 方便开发人员记忆，只要学会了Zero中的查询引擎语法，就可以直接将Json转换成核心查询树。
2. **空键**不具有任何业务意义，在真实业务场景中不属于任何**业务领域**，这样就和其他占位符不构成任何冲突。

    Zero中的空键只有两个值：`true/false`，它们的含义如下：

| 键  | 值     | 连接符       |
| -- | ----- | --------- |
| "" | true  | AND（或者不写） |
| "" | false | OR（或者不写）  |

### 3.2. 示例

1. 查询引擎中的默认连接符是`AND`。
2. 如果值格式是JsonArray则语法字段转换为`IN/NOT IN`。
3. 如果`op`不书写，则默认为语法`=`。
4. `$?`主要用于特殊条件下的占位符，补齐`column=value`中的两端专用，推荐使用`$`前缀，使得系统不会因为属性名存在而冲突。

#### 3.2.1. 基本语句

```json
{
    "name": "Lang",
    "email,s": "lang.yu"
}
```

    等价的SQL语句如：

```SQL
-- 查询name属性为Lang，email属性以lang.yu开始的记录。
NAME = 'Lang' AND `EMAIL` LIKE 'lang.yu%'
```

#### 3.2.2. 嵌套格式

```json
{
    "name,c": "Huan",
    "$0": {
        ""
        ：
        false,
        "email,e": "hpe.com",
        "email,s": "lang"
    }
}
```

    等价的SQL语句如：

```SQL
-- 查询name属性包含了Huan，并且email以lang开始或以hpe.com结尾的记录
NAME LIKE '%Huan%' AND (EMAIL LIKE '%hpe.com' OR EMAIL LIKE 'lang%')
```

#### 3.2.3. 同字段查询

    由于Json数据格式中键的限制，如果是同一个条件出现多次时，只能使用嵌套模式代替平行模式，如：

```json
{
    "name,c": "lang",
    "": false,
    "$0": {
        "name,c": "yu"
    }
}
```

    等价的SQL语句如：

```sql
-- 查询name中包含了lang或者包含了yu的记录集
NAME LIKE '%lang%' OR (NAME LIKE '%yu')
```

#### 3.2.4. 其他操作符

```json
{
    "age,>"
    ：
    16,
    "active,f": "$0"
}
```

    等价的SQL语句如:

```sql
AGE > 16 AND ACTIVE = FALSE
```

## 「肆」L1缓存

    除了前文提到的Zero中的查询引擎以外，新版的Zero（`0.6.0` ）引入了查询缓存，系统采用Cache-Aside模式提供了数据库级别的缓存，为了使得缓存结构本身不具有代码侵入性，整个缓存的调用会在您启用了AspectJ的时候执行。

    其实以前有人吐槽过Zero框架本身**越来越重** 的问题，关于这一点我只能说我会跟着项目实际需要和产品研发的路线走，它并不是一个单纯得什么都没有的框架，很多需求只是在开发过程中逐渐形成的，而它本身的插件模式使得您可以在使用过程中选择自己所需的组件，针对不同的项目可选择不同的Zero插件来完成。

### 4.1. 整体架构

    Zero中的完整L1缓存架构如下：

![](/files/N8J0wsRWSyGTlfheQWPp)

    Zero中的L1缓存设计主要基于目前Ox平台的CMDB高并发访问场景，它要解决如下几个核心问题：

1. 在多种不同查询交叉查询相同数据时，提高缓存中不同查询条件的命中率和缓存本身的使用效率。
2. 保证启用了缓存过后，数据本身的实时性，由监控平台实时查询Zero系统的数据。
3. 在写数据的过程中完善处理缓存中的变更（双删策略），减轻查询压力、提高查询的准确性，保证缓存数据一致性。

### 4.2. 环境配置

    Zero中的缓存使用了Redis + MySQL的架构，所以在启用缓存时需要在项目中配置Redis的相关信息，Zero中的Redis配置和Jooq配置比较类似。

**vertx.yml**

```yaml
zero:
  lime: redis, cache
```

**vertx-redis.yml**

```yaml
redis:
  host: ox.integration.cn
  port: 6379
  username: "redis"
  password: "redis"
```

**vertx-inject.yml**

```yaml
redis: io.vertx.tp.plugin.redis.RedisInfix
```

**vertx-cache.yml**

```yaml
cache:
  # L1 Cache between Dao and Database
  l1:
    component: "io.vertx.tp.plugin.redis.cache.L1"
    worker: "io.vertx.tp.plugin.redis.cache.L1Worker"
    address: "EVENT://L1-CACHE/FOR/DAO"
    options:
    # Definition for spec situations
    matrix:
```

    `lime` 使用最多的语义是“石灰”，具有侵入、腐蚀、热之隐喻，最早考虑使用这个词语也很随意，它负责将所有其他的文件全部组合到一起，如上述配置中因为配置了`redis, cache` 两个节点，就意味着你需要另外两个文件`vertx-redis.yml、vertx-cache.yml`来配置其扩展部分，此处我们其实配置了两个扩展。

### 4.3. 缓存中的问题

#### 4.3.1. 穿透

    缓存穿透指数据库和缓存中都没有数据，当用户不断发起请求，如发起`id = x` 的请求时，数据库中根本不存在`id = x` 的请求，如果用户本人就是攻击者，那么这种请求会无限放大，这种情况称之为穿透。这种情况可以用如下方法：

1. 接口层增加校验，如鉴权校验、参数校验、基于拦截。
2. 从缓存取不到数据，而且数据库中也没取到，可以将缓存写成`key=null`。
3. 减掉缓存的有效时间，如30秒（太长也会使得缓存无法使用），这样防止用户使用同一个id暴力攻击。

#### 4.3.2. 击穿

    缓存击穿是指缓存中没有数据，但数据库中有数据（一般是缓存时间到期），此时由于并发用户特别多，同时读取缓存时并没读到数据，而此时又同时去数据库中读取数据，造成数据库压力瞬间增大，造成了过大压力。这种情况可以用如下方法：

1. 设置热点数据（经常访问的）永远不过期。
2. 加互斥锁代码逻辑。

#### 4.3.3. 雪崩

    缓存雪崩是指缓存中数据大批量过期，而查询数据量巨大，引起数据库压力过大甚至停止。和击穿不同的是，缓存击穿是并发访问同一条数据，而雪崩是不同数据都过期导致大面积无法查询到。这种情况可以用如下方法：

1. 缓存数据的过期时间随机设置，防止同一时间大量数据过期现象发生。
2. 如果缓存数据是分布式部署，将热点数据均匀分布在不同的缓存数据库中。
3. 设置热点数据永不过期。

## 「伍」小结

    到此，Zero中的Jooq部分教程就到此为止了，本文没有讲解和Jooq相关的太多实例内容，这部分内容可以参考`up-athena` 项目中的测试用例，在文中就不重复了。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lang-yu.gitbook.io/zero/000.index/010.jooq.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
