MongoDB JavaScript Shell能让你玩转数据,对文档、集合以及MongoDB的特殊查询语言有切实的体验。你可以把以下内容当做对MongoDB的实用入门。
我们先说说Shell的启动与运行,然后再看看JavaScript是如何表示文档并将其插入MongoDB集合的。为了验证插入是否成功,你可以查询集合内容。紧随其后的是更新集合。最后,你还将了解到如何清除并删除集合。
2.1.1 启动Shell
如果已按照附录A的说明进行了安装,那么电脑上现在应该已经有一个可正常工作的MongoDB了。请确保有一个正在运行的mongod
实例,随后运行可执行文件mongo
启动MongoDB shell:
./mongo
如果Shell程序启动成功,你将看到如图2-1所示的界面。Shell开始的地方显示了正运行的MongoDB版本,还有和当前选中的数据库相关的一些信息。
图2-1 启动后的MongoDB JavaScript Shell
如果你懂点JavaScript,马上就可以键入代码使用Shell。如果不懂,就请继续读下去,看看如何插入自己的第一条数据。
2.1.2 插入与查询
如果启动时没有指定其他数据库,Shell会选择名为test
的默认数据库。为了让后续的教学练习都在同一个命名空间里,我们先切换到tutorial
数据库:
> use tutorialswitched to db tutorial
你会看到一行消息,说明你已经切换了数据库。
创建数据库与集合
你也许会感到奇怪:我们并没有创建
tutorial
数据库,又怎么能切换过去呢?实际上,创建数据库并不是必需的操作。数据库与集合只有在第一次插入文档时才会被创建。这个行为与MongoDB对数据的动态处理方式是一致的;因为不用事先定义文档的结构,单独的集合和数据库可以在运行时才被创建。这能简化并加速开发过程,而且有利于动态分配命名空间,很多时候这都很管用。如果你担心数据库或集合被意外创建,大多数驱动都能开启严格模式(strict mode),避免此类由疏忽引起的错误。
现在是时候创建你的第一个文档了。因为正在使用JavaScript Shell,所以将用JSON(JavaScript Object Notation)来描述文档。举个例子,一个最简单的描述用户的文档如下:
{username: /"jones/"}
该文档包含一对键和值,存储了Jones的用户名。要保存这个文档,需要选择一个集合,像下面这样把它保存到users集合里就再恰当不过了:
> db.users.insert({username: /"smith/"})
在键入这行代码后你会感觉到一丝延迟。这时tutorial
数据库和users
集合都还没在磁盘上创建出来,延迟是因为要为它们的初始化数据文件分配空间。
如果插入成功,那么就已经成功地保存了第一个文档。可以通过一条简单的查询来进行验证:
> db.users.find
查询的结果看起来是这样的:
{ _id : ObjectId(/"4bf9bec50e32f82523389314/"), username : /"smith/" }
请注意,文档中添加了_id
字段,你可以把它当做文档的主键。每个MongoDB文档都要求有一个_id
,如果文档创建时没有提供该字段,就会生成一个特殊的MongoDB对象ID并添加到文档里。在你——控制台里出现的对象ID与示例中的并不一样,但它在集合的所有_id
值里是唯一的,这是对该字段的唯一硬性要求。
在下一章里我会详细介绍对象ID。现在继续向集合中添加用户:
> db.users.save({username: /"jones/"})
集合里现在应该有两个文档了。接下来通过count命令验证一下:
> db.users.count2
既然集合里文档的数量已经不止一个了,那么就能看些稍微复杂一点儿的查询了。与之前一样,可以把集合里所有的文档都查出来:
> db.users.find{ _id : ObjectId(/"4bf9bec50e32f82523389314/"), username : /"smith/" }{ _id : ObjectId(/"4bf9bec90e32f82523389315/"), username : /"jones/" }
但也可以给find
方法传入一个简单的查询选择器。查询选择器(query selector)是一个文档,用来和集合中所有的文档进行匹配。要查询所有用户名是jones
的文档,可以像下面这样传入一个简单的文档,将其作为查询选择器:
> db.users.find({username: /"jones/"}){ _id : ObjectId(/"4bf9bec90e32f82523389315/"), username : /"jones/" }
查询选择器{username: /"jones
/"}返回了所有用户名是jones
的文档——它会逐字匹配现有的文档。
我刚才演示了创建和读取数据的基本方法,现在再来看看如何更新数据。
2.1.3 更新文档
所有的更新操作都要求至少有两个参数,第一个指明要更新的文档,第二个定义被选中的文档应该如何更新。有两种风格的更新;本节只关注针对性更新(targeted modification),这是MongoDB独有特性中最具代表性的。
举例来说,假设用户smith想要向自己的住所中添加国家信息,可以使用如下更新语句:
> db.users.update({username: /"smith/"}, {$set: {country: /"Canada/"}})
这条更新语句告诉MongoDB应找到用户名是smith
的文档,将其country
属性值设置为Canada
。如果现在执行查询,你将看到更新后的文档:
> db.users.find({username: /"smith/"}){ /"_id/" : ObjectId(/"4bf9ec440e32f82523389316/"), /"country/" : /"Canada/", username : /"smith/" }
稍后,如果用户决定档案中不再保留国家信息,使用$unset
操作符就能轻松去除该值:
> db.users.update({username: /"smith/"}, {$unset: {country: 1}})
让我们再丰富一下这个例子。如第1章所示,你使用文档来表示数据,其中能包含复杂的数据结构。因此,让我们假设一下,除了存储个人档案信息,用户还能用列表来存储自己喜欢的东西。一个好的文档表述看起来可能是这样的:
{ username: /"smith/", favorites: { cities: [/"Chicago/", /"Cheyenne/"], movies: [/"Casablanca/", /"For a Few Dollars More/", /"The Sting/"] }}
favorites
键指向一个对象,后者包含两个其他的键,它们分别指向喜欢的城市列表和电影列表。就目前所知道的知识,你能否想出一个办法将原来的smith
文档修改成这样?你应该能想到$set
操作符。请注意,本例实际是在改写文档,这也是$set
的合理用法:
> db.users.update( {username: /"smith/"},{ $set: {favorites: { cities: [/"Chicago/", /"Cheyenne/"], movies: [/"Casablanca/", /"The Sting/"] }}})
让我们对jones
做类似修改,但这里就添加两部喜欢的电影:
db.users.update( {username: /"jones/"}, {/"$set/": {favorites: { movies: [/"Casablanca/", /"Rocky/"] } }})
现在查询users
集合,确保两个更新都成功了:
> db.users.find
有了之前的几个示例文档,现在可以一窥MongoDB查询语言的威力了。尤其值得一提的是,它的查询引擎能深入内嵌对象,匹配数组元素,这种情况下这种能力特别有用。你可以使用特殊的点符号来实现这类查询。假设想找到所有喜欢电影《卡萨布兰卡》(Casablanca)的用户,能用这样的查询:
> db.users.find({/"favorites.movies/": /"Casablanca/"})
favorites
和movies
之间的点告诉查询引擎应找一个名为favorites
的键,它指向一个对象(该对象有一个名为movies
的内部键),然后匹配它的值。这条查询会把两个用户文档都返回。更进一步,假设你知道每个喜欢《卡萨布兰卡》的用户都喜欢《马耳他之鹰》(The Maltese Falcon),且想更新数据库来反映这个情况,该如何用一条MongoDB的更新语句来表示呢?
你可以再次请出$set
操作符,但这要求改写并发送整个movies
数组。既然你想做的只是向列表里添加一个元素,最好使用$push
或$addToSet
,这两个操作符都是向数组中添加一个元素,但后者会保证唯一性,防止重复添加。下面就是你要找的更新语句:
db.users.update( {/"favorites.movies/": /"Casablanca/"}, {$addToSet: {/"favorites.movies/": /"The Maltese Falcon/"} }, false, true )
这条语句大体上还是易懂的:第一个参数是一个查询选择器,匹配电影列表里有Casablanca的用户;第二个参数使用$addToSet
操作符向列表中添加了The Maltese Falcon;第三个参数false
现在暂时忽略;第四个参数true
说明这是一个多项更新(multi-update)。MongoDB的更新操作默认只会应用于查询选择器匹配到的第一个文档。如果希望操作被应用于匹配到的所有文档,需要显式说明。因为你希望对smith
和jones
都进行更新,所以多项更新是必需的。
我们稍后会对更新做更详细的说明,但请先试试这些例子。
2.1.4 删除数据
你已经知道了在MongoDB Shell中创建、读取和更新数据的基本方法了,最后我们来看最简单的操作——删除数据。
如果不加参数,删除操作会清空集合。要干掉foo
集合,只需键入:
> db.foo.remove
通常只需要删除集合文档的一个子集,为此可以给remove
方法传入一个查询选择器。如果想要删除所有喜欢城市Cheyenne的用户,用下面这个表达式就行了:
> db.users.remove({/"favorites.cities/": /"Cheyenne/"})
请注意,remove
操作不会删除集合,它只是从集合中删除文档。你可以把它想象成SQL中的DELETE
和TRUNCATE TABLE
指令。
如果想删除集合以及它的全部索引,可以使用drop
方法:
> db.users.drop
创建、读取、更新和删除是所有数据库的基本操作。如果你读过了前面的内容,现在应该能在MongoDB中实践这些基本的CRUD操作了。在下一节里,你将了解到二级索引,通过它来提高查询、更新和删除的性能。