版本修订

版本修订历史
版本 作者 时间 描述

0.21

Lang

2023-11-13

起草最新版本

Chapter I: 核心原理

1. Zero中的查询引擎

那些张口就来肯定一个东西的优缺点、自己没使用过的无脑观点就不聊了,曾经有人给我疯狂吐槽Jooq的性能问题,但从目前我们系统的表现看起来,虽然性能不是最优,但也不至于到他说的那种程度,评估一个东西请用正常模式,别推一样踩一样。

软件工程师最忌讳的一个点就是 信仰主义

1.1. 查询引擎基础

1.1.1. Jooq总结

Zero内置使用了Jooq做底层数据访问,它的优缺点如下:

优点:

  • 类型安全:Jooq支持强类型的查询,减少了错误和歧义的可能性,从而使代码更加可靠和安全。

  • 可读性高:Jooq生成的代码类似于SQL语句,易于理解和维护,同时还提供了高级功能,如内部联接和复杂查询。

  • 易于使用:Jooq提供了简单易懂的API,使开发人员可以快速编写和执行数据库查询。

  • 与多种数据库兼容:Jooq支持多种数据库,包括MySQL、PostgreSQL、Oracle等,这使得它适用于不同的项目和团队。

  • 可扩展性强:Jooq允许开发人员添加自定义数据类型和操作符,以支持特定的业务需求和数据库类型。

  • 支持多种数据库特性:Jooq支持许多数据库特性,如分页、事务、聚合、分组等,这使得开发人员可以轻松地使用这些功能而不必担心不同数据库之间的兼容性。

缺点:

  • 学习曲线较陡峭:Jooq的API相对复杂,需要一定的学习和实践才能熟练掌握。

  • 不支持所有数据库特性:虽然Jooq支持许多数据库特性,但并不支持所有功能。因此,在使用Jooq之前,需要确认所需的功能是否得到支持。

  • 生成的代码量较大:Jooq生成的代码相对较多,可能会导致代码库变得复杂和冗长,从而增加了维护的难度。

  • 比较笨重:相对于其他数据库访问库,Jooq的代码库比较庞大,可能会对应用程序的性能产生一定的影响。

  • 文档不够完善:Jooq的文档不够详尽和全面,可能需要花费更多时间和精力来学习和理解其使用方法和技巧。

Jooq的性能影响因素主要包含如下几点:

  • 复杂的查询语句:Jooq支持复杂的查询语句,但这些语句可能会导致性能下降,尤其是当查询的数据量非常大时。在这种情况下,可以考虑使用更简单和直接的查询语句,以提高性能。

  • 非优化的数据库表结构:如果数据库表结构设计不良,会对Jooq的性能产生影响。例如,如果表中有太多的冗余字段、重复的数据或过度归一化的设计,都会增加查询和更新的成本。

  • 大量数据的操作:当处理大量数据时,Jooq的性能可能会受到影响。这时可以考虑使用批量插入或更新操作,以减少数据库交互的次数,从而提高性能。

  • 过多的SQL语句:如果使用Jooq时生成了过多的SQL语句,会对性能产生负面影响。这时可以考虑通过使用缓存或重构查询语句来减少SQL语句的生成次数,以提高性能。

  • 应用程序本身的性能问题:Jooq本身的性能问题可能并不是最终的瓶颈。在一些情况下,应用程序本身的性能问题,如磁盘I/O或网络延迟等,可能会对Jooq的性能产生更大的影响。

1.1.2. Qr引擎

Zero中为SQL语法提供了 Qr(Query Result) 的查询引擎语法,语法本身是JSON格式,在前后端可以通过代码直接提取数据或构造不同的查询语法,查询引擎的好处:

  1. Zero为查询引擎提供了前后端同步的API,不论在前端还是后端都提供了查询引擎语法树,您可以很方便地将查询条件进行转换、计算、合并等各种操作。

  2. Json语法格式简单,开发人员很容易理解查询引擎语法,并根据业务需求提炼复杂的查询条件。

  3. 前端组件中有很多和查询相关的内容,统计、视图定制、排序、列表,所有和数据特性相关的运算可直接使用查询引擎完成,不需要针对特定场景开发对应接口(项目赶进度比较重要)。

  4. Zero底层对语法树做过三种不同算法层面的优化,只需识别模型即可处理各种复杂的优化查询,从目前生产环境表现看起来还算 稳定靠谱

  5. Zero针对底层查询执行了跨表类处理(JOIN),跨表所有数据库级别的 DML 操作都使用同样的API实现,如此就解决了用户在复杂业务跨表操作的问题,包括多表同读和多表同写。

查询引擎语法同样适用于动态建模项目(zero-atom)中,只要在Zero框架中和数据库打交道,不论注释文档中书写提到了查询引擎(QR)部分,都可直接使用查询引擎语法搞定。

除此之外,spring-up 项目中还可以将查询引擎语法用于Spring框架,只是需要依赖 QueryDSL 项目。

1.2. 语法说明

参考: 1.10.珷玞:Jooq

