一般在想到驅動時,映入腦海的都是低級的位操作和遲鈍的接口。感謝上帝,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.0 Successfully installed mongo-1.4.0 2 gems installed Installing 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.rb irb(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.new db = 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的設計,以便能更有效地使用驅動。