![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=1738888230-wh0Ltap5etlFeXUb9ob2C6e1T4wVrgYs-0-3ee9128d466f1da2e54f0aeafd282e45)
一个显著的优点是,当我们查询book文档的信息时,作者的信息也会一并返回。如果只希望返回作者的名称,则可以指定author.name,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_2.jpg?sign=1738888230-TAFOwZspAGdSNIDSkHxyqwnHAOfTeUS3-0-e420adb313b8a817bde866304a9cf0fd)
也可以将author.name作为查询条件,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_3.jpg?sign=1738888230-Eu7Y5lgDQvaD8Xty5owAJEo9eUte37r2-0-0fb8452d70d15ae658e0513aa498b2d3)
如果作者信息需要修改,则可以指定其中的某个字段,比如修改作者的家乡所在地,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_4.jpg?sign=1738888230-BnXH7SroSJDK1Ekube5h82hgoHXEuUqc-0-f82c0a0e7f8acab0798b3e83e8e0be1a)
3.4.2 数组
除了作者信息,book文档中还包含了若干个标签,这些标签可以用来表示book文档所包含的一些特征,如豆瓣读书中的标签(tag),如图3-3所示。
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_1.jpg?sign=1738888230-eDUFsLDems7Zb5dE60cYHwtzNrS4UFry-0-b0ec97ceeebefa159853713595d5690b)
图3-3 豆瓣读书中的标签
我们用文档结构来表示,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_2.jpg?sign=1738888230-vc9AsX4drlk1b4VNhAujvpi2WqEaqXkC-0-c782119ea32cc2af86eab0dd18d6d4ba)
1.查询元素
在查询文档时,数组中的标签会被一起返回,如果只想获得最后一个标签元素,则可以用如下命令查询:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_3.jpg?sign=1738888230-CvTUUp5LdTV8BSp1ABmcHhuvHDhOdGdS-0-d57344f82c070bbdc155d0c0e9bdc452)
这里的$silice是一个查询操作符,用于指定数组的切片方式,与JavaScript中的用法类似。
2.修改元素
如果希望在标签中的这个数组末尾添加一个元素,则可以使用$push操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_4.jpg?sign=1738888230-ixaiqQUs320fho5o7pW8iTlFCAzevBvt-0-9e9dba8ff505b5afe725116dc8520aa1)
$push操作符可以配合其他操作符,一起实现不同的数组修改操作,比如和$each操作符配合可以用于添加多个元素,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_5.jpg?sign=1738888230-62jycu3ug7aIoKi4ALOANxu0GUBLQnyt-0-e1be2e8ccd098335692c7b2ab7b767cf)
如果加上$slice操作符,那么只会保留经过切片后的元素,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_6.jpg?sign=1738888230-Lf0U9p2cKSqBbEhKb6Vd32XC0hcG8fgt-0-2a45ba670db980d5f1352108aa7974a3)
上述代码除了添加多个标签,最终只会保留最后的3个元素,即经过$slice操作后的结果。
3.根据元素查询
标签的一个重要作用就是用于查询,可以根据标签中的元素进行book文档的查找,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_1.jpg?sign=1738888230-7QMOh2I2NR4h5rAji2mqP5aKyXvdtAh3-0-b737f3406ef992945305b8ed6dd7a20c)
上述代码会将所有标签数组中包含“伤感”一词的book文档都查找出来。如果希望查询同时存在多个标签的文档,则可以使用$all操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_2.jpg?sign=1738888230-rvyx3vRFUcGpKkm80IBfF8nhsWEzSUGB-0-741dbdcb269539a70cc658e90b10dd0b)
3.4.3 嵌套型的数组
数组元素可以是基本类型,也可以是内嵌的文档结构,我们尝试将标签的概念扩充一下,一个标签由tagKey和tagValue所组成,文档结构如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_3.jpg?sign=1738888230-cUMt6kx460achMm0OnOz3s5JQcLGqchr-0-d6bdb3e5a1d0824dfa612c5bc7d09e91)
这种结构非常灵活,一个很适合的场景就是商品的多属性表示,如图3-4所示。
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_4.jpg?sign=1738888230-MlZQrU4lg2obagfZTcygwB7Gclue5dHM-0-01c31e38355c758d01a53350055c027e)
图3-4 电商平台中的商品属性
一个商品可以同时包含多个维度的属性,比如尺码、颜色、风格等,使用文档可以表示为:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_1.jpg?sign=1738888230-NlysTt3jCq2UjGF6iJJFhOKLUPeJPvuF-0-48d339b2be411fe0875eea684b6320ce)
以上的设计是一种常见的多值属性的做法,当我们需要根据属性进行检索时,需要用到$elementMatch操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_2.jpg?sign=1738888230-0dMnXS3v0vKS0AGZraQsMKnyc0b6hf2O-0-b273c98a739a819cfb877bcb40ee7310)
当然,如果进行组合式的条件检索,则可以使用多个$elemMatch操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_3.jpg?sign=1738888230-zoXk3iP4fHWIPbQrHQdcHUTjT9whrlzR-0-2bfce8a3f6d9bf0a8333ed29a84c51f1)
上述代码可以筛选出color=蓝色,并且size=大码的商品信息。