1.2.1. Json结构

Zero中的查询引擎的JSON语法主要包含 4个关键属性

关键属性 格式 含义

criteria

JsonObject

查询条件语法树,Qr 条件语法结构。

projection

JsonArray

数据列过滤,包括列过滤和列排序两个功能。

sorter

JsonArray

数据排序,可支持第一排序列和第N排序列,每个列可支持升序和降序二选一。

pager

JsonObject

数据分页功能,一般用于前端界面呈现。

本章节主要关注参数 criteria,它遵循 Zero 内部定义的JSON格式的查询语法。

1.2.1.1. 语法初见

由于JSON格式是一个树型结构,所以按照树的节点,Zero 中定义的节点类型包含如下:

  • 直接节点 :最简单的键值结构:column=value,二元操作符结构中 value 可以随意。

  • 嵌套节点 :当前节点之下不是一个单纯的:column=value 结构,相反是 子语法树,这种模式下只要保证键不重复即可,通常会使用类似 $x 的格式来书写键值。

  • 连接节点 :一般是 "" = true/false 结构,用于表示本层语法的连接符(注意只局限于本层)。

为了使用示例演示,先提供一个表结构:

属性名 类型 列名

name

字符串

NAME

email

字符串

EMAIL

password

字符串

PASSWORD

age

数值

AGE

active

逻辑值

ACTIVE

在看不同节点之前,先看一个Json示例:

{
    "name,c": "Lang",
    "": true,
    "$0": {
        "": false,
        "email,s": "silent",
        "email,e": "zhaowing.com"
    }
}

上边的语法最终会展开成如下SQL语句:

NAME LIKE '%Lang%' AND (EMAIL LIKE 'silent%' OR EMAIL LIKE '%zhaowing.com')
1.2.1.2. 语法定义

根据前一个章节示例,相信您对Zero Qr查询语法有了一个直观的理解。其实Qr定义中每一个单独条件的实际语法如下:

"<field>,<op>": "<value>"
  • <field> :此部分通常是 属性名,但是为了兼容部分底层开发程序员,该属性值也可以是 数据库列名,如 name,c 也可以写成 NAME,c

  • <op>:这部分内容是 操作符,操作符可以参考下边操作符表,用于制定查询条件专用。

  • <value>:此处存放的就是对应的值。

操作符说明表
操作符 格式 含义 等价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

ACTIVE = TRUE

f

"active,f": "任意"

* 等于FALSE

ACTIVE = 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%'

如此,您就可以组合各种复杂的SQL语句来完成 查询需求,从目前我们在真实项目中的应用看起来,足够解决大量问题了,所有在应用系统级别遇到的查询问题都可以在上述操作符中得到相关解答。

连接节点 是Zero中一个比较巧妙的设计——使用了编程过程中的的一个禁忌,就是 "" 作为键值,使用它的目的:

  1. 方便开发人员记忆,只要学会之后,可以很快将想要的查询条件转换成核心语法。

  2. 空键 不具有任何业务意义,真实场景中不会和字段产生冲突,起到名副其实占位符的作用。

连接符
连接符

""

true

AND

""

false

OR(或者不写)

1.2.1.3. 语法技巧

Zero中存在一部分默认语法转换规则,此处进一步说明,为开发人员解惑,也提供部分技巧。

  1. 查询引擎中的默认连接符是 OR,也就是说如果不存在 "" 键,那么本层条件连接符使用 OR

  2. 如果值格式为 JsonArray,且左侧不带 <op> 节点,则语法自动转换成 IN 语法。

  3. 通常字段不书写 <op> 节点时,默认语法为 = 符号,但优先级弱于第二法则。

  4. * 号的四个二元操作符推荐在值部分(任意)书写部分注释,可提供给别人查阅。

  5. 子查询树 格式通常是 column = JSON,此时的 column 为了不和其他字段起冲突,建议使用 $ 前缀。

1.2.1.4. 示例
/* Qr语法:
{
    "name": "Lang",
    "email,s": "lang.yu"
}
*/
-- 查询名称等于Lang,或者email以 lang.yu 开始的记录
NAME = 'LANG' AND EMAIL LIKE 'lang.yu%'

/* Qr语法
{
    "name,c": "lang",
    "": true,
    "$0": {
        "email,c": "yu"
    }
}
*/
-- 查询名称中包含了lang,并且邮箱中还包含了 yu 的记录
NAME LIKE '%lang%' AND EMAIL LIKE '%yu%'

1.2.2. 前端Qr组件

1.2.2.1. ExListComplex简介

本章节主要讲解前端一个消费 Qr 语法很集中的组件 ExListComplex,通过对组件关于 Qr 部分的讲解,让您对 Zero UI的前端解析有一个更加清楚的认识,ExListComplex 是前端最高频的列表组件,使用了大量的查询语法,它支持的功能如下:

  • 普通增删查改

  • 批量增删,导入和导出

  • 分页、排序、跳页、页面尺寸修改

  • 列过滤、检索

  • 保存个人视图

    • 直接在列定义中执行列筛选、列排序功能保存个人视图

    • 设置当前视图的查询条件,保存个人视图

    • 视图管理执行完善的增删改以及选择后查询

  • 高级搜索,提供子表单执行高级搜索功能

