![MongoDB进阶与实战:微服务整合、性能优化、架构管理](https://wfqqreader-1252317822.image.myqcloud.com/cover/697/38209697/b_38209697.jpg)
3.4 数组、内嵌
支持灵活的数据结构,是MongoDB这种文档数据库的一大优势。在面向对象的编程方式中,对象的成员可以是多种形式的,包括数组、子对象等。但是当我们希望将对象中的数据持久化到传统的关系型数据库中时,却发现没有很好的匹配模式。常见的一些做法如:
● 使用平铺式的多列式结构,如用tag1、tag2、tag3…表示数组中的若干个元素。
● 使用序列化的单列进行收敛,比如将数组或子对象转换为JSON字符串后存储到某个列,在读取时再进行解析。
无论哪一种方式,都是存在一些弊端的。平铺式的结构会导致列的数量膨胀,关系型数据库需要提前设计好Schema,但数组往往是动态的,无法满足快速变化的需求;单列序列化的方式带来了应用上的复杂性,数据库无法理解该列的内部结构,所能提供的操作只有“整存整取”。
MongoDB的文档模型充分理解了数组、内嵌式文档的数据结构,除了可以方便地对数组内的元素、内嵌文档的字段进行操作,还可以对这些“内嵌式”的字段进行索引以满足快速的查询。它们在使用方式上和普通的字段并没有什么大的不同,这是文档型数据库的一种强大的表现力。
值得注意的是,一些关系型数据库如MySQL、PostgreSQL在后来也支持数组和内嵌对象的类型,充分说明了该能力的重要性及普适性。
3.4.1 内嵌文档
让我们再回到前面的例子,一个book文档中可以包含作者的信息,包括作者名称、性别、家乡所在地等,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_1.jpg?sign=1738915717-QK4AuqWzv5d7tDOLMV8laYU0eiMaW981-0-b4a1a7f8977a60a078c5ce32ef1289eb)
一个显著的优点是,当我们查询book文档的信息时,作者的信息也会一并返回。如果只希望返回作者的名称,则可以指定author.name,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_2.jpg?sign=1738915717-d33QhsXk7v49HOP5hJjAGoZBYbsIvIGf-0-feced77e172a175d4cf0c9b360b99d5c)
也可以将author.name作为查询条件,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_3.jpg?sign=1738915717-49BJve8LBYvHt5KyQbekknpOF3vrRQ29-0-6b2970b4d95b7881681b53fa6699b4df)
如果作者信息需要修改,则可以指定其中的某个字段,比如修改作者的家乡所在地,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_4.jpg?sign=1738915717-a7KQyOjSYavPcwVFWKZeEePsN3PlpqGp-0-9fa164c43204b61ecb87f83629d08d95)
3.4.2 数组
除了作者信息,book文档中还包含了若干个标签,这些标签可以用来表示book文档所包含的一些特征,如豆瓣读书中的标签(tag),如图3-3所示。
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_1.jpg?sign=1738915717-WwY4A69MAYNsBvq29zif3VnhhJUF657a-0-c005368ae8d027562f3853ae28ccc1a7)
图3-3 豆瓣读书中的标签
我们用文档结构来表示,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_2.jpg?sign=1738915717-SAWN76xvcQBUryKXUsroKOT9K21fyY2N-0-1dec2527101f12084652c8729898b71d)
1.查询元素
在查询文档时,数组中的标签会被一起返回,如果只想获得最后一个标签元素,则可以用如下命令查询:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_3.jpg?sign=1738915717-pFMyBinDKwv7p9Q2lKAEFNyZKRe1HuvI-0-f2bdbb01875cea7f30087b356edaef11)
这里的$silice是一个查询操作符,用于指定数组的切片方式,与JavaScript中的用法类似。
2.修改元素
如果希望在标签中的这个数组末尾添加一个元素,则可以使用$push操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_4.jpg?sign=1738915717-kggnX2goLDxDWXohqEMnvCwiJD7g3f3C-0-502ff850e125679c922d69ea11026a73)
$push操作符可以配合其他操作符,一起实现不同的数组修改操作,比如和$each操作符配合可以用于添加多个元素,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_5.jpg?sign=1738915717-KEi5e84TJbhiiyURcuHsmvwkFW98RDJz-0-a15d7ae61fc485a5141e2053c6a585ce)
如果加上$slice操作符,那么只会保留经过切片后的元素,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_6.jpg?sign=1738915717-qNPzj3VjVPGniph4QHkP6GA3ZULw02hc-0-1495667b642db68e832a43fd7f4b2dcf)
上述代码除了添加多个标签,最终只会保留最后的3个元素,即经过$slice操作后的结果。
3.根据元素查询
标签的一个重要作用就是用于查询,可以根据标签中的元素进行book文档的查找,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_1.jpg?sign=1738915717-aagkagYbtj67gfY83Yz7CyHsTMZOcNZc-0-977ed4d053b24af95258245b49ee5a19)
上述代码会将所有标签数组中包含“伤感”一词的book文档都查找出来。如果希望查询同时存在多个标签的文档,则可以使用$all操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_2.jpg?sign=1738915717-LoB8saScS3MC7r2848zdaRznR0q80HuS-0-db0979cf74ac5c3ed8382d898f51ab10)
3.4.3 嵌套型的数组
数组元素可以是基本类型,也可以是内嵌的文档结构,我们尝试将标签的概念扩充一下,一个标签由tagKey和tagValue所组成,文档结构如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_3.jpg?sign=1738915717-0D1wSf4TcDDQFkT8Ssq5u7rkRYEHsAy7-0-cd0bce42969d1fc9375d8afd5ca03089)
这种结构非常灵活,一个很适合的场景就是商品的多属性表示,如图3-4所示。
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_4.jpg?sign=1738915717-x21fhLT6bvNAglprQzcL2oRSQEB6a8vc-0-8ae2024731a09274f2d9eb3ab7108e22)
图3-4 电商平台中的商品属性
一个商品可以同时包含多个维度的属性,比如尺码、颜色、风格等,使用文档可以表示为:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_1.jpg?sign=1738915717-Ypjs634PnqK3Gc0sqLG1iRkbK7GDR5Gp-0-cc2e3da37fd5942af83cd72e75e2abde)
以上的设计是一种常见的多值属性的做法,当我们需要根据属性进行检索时,需要用到$elementMatch操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_2.jpg?sign=1738915717-DGHxCNpX3apPSPLEfnOonfeC86ScFk0k-0-dc44c671f73a8fcd594d9132b9269e34)
当然,如果进行组合式的条件检索,则可以使用多个$elemMatch操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_3.jpg?sign=1738915717-ciScPRDCGTmP0IvIhvEb5aHz6fa6NQSW-0-404616b45ef3cd25c3974719f48066ba)
上述代码可以筛选出color=蓝色,并且size=大码的商品信息。