一般在想到驱动时,映入脑海的都是低级的位操作和迟钝的接口。感谢上帝,MongoDB的语言驱动和这一点儿都不沾边,API反而设计得很直观、很对语言的胃口,因此很多应用程序索性直接把MongoDB驱动作为与数据库通信的唯一接口。驱动API在不同语言之间保持着相当的一致性,这意味着,如果需要,开发者可以轻松地在语言之间进行切换。如果你是一位应用程序开发者,会发现在使用任何MongoDB驱动时都感觉良好且生产率很高,不用自己操心底层的实现细节。
本节将带你安装MongoDB Ruby驱动,连接数据库,了解如何执行基本的CRUD操作。这将为本章最后要构建的应用程序打下基础。
3.1.1 安装与连接
我们可以使用RubyGems安装MongoDB Ruby驱动,RubyGems是Ruby的包管理系统。
注意 如果还没在系统上安装Ruby,可以在找到详细的安装指南。你还需要Ruby的包管理器RubyGems,可以在http://docs.rubygems.org/read/chapter/3找到RubyGems的安装指南。
gem install mongo
这条命令会安装mongo
和bson
1Gem。我们应该会看到如下输出(版本号可能会比下面的更高):
1. BSON会在下一节中做详细说明,它是一种受JSON启发的二进制格式,MongoDB用它来表示文档。bson
Ruby Gem能将Ruby对象序列化为BSON,反之亦然。
Successfully installed bson-1.4.0Successfully installed mongo-1.4.02 gems installedInstalling ri documentation for bson-1.4.0...Installing ri documentation for mongo-1.4.0...Installing RDoc documentation for bson-1.4.0...Installing RDoc documentation for mongo-1.4.0...
我们从连接MongoDB开始。首先确保mongod
正在运行,接下来创建一个名为connect.rb
的文件,键入以下代码:
require /'rubygems/'require /'mongo/'@con = Mongo::Connection.new@db = @con[/'tutorial/']@users = @db[/'users/']
头两条require
语句保证一定加载了驱动,接下来的三行实例化了一个连接,将tutorial
数据库分配给了@db
变量,在@users
变量中保存了一个对users
集合的引用。保存并运行文件:
$ruby connect.rb
如果没有抛出异常,那么你已经成功地用Ruby连接到MongoDB了。虽然不够诱人,但连接是任何语言使用MongoDB的第一步。接下来,我们使用该连接插入文档。
3.1.2 用Ruby插入文档
所有MongoDB驱动在设计上都要求使用其语言中最自然的文档表述方式。在JavaScript中JSON对象是最明显的选择,因为JSON是一种文档数据结构;在Ruby中,散列数据结构最为合适。原生的Ruby散列和JSON对象只是稍有不同,最明显的是JSON用冒号来分隔键和值,而Ruby则使用=>2。
2. 在Ruby 1.9中,也可以将冒号作为键值分隔符,但为了保证向后兼容性,本书中仅使用=>。
如果你是一路跟着示例做下来的,那么继续向connect.rb文件添加代码。你也可以选择另一种不错的方式,即使用Ruby的交互式REPL——irb
。你可以运行irb
,载入connect.rb,这样立刻就能访问到其中实例化的连接、数据库和集合对象了。接着可以运行Ruby代码并接收实时反馈。下面就是一个例子:
$ irb -r connect.rbirb(main):001:0> id = @users.save({/"lastname/" => /"knuth/"})=> BSON::ObjectId(/'4c2cfea0238d3b915a000004/')irb(main):002:0> @users.find_one({/"_id/" => id})=> {/"_id/"=>BSON::ObjectId(/'4c2cfea0238d3b915a000004/'), /"lastname/"=>/"knuth/"}
让我们为users
集合构建一些文档。创建两个文档来表示用户smith和jones。每个文档都用Ruby散列来表示并被分配一个变量:
smith = {/"last_name/" => /"smith/", /"age/" => 30}jones = {/"last_name/" => /"jones/", /"age/" => 40}
要保存文档,将它们传给集合的insert
方法即可。每次调用insert
都会返回一个唯一ID,应该将它保存在变量里以便日后获取数据:
smith_id = @users.insert(smith)jones_id = @users.insert(jones)
可以通过一些简单的查询来验证文档是否成功保存。通常每个文档的对象ID都会被保存在_id
键中。可以通过用户集合的find_one
方法来进行查询:
@users.find_one({/"_id/" => smith_id})@users.find_one({/"_id/" => jones_id})
如果你是在irb
里运行代码的,查询的返回值会显示在提示符中。如果是运行Ruby文件,加上Ruby的p
方法,把结果输出到屏幕上:
p @users.find_one({/"_id/" => smith_id})
你已经成功地用Ruby插入了两个文档,现在再来仔细看看查询。
3.1.3 查询与游标
你刚使用了驱动的find_one
方法来获取单条结果。能这么简单是因为find_one
隐藏了一些MongoDB查询的细节。通过标准的find
方法能对此有所了解,以下是两个可能的对数据集的查找操作:
@users.find({/"last_name/" => /"smith/"})@users.find({/"age/" => {/"$gt/" => 20}}
很明显,第一个查询找出了所有last_name
是smith
的用户文档,第二个查询匹配所有age
大于20
的文档。试着在irb
中键入第二个查询:
irb(main):008:0> @users.find({/"age/" => {/"$gt/" => 30}})=> <#Mongo::Cursor:0x10109e118 ns=/"tutorial.users/" @selector={/"age/" => /"$gt/" => 30}}>
你将发现的第一件事会是find
方法并不返回结果集,而是一个游标对象。游标出现在很多数据库系统中,出于对效率的考虑,迭代地批量返回查询结果集。假设users
集合包含100万个匹配查询的文档。如果没有游标,就必须一次性返回全部这些文档。立刻返回这么大的结果意味着将所有数据复制到内存里,通过网络进行传输,然后反序列化到客户端。这本不应是个资源密集型操作,为了防止这种情况,查询实例化了一个游标,以一个可控的分块大小来获取结果集。当然,这对用户而言是透明的;在按需通过游标请求更多结果、连续调用MongoDB时,会填充驱动的游标缓冲。
下一节中会更详细地解释游标。回到例子上,现在获取到$gt
查询的结果:
cursor = @users.find({/"age/" => {/"$gt/" => 20}})cursor.each do |doc| puts doc[/"last_name/"]end
这里用到了Ruby的each
迭代器,它将每个结果都传递给一个代码块,本例中,稍后会将last_name
属性输出到控制台。如果你不熟悉Ruby的迭代器,下面是一段更语言中立的等效代码:
cursor = @users.find({/"age/" => {/"$gt/" => 20}})while doc = cursor.next puts doc[/"last_name/"]end
这个例子里,我们连续调用游标的next
方法,将值赋给本地变量doc
,使用一个简单的while
循环对游标进行迭代。
回想上一章里的Shell示例,再想想本节的游标,你会感到大吃一惊。Shell中使用游标的方式与驱动一样,不同之处在于调用find
时Shell会自动迭代前20个游标结果。要获取剩下的结果,可以通过it
命令继续手工迭代。
3.1.4 更新与删除
注意,上一章里的更新操作要求至少有两个参数:一个查询选择器和一个更新文档。下面是一个使用Ruby驱动的简单示例:
@users.update({/"last_name/" => /"smith/"}, {/"$set/" => {/"city/" => /"Chicago/"}})
这个更新先查找last_name
是smith
的第一个用户,如果找到的话就将它的city
值设置为Chicago
,其中使用了$set
操作符。
默认情况下,MongoDB只会更新单个文档。就算你有多个用户的姓是smith
,也只会更新一个文档。要将更新应用到特定的smith
上,需要向查询选择器添加更多的条件。但如果是想更新所有的smith
文档,必须发起多项更新(multi-update)。为此,我们可以将:multi => true
作为第三个参数传递给update
方法:
@users.update({/"last_name/" => /"smith/"}, {/"$set/" => {/"city/" => /"New York/"}}, :multi => true)
删除数据更加简单,使用remove
方法就可以了。该方法接受一个可选的查询选择器,只删除那些匹配选择器的文档。如果没有提供选择器,就删除集合中的所有文档。此处,我们要删除age
属性值大于等于40的所有用户文档:
@users.remove({/"age/" => {/"$gte/" => 40}})
如果不带参数,remove
方法会删除所有的文档:
@users.remove
在上一章里我们说过remove
实际上并不会删除集合,要删除集合及其索引,可以使用drop_collection
方法:
connection = Mongo::Connection.newdb = connection[/'tutorial/']db.drop_collection(/'users/')
3.1.5 数据库命令
在上一章里我们已经见到过数据库命令了,并看了两个stats
命令。此处,我们将了解如何在驱动中运行命令,例子就是listDatabases
命令,这是几个必须在admin
数据库上运行的命令之一,在开启身份验证的时候还做了特殊处理。关于身份验证与admin
数据库的详细内容,请阅读第10章。
首先,实例化一个Ruby数据库对象指向admin
数据库。然后将命令的查询说明(query specification)传给command
命令:
@admin_db = @con[/'admin/']@admin_db.command({/"listDatabases/" => 1}
执行的响应是一个Ruby散列,罗列了所有存在的数据库和其在磁盘上的大小:
{ /"databases/" => [ { /"name/" => /"tutorial/", /"sizeOnDisk/" => 218103808, /"empty/" => false }, { /"name/" => /"admin/", /"sizeOnDisk/" => 1, /"empty/" => true }, { /"name/" => /"local/", /"sizeOnDisk/" => 1, /"empty/" => true } ], /"totalSize/" => 218103808, /"ok/" => true}
一旦习惯了使用Ruby散列来表示文档,几乎就可以无缝地从Shell API过渡过来。如果你还是对通过Ruby使用MongoDB感到不安,请不用担心,3.3节将带你进行更多的练习。但现在我们要稍作停顿,了解一下MongoDB驱动是如何工作的,这能让人更多地了解MongoDB的设计,以便能更有效地使用驱动。