ExListComplex 组件的查询入口主要有三个:

  1. 列过滤筛选,默认使用条件堆积,列和列之间使用 AND 操作符

  2. 输入框筛选,一般是核心字段检索功能,字段和字段之间使用 OR 操作符

  3. 高级搜索,一般是完整的查询表单,抽屉从右侧拉出,连接符可自己在表单中设置。

本章不是讲解 ExListComplex 组件,所以只抽取了和查询引擎强相关的内容进行讲解,其他话题不在此章节详细讲解(如:加载状态流、表单配置、扩展插件、导入导出等)。

ExListComplex 组件的核心查询变量如下:

常用查询属性表
属性名 来源 含义

config

props

从外层传入的列表标准配置,内部包含 query 节点。

$query

props

从外层传入的查询条件,当前组件作为被控组件时需使用此查询条件作为查询的基础条件。

$myDefault

props

当前列表运行时我的默认视图。

$myView

state

当前列表运行时运行的视图信息,我的视图。

$condition

state

列过滤专用条件属性。

$terms

state

列过滤元数据定义(支持查询的过滤列信息)。

$keyword

state

使用搜索时的 高亮 关键字。

$qr

state

高级查询表单专用的查询初始值。

__qr

ajax

使用查询引擎接口类似 POST /api/xxx/search 时,远程返回数据中如果包含 qr 则证明当前请求存在视图,而 qr 记录的是当前视图中 criteria 字段中存储的数据。

$queryDefault

state

当前列表的初始化查询核心参数,即列表查询参数的默认值。

$queryView

state

当前列表的初始化查询条件之后根据视图 __qr 计算的新的查询核心参数,即列表查询参数带视图的默认值。

$query

state

当前列表的运行时参数,每次请求过程中的参数以此参数为准。

$columns

state

当前列表访问模型的全列信息。

$columnsMy

state

当前列表访问模型的我的列信息(存在视图时来自视图)。

前端的 $query 通常意义上表示的是完整的查询引擎参数,类似如下结构:

{
    "criteria": {},
    "pager":{
        "page": 1,
        "size": 10
    },
    "sorter": [
        "xxxx=ASC"
    ],
    "projection": [
    ]
}

不仅如此,由于Zero前端存在 语法解析器,所以上述结构也可以使用简易语法:

{
    "criteria": {},
    "pager": "1,10",
    "sorter": "xxx=ASC",
    "projection: []
}
1.2.2.2. 查询数据流

接下来参考下边的数据流图来理解上述所有参数以及在 ExListComplex 中的协同工作原理。

zqr arch

2. 核心缓存

2.1. 安全缓存池

2.1.1. 账号登录缓存

账号登录缓存是在登录之后存储到系统会话中的缓存,它通过下边代码写入到缓存中:

    ScUser.login(data)   // 登录缓存初始化(静态调用)

其中此处的 data 数据结构如:

{
    "user": "账号ID",
    "role": [
        {
            "roleId": "角色ID",
            "priority": "数值,角色优先级"
        }
    ],
    "group": [
        {
            "groupId": "用户组ID",
            "priority": "数值,用户组优先级"
        }
    ],
    "habitus": "128位会话随机字符串,每次登录时生成,等价于会话ID",
    "session": "Vert.x生成的会话 Session 的 id"
}

账号登录缓存的数据结构如下图:

0

上述 data 数据输出会帮着用户初始化一个完整用户登录缓存信息,作以下几点说明:

  • 用户登录缓存的主缓存池使用了SharedMap中的固定池名称 ZERO-CACHE-HABITUS,但实际访问(引用)它的对象为 ScUser 对象。

  • 根据会话键 habitus,每个用户登录之后会分配一个唯一的 habitus 值,即真正开发时:您可以使用用户ID获取 ScUser 对象,然后直接操作 ScUser 对象实现用户登录数据的提取。

  • 存储数据本身不包含 habitus,参考左侧 remove

  • 最核心的两个对象为 profileview

    • profile 就是消费端提到的 64 种Profile,根据用户本身数据信息计算出来的。

    • view 则是一个动态结构,会根据用户发送请求计算 viewKey,最终根据这个值计算用户的视图信息,视图信息是序列化过后的 DataBound 对象。

2.1.2. 401认证缓存

认证缓存在系统中由核心框架提供 执行模式,开发人员则只需要提供存储数据结构即可,有了认证缓存后,用户就不需要每次发送请求都让系统运行 标准认证流程,只有当如下情况发生时才会要求用户重新认证:

  • 用户令牌过期,需要使用新令牌对用户重新执行认证。

  • 系统检测到用户出现了非安全性操作。

  • 用户账号出现了异常,导致请求过程中安全身份信息无法通过会话检查。

401认证缓存 使用了SharedMap中固定的池名称 ZERO-CACHE-401,它的数据结构如下: