首页 » MongoDB实战 » MongoDB实战全文在线阅读

《MongoDB实战》2.1 深入MongoDB Shell

关灯直达底部

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/"})  

favoritesmovies之间的点告诉查询引擎应找一个名为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的更新操作默认只会应用于查询选择器匹配到的第一个文档。如果希望操作被应用于匹配到的所有文档,需要显式说明。因为你希望对smithjones都进行更新,所以多项更新是必需的。

我们稍后会对更新做更详细的说明,但请先试试这些例子。

2.1.4 删除数据

你已经知道了在MongoDB Shell中创建、读取和更新数据的基本方法了,最后我们来看最简单的操作——删除数据。

如果不加参数,删除操作会清空集合。要干掉foo集合,只需键入:

> db.foo.remove  

通常只需要删除集合文档的一个子集,为此可以给remove方法传入一个查询选择器。如果想要删除所有喜欢城市Cheyenne的用户,用下面这个表达式就行了:

> db.users.remove({/"favorites.cities/": /"Cheyenne/"})  

请注意,remove操作不会删除集合,它只是从集合中删除文档。你可以把它想象成SQL中的DELETETRUNCATE TABLE指令。

如果想删除集合以及它的全部索引,可以使用drop方法:

> db.users.drop  

创建、读取、更新和删除是所有数据库的基本操作。如果你读过了前面的内容,现在应该能在MongoDB中实践这些基本的CRUD操作了。在下一节里,你将了解到二级索引,通过它来提高查询、更新和删除的性能。