作者选择了 开放式互联网 / 自由言论基金作为 写给捐赠计划的一部分接受捐款。
介绍
MongoDB是一个持久的以文档为导向的数据库,用于以文档形式存储和处理数据,与其他数据库管理系统一样,MongoDB允许您通过四种基本类型的数据操作来管理和交互数据:
Create操作,涉及将数据写入数据库
- Read操作,用于查询数据库以从其中获取数据
- Update操作,用于更改数据库中已经存在的数据库
- Delete操作,永久删除数据库
这四个操作被称为 CRUD 操作。
本教程概述了如何创建新的 MongoDB 文档,然后获取它们以读取其数据,它还解释了如何更新文档中的数据,以及如何在不再需要时删除文档。
前提条件
要遵循本教程,您将需要:
- 一个拥有
sudo
特权和与 UFW 配置的防火墙的常规非根用户的服务器. 本教程是使用运行 Ubuntu 20.04 的服务器进行验证的,您可以通过遵循此(Ubuntu 20.04 的初始服务器设置教程)来准备您的服务器(https://andsky.com/tech/tutorials/initial-server-setup-with-ubuntu-20-04)。 - MongoDB 安装在您的服务器上。 要设置此,请遵循我们的教程(How to Install MongoDB on Ubuntu 20.04)(https://andsky.com/tech/tutorials/how-to-install-mongodb-on-ubuntu-20-04)。
- 您的服务器的 MongoDB 实例通过启用身份验证和创建一个管理用户而得到保护。 为了确保 MongoDB 像这样,请遵循我们的教程(How To Secure MongoDB on Ubuntu 20.04)(https://andsky.com/tech/tutorials/how-to-secure-mongodb-on-ubuntu-20-04). _
<$>[注] **注:**有关如何配置服务器,安装,然后安全MongoDB安装的链接教程参考Ubuntu 20.04.本教程专注于MongoDB本身,而不是潜在的操作系统。
步骤 1 – 连接到 MongoDB 服务器
此指南涉及使用 MongoDB 壳与 MongoDB 互动. 为了跟踪和练习 MongoDB 中的 CRUD 操作,您必须先通过打开 MongoDB 壳连接到 MongoDB 数据库。
如果您的 MongoDB 实例在远程服务器上运行,则从本地计算机向该服务器输入 SSH:
1[environment local]
2ssh sammy@your_server_ip
然后通过打开 MongoDB 壳来连接到您的 MongoDB 安装程序. 请确保您作为具有编写和阅读数据权限的 MongoDB 用户连接. 如果您遵循了 前提 MongoDB 安全教程,您可以作为您在该指南的 步骤 1中创建的管理用户连接:
1mongo -u AdminSammy -p --authenticationDatabase admin
在提供用户密码后,您的终端提示将更改为大于符号(>
)。
<$>[注]
注: 新连接时,MongoDB 壳将默认情况下自动连接到测试
数据库,您可以安全地使用此数据库来实验 MongoDB 和 MongoDB 壳。
或者,您也可以切换到另一个数据库以运行本教程中提供的所有示例命令. 要切换到另一个数据库,请运行使用
命令,然后是您的数据库名称:
1use database_name
美元
现在您已使用 MongoDB 壳连接到 MongoDB 服务器,您可以继续创建新文档。
步骤二:创建文件
为了在本指南的后续步骤中练习阅读、更新和删除数据,此步骤侧重于如何在 MongoDB 中创建数据文档。
假设您使用 MongoDB 构建和管理来自世界各地的著名历史古迹目录,此目录将存储每个古迹的名称、国家、城市和地理位置等信息。
本目录中的文档将遵循类似于此示例的格式,该格式代表 ** 吉萨金字塔**:
1[label The Pyramids of Giza]
2{
3 "name": "The Pyramids of Giza",
4 "city": "Giza",
5 "country": "Egypt",
6 "gps": {
7 "lat": 29.976480,
8 "lng": 31.131302
9 }
10}
此文档和所有 MongoDB 文档一样,都是用 BSON 编写的。BSON 是 JSON的二进制形式,是人类可读的数据格式. BSON 或 JSON 文档中的所有数据都以字段:值
的形式表示为字段和值对。
该文件由四个字段组成. 首先是纪念碑的名称,其次是城市和国家。 所有三个字段都包含字符串. 最后一个字段,称为gps
,是一个嵌入的文件,详细介绍了纪念碑的GPS位置。 这个位置由一对纬度和长度坐标组成,分别由lat
和lng
字段表示,每个字段都包含浮点值。
<$>[注] **注:**您可以在我们的概念文章中了解更多有关MongoDB文档的结构。
使用insertOne
方法将此文档插入一个名为纪念碑
的新集合中,正如其名称所暗示的那样,insertOne
用于创建单个文档,而不是同时创建多个文档。
在 MongoDB 壳中,执行以下操作:
1db.monuments.insertOne(
2 {
3 "name": "The Pyramids of Giza",
4 "city": "Giza",
5 "country": "Egypt",
6 "gps": {
7 "lat": 29.976480,
8 "lng": 31.131302
9 }
10 }
11)
请注意,在执行此insertOne
方法之前,您没有明确创建纪念碑
集合。MongoDB允许您在不存在的集合上自由运行命令,而缺少的集合只有在插入第一个对象时才被创建。
MongoDB将执行insertOne
方法并插入代表吉萨金字塔的请求文档,该操作的输出将告知您该操作成功执行,并为新文档提供自动生成的ObjectId
:
1[secondary_label Output]
2{
3 "acknowledged" : true,
4 "insertedId" : ObjectId("6105752352e6d1ebb7072647")
5}
在 MongoDB 中,集合中的每个文档都必须有一个独特的_id
字段,该字段作为主要密钥。您可以包括_id
字段,并为其提供您自己选择的值,只要您确保每个文档的_id
字段是独一无二的。
您可以通过检查纪念碑
收藏中的对象数来验证文档的插入:
1db.monuments.count()
由于您只将一个文档插入到该集合中,因此计数
方法将返回1
:
1[secondary_label Output]
21
如果您想要创建多个文档,这样单独插入文档会很快变得无聊。
运行以下示例命令,该命令使用insertMany
方法将六个额外的著名纪念碑插入纪念碑
集合:
1db.monuments.insertMany([
2 {"name": "The Valley of the Kings", "city": "Luxor", "country": "Egypt", "gps": { "lat": 25.746424, "lng": 32.605309 }},
3 {"name": "Arc de Triomphe", "city": "Paris", "country": "France", "gps": { "lat": 48.873756, "lng": 2.294946 }},
4 {"name": "The Eiffel Tower", "city": "Paris", "country": "France", "gps": { "lat": 48.858093, "lng": 2.294694 }},
5 {"name": "Acropolis", "city": "Athens", "country": "Greece", "gps": { "lat": 37.970833, "lng": 23.726110 }},
6 {"name": "The Great Wall of China", "city": "Huairou", "country": "China", "gps": { "lat": 40.431908, "lng": 116.570374 }},
7 {"name": "The Statue of Liberty", "city": "New York", "country": "USA", "gps": { "lat": 40.689247, "lng": -74.044502 }}
8])
注意围绕六个文档的方块([
和 ]
)。这些方块表示文档的 _array。在方块中,可以出现一个接一个的多个对象,这些对象由条纹划分。
MongoDB 将用多个对象标识符响应,为每一个新插入的对象提供一个:
1[secondary_label Output]
2{
3 "acknowledged" : true,
4 "insertedIds" : [
5 ObjectId("6105770952e6d1ebb7072648"),
6 ObjectId("6105770952e6d1ebb7072649"),
7 ObjectId("6105770952e6d1ebb707264a"),
8 ObjectId("6105770952e6d1ebb707264b"),
9 ObjectId("6105770952e6d1ebb707264c"),
10 ObjectId("6105770952e6d1ebb707264d")
11 ]
12}
您可以通过检查纪念碑
收藏中的对象数来验证文档的插入:
1db.monuments.count()
添加了这六个新文档后,这个命令的预期输出为7
:
1[secondary_label Output]
27
因此,您使用了两个单独的插入方法来创建一系列代表几个著名的纪念碑的文档,接下来,您将阅读您刚刚使用MongoDB的find()
方法插入的数据。
步骤三:阅读文件
现在您的收藏中存储了一些文档,您可以查询您的数据库以获取这些文档并读取其数据. 此步骤首先描述了如何查询特定收藏中的所有文档,然后描述了如何使用过滤器来缩小所获取文档列表。
完成前一步后,您现在有七份描述著名的纪念碑的文件插入到纪念碑
收藏中,您可以使用find()
方法以一次操作获取所有七份文件:
1db.monuments.find()
这种方法在没有任何参数的情况下使用时,不会应用任何过滤,并要求MongoDB返回指定的集合中的所有可用对象,即纪念碑
。
1[secondary_label Output]
2{ "_id" : ObjectId("6105752352e6d1ebb7072647"), "name" : "The Pyramids of Giza", "city" : "Giza", "country" : "Egypt", "gps" : { "lat" : 29.97648, "lng" : 31.131302 } }
3{ "_id" : ObjectId("6105770952e6d1ebb7072648"), "name" : "The Valley of the Kings", "city" : "Luxor", "country" : "Egypt", "gps" : { "lat" : 25.746424, "lng" : 32.605309 } }
4{ "_id" : ObjectId("6105770952e6d1ebb7072649"), "name" : "Arc de Triomphe", "city" : "Paris", "country" : "France", "gps" : { "lat" : 48.873756, "lng" : 2.294946 } }
5{ "_id" : ObjectId("6105770952e6d1ebb707264a"), "name" : "The Eiffel Tower", "city" : "Paris", "country" : "France", "gps" : { "lat" : 48.858093, "lng" : 2.294694 } }
6{ "_id" : ObjectId("6105770952e6d1ebb707264b"), "name" : "Acropolis", "city" : "Athens", "country" : "Greece", "gps" : { "lat" : 37.970833, "lng" : 23.72611 } }
7{ "_id" : ObjectId("6105770952e6d1ebb707264c"), "name" : "The Great Wall of China", "city" : "Huairou", "country" : "China", "gps" : { "lat" : 40.431908, "lng" : 116.570374 } }
8{ "_id" : ObjectId("6105770952e6d1ebb707264d"), "name" : "The Statue of Liberty", "city" : "New York", "country" : "USA", "gps" : { "lat" : 40.689247, "lng" : -74.044502 } }
请注意,这些对象中的每一个都具有您未定义的_id
属性。如前所述,_id
字段作为各自的文档的主要密钥,并在您在上一步运行insertMany
方法时自动创建。
MongoDB 壳的默认输出是紧凑的,每个文档的字段和值被打印成一个行,特别是包含多个字段或嵌入文档的对象很难读取。
要使find()
方法的输出更易于读取,您可以使用其漂亮
打印功能,如下:
1db.monuments.find().pretty()
这一次,MongoDB壳将在多个行上打印文档,每个行都有插头:
1[secondary_label Output]
2{
3 "_id" : ObjectId("6105752352e6d1ebb7072647"),
4 "name" : "The Pyramids of Giza",
5 "city" : "Giza",
6 "country" : "Egypt",
7 "gps" : {
8 "lat" : 29.97648,
9 "lng" : 31.131302
10 }
11}
12{
13 "_id" : ObjectId("6105770952e6d1ebb7072648"),
14 "name" : "The Valley of the Kings",
15 "city" : "Luxor",
16 "country" : "Egypt",
17 "gps" : {
18 "lat" : 25.746424,
19 "lng" : 32.605309
20 }
21}
22. . .
請注意,在前兩個例子中,「find()」方法在沒有任何論點的情況下執行,在這兩種情況下,它會返回集合中的每個對象。
请记住从之前的例子中,MongoDB 自动分配给The Valley of the Kings
一个具有ObjectId
(6105770952e6d1ebb7072648
)的值的对象标识符。
下面的查找()
方法通过接受一个 _ 查询过滤文件_ 作为参数返回一个单个对象. 查询过滤文件的结构与您插入到集合中的文档相同,包括字段和值,但它们被用来过滤查询结果。
在本示例中使用的查询过滤文件中包含_id
字段,其值为The Valley of the Kings
**对象标识符。
1db.monuments.find({"_id": ObjectId("6105770952e6d1ebb7072648")}).pretty()
此示例中的查询过滤文件使用平等条件,这意味着查询将返回任何具有文件中指定的字段和值对的文档,本质上,此示例告诉find()
方法只返回那些文档的_id
值等于ObjectId
(6105770952e6d1ebb7072648
)。
执行此方法后,MongoDB 将返回一个匹配所请求的对象标识符的单个对象:
1[secondary_label Output]
2{
3 "_id" : ObjectId("6105770952e6d1ebb7072648"),
4 "name" : "The Valley of the Kings",
5 "city" : "Luxor",
6 "country" : "Egypt",
7 "gps" : {
8 "lat" : 25.746424,
9 "lng" : 32.605309
10 }
11}
您也可以在文档中的任何其他领域使用质量条件. 为了说明,请尝试查找法国的纪念碑:
1db.monuments.find({"country": "France"}).pretty()
这种方法将返回两个纪念碑:
1[secondary_label Output]
2{
3 "_id" : ObjectId("6105770952e6d1ebb7072649"),
4 "name" : "Arc de Triomphe",
5 "city" : "Paris",
6 "country" : "France",
7 "gps" : {
8 "lat" : 48.873756,
9 "lng" : 2.294946
10 }
11}
12{
13 "_id" : ObjectId("6105770952e6d1ebb707264a"),
14 "name" : "The Eiffel Tower",
15 "city" : "Paris",
16 "country" : "France",
17 "gps" : {
18 "lat" : 48.858093,
19 "lng" : 2.294694
20 }
21}
查询过滤文件非常强大和灵活,它们允许您将复杂的过滤器应用到收集文档中。
您可以在我们的 如何创建查询教程中了解有关查询集合的不同方式的更多信息。
──」
第4步:更新文件
MongoDB 等文档导向数据库中的文档通常会随着时间的推移而发生变化,有时它们的结构必须随着应用程序的需求的变化而演变,或者数据本身可能会发生变化。
与insertOne()
和insertMany()
方法类似,MongoDB 提供了允许您同时更新单个文档或多个文档的方法. 与这些更新方法的一个重要区别是,在创建新文档时,您只需要将文档数据传递为方法参数。
为了允许用户这样做,MongoDB 在更新方法中使用相同的查询过滤文件机制,就像您在上一步中使用的查找和检索文档一样。
尝试将 Arc de Triomphe的名称更改为 Arc de Triomphe de l'Étoile的完整名称。
1db.monuments.updateOne(
2 { "name": "Arc de Triomphe" },
3 {
4 $set: { "name": "Arc de Triomphe de l'Étoile" }
5 }
6)
「更新一」方法的第一个参数是具有单一等级条件的查询过滤文件,如前一个步骤所述。在本示例中,名称
:胜利方舟
找到了具有名称
密钥的文档,其值为胜利方舟
。
第二个参数是更新文档,说明在更新过程中应该应用哪些更改。更新文档包括更新操作员作为密钥,以及每个操作员的参数作为值。在本示例中,使用的更新操作员是$set
。它负责将文档字段设置为新值,并需要一个具有新值的JSON对象。在这里,设置: {
名称:
Arc de Triomphe de l’Étoile }
告诉MongoDB将字段的值设置为Arc de Triomphe de l’Étoile
。
该方法将返回一个结果,告诉您查询过滤器文档发现了一个对象,并成功更新了一个对象。
1[secondary_label Output]
2{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
<$>[注]
注: 如果文档查询过滤器不足以选择单个文档,则 updateOne()
只会更新从多个结果中返回的 first 文档。
要检查更新是否有效,请尝试检索与 France
相关的所有纪念碑:
1db.monuments.find({"country": "France"}).pretty()
这次,该方法返回了 Arc de Triomphe,但它的完整名称被更改了更新操作:
1[secondary_label Output]
2{
3 "_id" : ObjectId("6105770952e6d1ebb7072649"),
4 "name" : "Arc de Triomphe de l'Étoile",
5 "city" : "Paris",
6 "country" : "France",
7 "gps" : {
8 "lat" : 48.873756,
9 "lng" : 2.294946
10 }
11}
12. . .
若要更改多个文档,则可以使用 updateMany()
方法。
举个例子,假设您注意到没有关于谁创建了该条目的信息,并且您希望将每个纪念碑添加到数据库的作者归功。
下面的示例包括一个空查询过滤文件. 通过添加一个空查询文档,此操作将匹配集合中的每个文档,并且 updateMany()
方法会影响每个文档。
1db.monuments.updateMany(
2 { },
3 {
4 $set: { "editor": "Sammy" }
5 }
6)
此方法将返回以下输出:
1[secondary_label Output]
2{ "acknowledged" : true, "matchedCount" : 7, "modifiedCount" : 7 }
此输出告知您,7个文件匹配,7个文件也进行了修改。
确认变更已应用:
1db.monuments.find().pretty()
1[secondary_label Output]
2{
3 "_id" : ObjectId("6105752352e6d1ebb7072647"),
4 "name" : "The Pyramids of Giza",
5 "city" : "Giza",
6 "country" : "Egypt",
7 "gps" : {
8 "lat" : 29.97648,
9 "lng" : 31.131302
10 },
11 "editor" : "Sammy"
12}
13{
14 "_id" : ObjectId("6105770952e6d1ebb7072648"),
15 "name" : "The Valley of the Kings",
16 "city" : "Luxor",
17 "country" : "Egypt",
18 "gps" : {
19 "lat" : 25.746424,
20 "lng" : 32.605309
21 },
22 "editor" : "Sammy"
23}
24. . .
所有返回的文档现在都有一个名为编辑器
的新字段,设置为Sammy
。 通过向$set
更新操作器提供一个不存在的字段名称,更新操作将在所有匹配的文档中创建缺少的字段,并正确设置新的值。
虽然您可能会经常使用$set
,但 MongoDB 中还有许多其他更新操作器,允许您对文档的数据和结构进行复杂的更改。
步骤5:删除文件
与 Mongo 的更新和插入操作一样,有一个 deleteOne()
方法,只删除查询过滤文件匹配的 first 文档,以及 deleteMany()
,同时删除多个对象。
要练习使用这些方法,请尝试删除您之前修改的 Arc de Triomphe de l'Étoile纪念碑:
1db.monuments.deleteOne(
2 { "name": "Arc de Triomphe de l'Étoile" }
3)
请注意,此方法包括查询过滤文件,例如以前的更新和检索示例,您可以使用任何有效的查询来指定将删除的文档。
MongoDB 将返回以下结果:
1[secondary_label Output]
2{ "acknowledged" : true, "deletedCount" : 1 }
在这里,结果告诉你在过程中删除了多少文档。
检查该文档是否确实从收藏中被删除,通过查询法国的纪念碑:
1db.monuments.find({"country": "France"}).pretty()
这一次,该方法只返回单一的纪念碑,即艾菲尔铁塔
,因为您删除了阿克德·德·埃斯托尔
(Arc de Triomphe de l’Étoile):
1[secondary_label Output]
2{
3 "_id" : ObjectId("6105770952e6d1ebb707264a"),
4 "name" : "The Eiffel Tower",
5 "city" : "Paris",
6 "country" : "France",
7 "gps" : {
8 "lat" : 48.858093,
9 "lng" : 2.294694
10 },
11 "editor" : "Sammy"
12}
为了说明删除多个文档,请删除所有Sammy
是编辑的纪念文档,从而将收藏排空,因为您之前已经指定了Sammy
为每个纪念碑的编辑:
1db.monuments.deleteMany(
2 { "editor": "Sammy" }
3)
这一次,MongoDB 让你知道这个方法删除了六个文档:
1[secondary_label Output]
2{ "acknowledged" : true, "deletedCount" : 6 }
您可以通过计算其中的文件数量来验证纪念碑
收藏现在是空的:
1db.monuments.count()
1[secondary_label Output]
20
由于您刚刚从集合中删除了所有文档,此命令返回预期输出值为0
。
结论
通过阅读本文,您已经熟悉了CRUD操作的概念 - Create, Read, Update和 Delete - 数据管理的四个基本组件。
但是,请注意,本教程只涵盖了一种基本的查询过滤方式。MongoDB提供了一个强大的查询系统,允许您根据复杂的标准精确选择感兴趣的文档。