![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=1739265193-aiqHcW5Dnt1O6uM1tokBYurdZWj8Idgz-0-9599d16969ed7cadad19ef127557ea95)
一个显著的优点是,当我们查询book文档的信息时,作者的信息也会一并返回。如果只希望返回作者的名称,则可以指定author.name,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_2.jpg?sign=1739265193-8Twk3mOGiL6hPx7Sa5sDJQcehV2KTIvV-0-b9a2cce43b9894867d64549e0ed06c3f)
也可以将author.name作为查询条件,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_3.jpg?sign=1739265193-dYuTvDRqMA7p4mf09rwp2YYKvh5enRcl-0-e029bf06567e07af802ef9065f88847c)
如果作者信息需要修改,则可以指定其中的某个字段,比如修改作者的家乡所在地,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_60_4.jpg?sign=1739265193-97GCcRcSXnblvvRCq1KOXXRZiVmzyswt-0-dae9f27aca5ab253d6fe7a02fcc26e41)
3.4.2 数组
除了作者信息,book文档中还包含了若干个标签,这些标签可以用来表示book文档所包含的一些特征,如豆瓣读书中的标签(tag),如图3-3所示。
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_1.jpg?sign=1739265193-GOrnTchtUKNC7rCkVltG2HEQndHQ0hcJ-0-b6b9ca58aa2cdf71076734b41934b1df)
图3-3 豆瓣读书中的标签
我们用文档结构来表示,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_2.jpg?sign=1739265193-UEq5pmtBaTGCZ7tmuhjIRFJKOYWSgxu6-0-e10d414c039d9808a75c62fcd0ea6ee7)
1.查询元素
在查询文档时,数组中的标签会被一起返回,如果只想获得最后一个标签元素,则可以用如下命令查询:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_3.jpg?sign=1739265193-WfrFN4byiVFy6CXOist0V1qZvAT9bxBR-0-8390890355d8c6213b4106f10d857731)
这里的$silice是一个查询操作符,用于指定数组的切片方式,与JavaScript中的用法类似。
2.修改元素
如果希望在标签中的这个数组末尾添加一个元素,则可以使用$push操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_4.jpg?sign=1739265193-PDq5lK5wHLZGCEsGKzYwBJiah8EKZb4f-0-1bd6a6bd4f6b302ce241de4ee62c76c2)
$push操作符可以配合其他操作符,一起实现不同的数组修改操作,比如和$each操作符配合可以用于添加多个元素,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_5.jpg?sign=1739265193-unkHFJ5p5yRiL3Y1QC9y9saqeGTL4g5E-0-96bd2cd90c17d829cb661b7201ef14b1)
如果加上$slice操作符,那么只会保留经过切片后的元素,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_61_6.jpg?sign=1739265193-8zGGo4vNufQtNHkFC3047VF7sY0NyALU-0-43dc487193aac6c624945da5eabdfc31)
上述代码除了添加多个标签,最终只会保留最后的3个元素,即经过$slice操作后的结果。
3.根据元素查询
标签的一个重要作用就是用于查询,可以根据标签中的元素进行book文档的查找,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_1.jpg?sign=1739265193-loOW3IEj5fGqNZ8HZdx8yUBqGkH5gunX-0-07842e292105d2613be5046ea162d12c)
上述代码会将所有标签数组中包含“伤感”一词的book文档都查找出来。如果希望查询同时存在多个标签的文档,则可以使用$all操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_2.jpg?sign=1739265193-MS4eLax6JnN3ccl6az6fdXGfgBc8iCra-0-a51cccf1988a8735cb8d1d53f2939b41)
3.4.3 嵌套型的数组
数组元素可以是基本类型,也可以是内嵌的文档结构,我们尝试将标签的概念扩充一下,一个标签由tagKey和tagValue所组成,文档结构如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_3.jpg?sign=1739265193-F2ZO2Y2VxxlE8rqOnwyPewQtuA81dVhT-0-892ee9743897c5397a3facb0a643353e)
这种结构非常灵活,一个很适合的场景就是商品的多属性表示,如图3-4所示。
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_62_4.jpg?sign=1739265193-LlrrM5EXhWbq9UgUWR870Y8RC2sYyvTn-0-c129c7f92ae1cc03a1719a1f61e8dcb0)
图3-4 电商平台中的商品属性
一个商品可以同时包含多个维度的属性,比如尺码、颜色、风格等,使用文档可以表示为:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_1.jpg?sign=1739265193-k4GN9jWG3H9MInmSLDFKeGSM2fqS7vKO-0-057a01dfc086116a7c10d52a3911f7b2)
以上的设计是一种常见的多值属性的做法,当我们需要根据属性进行检索时,需要用到$elementMatch操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_2.jpg?sign=1739265193-UoOLJIRLRiqxiPpvdJXT3FlaAPoocfjp-0-4ae06e93d5f67e08f2deba20564c0773)
当然,如果进行组合式的条件检索,则可以使用多个$elemMatch操作符,代码如下:
![](https://epubservercos.yuewen.com/6F449D/20118171608699906/epubprivate/OEBPS/Images/40827_63_3.jpg?sign=1739265193-Z48I66PzlhbvzsEtchxG5B62sebHzw6I-0-c91b0f6c04261d8b93e67513fd1da6a8)
上述代码可以筛选出color=蓝色,并且size=大码的商品信息。