From 90830cc166edeaeef9d78de2c434537876462d46 Mon Sep 17 00:00:00 2001 From: wanganxp Date: Sun, 27 Dec 2020 05:03:31 +0800 Subject: [PATCH] Update clientdb.md --- docs/uniCloud/clientdb.md | 350 ++++++++++++++++++++++++-------------- 1 file changed, 225 insertions(+), 125 deletions(-) diff --git a/docs/uniCloud/clientdb.md b/docs/uniCloud/clientdb.md index d635d4cb4a..e18f497204 100644 --- a/docs/uniCloud/clientdb.md +++ b/docs/uniCloud/clientdb.md @@ -8,7 +8,7 @@ 1个应用开发的一半的工作量,就此直接省去。 -当然使用`clientDB`需要扭转传统后台开发观念,不再编写云函数,直接在前端操作数据库。但是为了数据安全,需要在数据库上配置`DB Schema`。 +当然使用`clientDB`需要扭转传统后台开发观念,不再编写服务端代码,直接在前端操作数据库。但是为了数据安全,需要在数据库上配置`DB Schema`。 在`DB Schema`中,配置数据操作的权限和字段值域校验规则,阻止前端不恰当的数据读写。详见:[DB Schema](https://uniapp.dcloud.net.cn/uniCloud/schema) @@ -23,14 +23,14 @@ ## clientDB图解 ![](https://static-eefb4127-9f58-4963-a29b-42856d4205ee.bspapp.com/clientdb.jpg) -`clientDB`的前端部分包括js API和``组件两部分。 +`clientDB`的前端,有两种用法,可以用js API操作云数据库,也可以使用``组件。 js API可以执行所有数据库操作。``组件是js API的再封装,进一步简化查询等常用数据库操作的代码量。 - 在HBuilderX 3.0+,``组件已经内置,可以直接使用。文档另见:[组件](/uniCloud/unicloud-db) - 在HBuilderX 3.0以前的版本,使用该组件需要在插件市场单独引用`插件`,另见:[https://ext.dcloud.net.cn/plugin?id=3256](https://ext.dcloud.net.cn/plugin?id=3256) -以下文章重点介绍`clientDB`的js API。组件的使用语法类似,但请单独查阅组件的文档。 +以下文章重点介绍`clientDB`的js API。至于组件的用法,另见[文档](/uniCloud/unicloud-db)。 ## clientDB前端API@jssdk @@ -108,7 +108,7 @@ db.collection('list') ```js const db = uniCloud.database() let res = await db.collection('table').where({ - user_id: db.env.uid // 查询当前用户的数据 + user_id: db.env.uid // 查询当前用户的数据。虽然代码编写在客户端,但环境变量会在云端运算 }).get() ``` @@ -116,7 +116,7 @@ let res = await db.collection('table').where({ `jql`,全称javascript query language,是一种js方式操作数据库的语法规范。 -`jql`大幅降低了js工程师操作数据库的难度、大幅缩短开发代码量。并利用json数据库的嵌套特点,极大的简化了联表查询的复杂度。 +`jql`大幅降低了js工程师操作数据库的难度、大幅缩短开发代码量。并利用json数据库的嵌套特点,极大的简化了联表查询和树查询的复杂度。 #### jql的诞生背景 @@ -139,7 +139,7 @@ let res = await db.collection('table').where({ sql写法,对js工程师而言有学习成本,而且无法处理非关系型的MongoDB数据库,以及sql的联表查询inner join、left join也并不易于学习。 -而nosql的写法,实在过于复杂。 +而nosql的写法,实在过于复杂。比如如下3个例子: 1. 运算符需要转码,`>`需要使用`gt`方法、`==`需要使用`eq`方法 @@ -645,173 +645,273 @@ const db = uniCloud.database() } ``` -### 查询树形结构所有子节点@gettree +### 查询树形数据@gettree -自3.0.3起,clientDB支持在get方法内传入getTree参数对包含父子关系的表查询返回树状结构数据。 +HBuilderX 3.0.3+起,clientDB支持在get方法内传入getTree参数查询树状结构数据。(unicloud-db组件还未支持,目前只能通过js方式使用) -```js -// get方法示例 -get({ - getTree: { - limitLevel: 10, // 最大查询层级(不包含当前层级),可以省略默认10级,最大15最小1 - startWith: 'parent_code==null' // 第一层级条件,此初始条件可以省略,不传startWith时默认从最顶级开始查询 - } -}) +树形数据,在数据库里一般不会按照tree的层次来存储,因为按tree结构通过json对象的方式存储不同层级的数据,不利于对tree上的某个节点单独做增删改查。 + +一般存储树形数据,tree上的每个节点都是一条单独的数据表记录,然后通过类似parent_id来表达父子关系。 + +如部门的数据表,里面有2条数据,一条数据记录是“总部”,`parent_id`为空;另一条数据记录“一级部门A”,`parent_id`为总部的`_id` +```json +{ + "_id": "5fe77207974b6900018c6c9c", + "name": "总部", + "parent_id": "", +} +``` +```json +{ + "_id": "5fe77232974b6900018c6cb1", + "name": "一级部门A", + "parent_id": "5fe77207974b6900018c6c9c", +} ``` -以下面的表结构及数据为例 +虽然存储格式是分条记录的,但查询反馈到前端的数据仍然需要是树形的。这种转换在过去比较复杂。 -```js -// test-tree 表结构 +clientDB提供了一种简单、优雅的方案,在DB Schema里配置parentKey来表达父子关系,然后查询时声明使用Tree查询,就可以直接查出树形数据。 + +department部门表的schema中,将字段`parent_id`的"parentKey"设为"_id",即指定了数据之间的父子关系,如下: + +```json { "bsonType": "object", - "required": [], + "required": ["name"], "properties": { - "parent_code": { - "bsonType": "string", - "description": "父级code", - "parentKey": "code", // 指定父子关系为:如果数据库记录A的code和数据库记录B的parent_code相等,则A是B的父级。一个表内只能有一个父子关系 - }, - "code": { - "bsonType": "string", - "description": "当前code - }, + "_id": { + "description": "ID,系统自动生成" + }, "name": { - "bsonType": "string", - "description": "名称" - } + "bsonType": "string", + "description": "名称" + }, + "parent_id": { + "bsonType": "string", + "description": "父id", + "parentKey": "_id", // 指定父子关系为:如果数据库记录A的_id和数据库记录B的parent_id相等,则A是B的父级。 + } } } ``` -假设上述表有以下数据 +parentKey字段将数据表不同记录的父子关系描述了出来。查询就可以直接写了。 -```js -{ - name: 'p1', - code: '1' -} -{ - parent_code: '1', - name: 'c11', - code: '11' -} -{ - parent_code: '1', - name: 'c12', - code: '12' -} -{ - name: 'p2', - code: '2' -} -{ - parent_code: '2', - name: 'c21', - code: '21' -} -{ - parent_code: '2', - name: 'c22', - code: '22' -} -``` +注意一个表内只能有一个父子关系,即一个表的schema里只能配置一份parentKey。 + +schema里描述好后,查询就变的特别简单。 + +查询树形数据,分为 查询所有子节点 和 查询父级路径 这2种需求。 + +#### 查询所有子节点 -以上面的表结构为例以下两种写法是等价的 +指定符合条件的记录,然后查询它的所有子节点,并且可以指定层级,返回的结果是以符合条件的记录为一级节点的所有子节点数据,并以树形方式嵌套呈现。 +只需要在clientDB的get方法中增加`getTree`参数,如下 ```js +// get方法示例 get({ getTree: { - limitLevel: 10, - startWith: 'parent_code==null' + limitLevel: 10, // 最大查询层级(不包含当前层级),可以省略默认10级,最大15,最小1 + startWith: "parent_code==''" // 第一层级条件,此初始条件可以省略,不传startWith时默认从最顶级开始查询 } }) +``` -get({ - getTree: true -}) +完整的代码如下: +```js +db.collection("department").get({ + getTree: {} + }) + .then((res) => { + const resdata = res.result.data + console.log("resdata", resdata); + }).catch((err) => { + uni.showModal({ + content: err.message || '请求服务失败', + showCancel: false + }) + }).finally(() => { + + }) ``` -如果要查询code为'1'的数据及其在树形结构所有的子节点,可以使用如下写法 +查询的结果如下: +```json +"data": [{ + "_id": "5fe77207974b6900018c6c9c", + "name": "总部", + "parent_id": "", + "children": [{ + "_id": "5fe77232974b6900018c6cb1", + "name": "一级部门A", + "parent_id": "5fe77207974b6900018c6c9c", + "children": [] + }] +}] +``` -```js -// 完整写法示例 -const res = await db.collection('opendb-china-cities') - .field('name as text,code as value') - .get({ - getTree: { - startWith: 'code == "1"' - } - }) - -// 查询结果 -{ - data: [{ - text:'p1', - value:'1', - children:[{ - text:'c11', - value:'11', - children: [] - },{ - text:'c12', - value:'12', - children: [] - }] - }] -} +可以看出,每个子节点,被嵌套在父节点的"children"下,这个"children"是一个固定的格式。 + +如果不指定getTree的参数,会把department表的所有数据都查出来,从总部开始到10级部门,以树形结构提供给客户端。 + +如果有多个总部,即多行记录的`parent_id`为空,则多个总部会分别作为一级节点,把它们下面的所有children一级一级拉出来。如下: + +```json +"data": [ + { + "_id": "5fe77207974b6900018c6c9c", + "name": "总部", + "parent_id": "", + "children": [{ + "_id": "5fe77232974b6900018c6cb1", + "name": "一级部门A", + "parent_id": "5fe77207974b6900018c6c9c", + "children": [] + }] + }, + { + "_id": "5fe778a10431ca0001c1e2f8", + "name": "总部2", + "parent_id": "", + "children": [{ + "_id": "5fe778e064635100013efbc2", + "name": "总部2的一级部门B", + "parent_id": "5fe778a10431ca0001c1e2f8", + "children": [] + }] + } +] ``` + +如果觉得返回的`parent_id`字段多余,也可以指定`.field("_id,name")`,过滤掉该字段。 + +**getTree的参数limitLevel的说明** + +limitLevel表示查询返回的树的最大层级。超过设定层级的节点不会返回。 + +- limitLevel的默认值为10。 +- limitLevel的合法值域为1-15之间(包含1、15)。如果数据实际层级超过15层,请分层懒加载查询。 +- limitLevel为1,表示向下查一级子节点。假如数据库中有2级、3级部门,如果设limitLevel为1,且查询的是“总部”,那么返回数据包含“总部”和其下的一级部门。 + +**getTree的参数startWith的说明** + +如果只需要查“总部”的子部门,不需要“总部2”,可以在where条件里指定(`.where("name=='总部'")`),也可以在startWith里指定(`getTree: {"startWith":"name=='总部'"}`)。 + +那么这2种指定方式的区别是什么呢?有的场景下,where条件可能用于描述其他查询条件,和以什么数据为根的描述冲突。此时就需要单独指定starWith。 + +startWith不填时,默认的条件是 `'parent_id==null||parent_id==""'`,即schema配置parentKey的字段为null(即不存在)或值为空字符串时,这样的节点被默认视为根节点。 + +getTree一定会返回一个嵌套的树形数据,如果指定的查询条件没有子数据,查询会报错。 + +**大数据量的树形数据查询** + +如果tree的数据量较大,则不建议一次性把所有的树形数据返回给客户端。建议分层查询,即懒加载。 + +比如地区选择的场景,全国的省市区数据量很大,一次性查询所有数据返回给客户端非常耗时和耗流量。可以先查省,然后根据选择的省再查市,以此类推。 + **注意** -- 目前不支持使用getTree的同时使用其他联表查询语法 +- 暂不支持使用getTree的同时使用联表查询 - 如果使用了where条件会对所有查询的节点生效 -- 不可一次查询量特别大的数据,比如一次性加载所有的省市区。推荐在菜单、部门选择等小数据量的场景下使用 -### 查询树形结构所有父节点@gettreepath +#### 查询树形结构父节点路径@gettreepath + +getTree是查询子节点,而getTreePath,则是查询父节点。 -自2020-12-26起,clientDB支持在get方法内传入getTreePath参数对包含父子关系的表查询返回树状结构数据某节点路径。 +get方法内传入getTreePath参数对包含父子关系的表查询返回树状结构数据某节点路径。 ```js // get方法示例 get({ getTreePath: { - limitLevel: 10, // 最大查询层级(不包含当前层级),可以省略默认10级,最大15最小1 - startWith: 'code=="22"' // 第一层级条件,此初始条件不可以省略 + limitLevel: 10, // 最大查询层级(不包含当前层级),可以省略默认10级,最大15,最小1 + startWith: 'name=="一级部门A"' // 末级节点的条件,此初始条件不可以省略 } }) ``` -仍以上面getTree中的表结构和数据为例 +查询返回的结果为,从“一级部门A”起向上找10级,找到最终节点后,以该节点为根,向下嵌套children,一直到达“一级部门A”。 + +返回结果只包括“一级部门A”的直系父,其父节点的兄弟节点不会返回。所以每一层数据均只有一个节点。 -如果要查询code为'22'的数据的在树形结构中的路径可以使用下面的写法 +仍以上面department的表结构和数据为例 ```js -// 完整写法示例 -const res = await db.collection('opendb-china-cities') - .field('name as text,code as value') - .get({ - getTreePath: { - startWith: 'code=="22"' - } - }) - -// 查询返回结果 +db.collection("department").get({ + getTreePath: { + "startWith": "_id=='5fe77232974b6900018c6cb1'" + } + }) + .then((res) => { + const treepath = res.result.data + console.log("treepath", treepath); + }).catch((err) => { + uni.showModal({ + content: err.message || '请求服务失败', + showCancel: false + }) + }).finally(() => { + uni.hideLoading() + // console.log("finally") + }) +``` + +查询返回结果 + +从根节点“总部”开始,返回到“一级部门A”。“总部2”等节点不会返回。 + +```json +{ + "data": [{ + "_id": "5fe77207974b6900018c6c9c", + "name": "总部", + "parent_id": "", + "children": [{ + "_id": "5fe77232974b6900018c6cb1", + "name": "一级部门A", + "parent_id": "5fe77207974b6900018c6c9c" + }] + }] +} +``` + +如果startWith指定的节点没有父节点,则返回自身。 + +如果startWith匹配的节点不止一个,则以数组的方式,返回每个节点的treepath。 + +例如“总部”和“总部2”下面都有一个部门的名称叫“销售部”,且` "startWith": "name=='销售部'"`,则会返回“总部”和“总部2”两条treepath,如下 + +```json { - data: [{ - text:'p2', - value:'2', - children:[{ - text:'c22', - value:'22', - }] - }] + "data": [{ + "_id": "5fe77207974b6900018c6c9c", + "name": "总部", + "parent_id": "", + "children": [{ + "_id": "5fe77232974b6900018c6cb1", + "name": "销售部", + "parent_id": "5fe77207974b6900018c6c9c" + }] + }, { + "_id": "5fe778a10431ca0001c1e2f8", + "name": "总部2", + "parent_id": "", + "children": [{ + "_id": "5fe79fea23976b0001508a46", + "name": "销售部", + "parent_id": "5fe778a10431ca0001c1e2f8" + }] + }] } ``` + **注意** -- 目前不支持使用getTreePath的同时使用其他联表查询语法 +- 暂不支持使用getTreePath的同时使用其他联表查询语法 - 如果使用了where条件会对所有查询的节点生效 ### 新增数据记录add @@ -1158,7 +1258,7 @@ const res = await db.collection('table1').where({ ### 其他数据库操作 -clientDB支持使用聚合操作读取数据,关于聚合操作请参考[聚合操作](uniCloud/cf-database.md?id=aggregate) +clientDB API支持使用聚合操作读取数据,关于聚合操作请参考[聚合操作](uniCloud/cf-database.md?id=aggregate) 例:取status等于1的随机20条数据 @@ -1511,7 +1611,7 @@ module.exports = { command: { // getMethod('where') 获取所有的where方法,返回结果为[{$method:'where',$param: [{a:1}]}] getMethod, - // getMethod({name:'where',index: 0}) 获取第1个where方法的参数,结果为数组形式,例:[{a:1}] + // getParam({name:'where',index: 0}) 获取第1个where方法的参数,结果为数组形式,例:[{a:1}] getParam, // setParam({name:'where',index: 0, param: [{a:1}]}) 设置第1个where方法的参数,调用之后where方法实际形式为:where({a:1}) setParam -- GitLab