1. 前言
在上一篇文章中,我們介紹了MongoDB。現在,我們來看下如何在MongoDB中進行常規的CRUD操作。畢竟,作為一個存儲系統,它的基本功能就是對數據進行增刪改查操作。
MongoDB中的增刪改查操作,不同於我們熟悉的關系數據庫中的操作。在關系數據庫中,比如MySQL,我們通常使用SQL語句對數據庫進行增(INSERT)刪(DELETE)改(UPDATE)查(SELECT)。MongoDB在對數據進行操作過程中,使用的是Document進行數據操作。在對數據庫進行操作的時候,使用Document來表示需要查詢的條件和需要更新的數據,功能類似於關系數據庫中的SQL語句。接下來,我們來看下在MongoDB中是如何通過Document來進行CRUD操作的。
注意:下面介紹的CRUD操作,是在MongoDB的mongo shell操作的。mongo shell是一個JavaScript交互環境,是MongoDB提供的一個客戶端。不同的語言有自己對應的驅動和對應的操作API,但是原理是類似的。相信通過在mongo shell中進行CRUD操作的介紹,大家也可以舉一反三,在不同的語言中操作MongoDB。
2. INSERT操作
MongoDB中的新增操作,把一個新的Document插入到一個Collection中。
如果該Collection不存在,則新增一個新的Collection。這個和關系數據庫有很大的區別,在關系數據庫中,我們需要定義數據庫的schema和表結構,這是NoSQL(MongoDB是NoSQL的一種)數據庫和關系數據庫很大的區別。在MongoDB中,我們可以在一個Collection中包含多個不同結構的Document(不推薦這樣做,一個Collection最好具有相同格式的Document,便於維護和使用)。
關於"_id"字段,當我們新增一個Document到Collection中的時候,MongoDB需要每一個新增的Document中有一個"_id"字段,MongoDB把這個字段作為主鍵,所以要求這個字段在Collection中是唯一的。如果新增的Document中沒有包含"_id"字段,那么MongoDB的客戶端會在該Document中新增一個值為ObjectId類型的"_id" 字段;如果MongoDB服務在新增Document的時候發現Document中沒有"_id"字段,那么mongod會新增一個值為ObjectId類型的"_id"字段到該Document中。
MongoDB對單個Document的寫操作是原子的。
MongoDB提供了如下的方式來進行新增操作:
- db.collection.insert()
- db.collection.insertOne() 3.2版本新增
- db.collection.insertMany() 3.2版本新增
2.1 db.collection.insert()
insert操作可以新增單個Document,也可以新增多個Document。如果新增單個Document,則把需要新增的Document作為參數傳遞給insert(),如果新增多個Document,則將多個Document的數組作為參數傳遞個insert()函數。
比如我們需要在"post"這個collection上新增一個Document來表示我們的博客中新增了一篇文章,我們可以這么做:
現在,我們給post這個Collection新增了一個Document,表示在Blog中新增了一篇文章。我們可以看下現在post這個Collection中是不是有我們新增的Document。
這里我們使用findOne()來查詢,我們可以看到,我們由於沒有在Document中包含"_id"字段,所以MongoDB自動為我們新增了一個"_id"字段。
insert()函數可以一次新增多個Document,只要將一個Document的數組傳遞給insert()就可以了,如:
2.2 db.collection.insertOne()
insertOne()函數是在3.2版本中新增的,它用來添加單個Document。例子如下:
2.3 db.collection.insertMany()
insertMany()函數是insert的批量增加的版本,支持一次新增多個Document,它也是3.2版本中新增的函數:
好了,我們簡單介紹了下insert操作的三個函數,下面,我們已經在MongoDB的數據庫里新增了幾個Document了。接下來,是時候開始學習查找操作來查看這些已經存儲在MongoDB的記錄了。
3. QUERY操作
MongoDB提供了db.collection.find()函數來執行查詢操作,函數將返回一個游標(cursor),用於遍歷查詢到的Documents。find()函數接受兩個參數,一個是過濾條件,還有一個是投影。
db.collection.find( <query filter>, <projection> )
- <query filter>用於查找滿足過濾條件的Document
- <projection>(投影)用於指定被找到的Document中需要返回哪些字段,用於限制網絡中傳輸的數據的大小。投影的概念和關系數據庫中的投影的概念基本是一致的。
現在,我們可以查詢下剛才我們新增的所有的Documents,通過find(),我們來看下如何查詢:
如果我們沒有傳遞任何參數,或者傳遞一個"{}"給find()函數,那會find()返回Collection中所有的Document。現在,可以看到我們剛才新增的所有的Documents。
接下來,我們通過指定條件,查詢標題為"Third Post"的Document,我們可以這樣做:
發現了么,我們的查詢條件其實就是以Document方式構造的。在MongoDB中,我們可以通過構造不同的Document來定義不同的查詢條件。
除了使用具體的字段來過濾,我們還可以使用查詢操作符來做更加靈活的操作。我們現在需要查找出標題是"Third Post"或"Fifth Post"這兩篇Post中的任何一篇,我們可以使用$in操作符來查詢:
除了使用$in,MongoDB還提供了很多有用的操作符來幫助構造過濾條件,如 $lt, $gt, $and, $or等。
3.1 使用子Document中值作為過濾條件
現在,我們假設有一篇文章,在Document中的存儲如下:
現在,我們需要匹配內部Document中的條件,比如我們需要查找author中name是Duke的記錄,我們可以這樣構造我們的篩選條件:
db.post.findOne( { "author": { "name": "Duke", "email": "740313507@qq.com" } })
查詢結果就是這樣的
我們觀察這個過濾條件,發現和上面的過濾條件的結構一樣,就是{"field":"value"}的格式,只是不同的是,這次的"value"不再是一個普通的值了,而是一個Document。通過這種方式來過濾,具有一些局限性,我們如果要查找author中name是Duke的記錄,我們必須完整指定author的值,也就是需要完整的Document。如果需要只過濾name,而不關系email的值,我們可以這樣構造過濾條件:
db.post.findOne({"author.name": "Duke"})
查詢到和上面一樣的結果
但是,這次我們使用了"author.name"的方式來指定field。這種類似於成員引用的"點符號"結構的查詢,可以用來對內嵌的Document中的指定的field進行過濾。后面你將會看到,對於數組類型的值,也可以通過類似的方式來構造過濾條件。
3.2 使用數組中的元素作為過濾條件
接下來我們看下如何使用數組中的值來構造過濾條件。最簡單的,也是最容易想到的,就是把整個數組作為過濾條件,類似於上面的把整個子Document作為過濾條件一樣,我們可以這樣構造
db.post.findOne({"comments": ["comment one", "comment two"]})
查詢的結果如下:
但是,這種方式沒法指定數組中的某個值作為過濾條件。如果要使用數組中的某個值作為過濾條件,我們可以這么構造過濾條件:
db.post.find({"comments": "comment two"})
查詢到的結果如下:
這里最后使用了"pretty()"方法來格式化輸出,輸出格式化的數據,便於觀察,僅此而已,沒有別的用途。我們可以看到,這種方式構造的過濾條件,使用了數組中的一個元素來篩選記錄,只要數組中包含了這個元素(不管這個元素的下標),那么就會過濾出這條記錄,有點像集合中的in操作。這種方式可以匹配數組中的一個元素。
接下來,我們更近一步,我們需要匹配數組的某一個下標位置的元素,那么我們需要使用上面一開始提到的,使用類似匹配子Document的那種成員引用(點符號)方式來構造過濾條件:
db.post.find({"comments.0": "comment two"})
查詢到的結果如下:
這里,我們使用{"comments.0": "comment two"}的方式指定匹配的條件是:數組"comments"下標為0的位置的值為"comment two"。這樣,就可以過濾掉之前下標為1的位置為"comment two"的記錄了。可以看出,在使用數組下標構造過濾條件的時候,下標是從0開始的。
MongoDB提供了豐富的操作符來支持構造靈活的過濾條件,這里就先介紹這么點。由於篇幅關系,這里就不再展開了,下次獨立寫篇文章重點介紹下MongoDB中的查詢操作。接下來,我們該看下如何在MongoDB中進行更新操作。
4. UPDATE操作
MongoDB也支持基本的更新操作,它提供了4個用於更新的方法:
- db.collection.updateOne() 3.2版本新增
- db.collection.updateMany() 3.2版本新增
- db.collection.update()
- db.collection.replaceOne() 3.2版本新增
這些Update方法支持三個參數:
- 用於篩選記錄的過濾條件,過濾出需要被更新的記錄,過濾條件和query中使用的過濾條件類似。
- 一個新的Document,用於更新部分值或者替換除了"_id"之外的一整個Document。
- 一個以Document格式組織的一組更新選項。
MongoDB對於單個Document的更新操作是原子的。 MongoDB在更新時對於"_id"主鍵的處理原則是,不管是更新還是替換Document,都不能更改被更新的Document的"_id"主鍵,如果在替換的時候包含了不同的"_id",那么替換會失敗,如:
上面的例子中,我們修改了原先的"_id"值為1,然后進行替換更新,發現更新失敗,提示"_id"值不能被更改。
當我們更新的時候,新的Document的大小超過了原先舊的Document的大小的時候,更新操作會重新申請一塊更大的空間來存放這個新的Document。
接下來,我們來看下這些更新API的用法。
4.1 db.collection.updateOne()
updateOne()函數是在3.2版本中新增的一個API,用於更新一條匹配到的Document。現在我們需要更新我們的Post集合中的標題為"First Post"的Document,我們想增加一些評論,我們可以這么構造我們的更新表達式:
db.post.updateOne({"title": "First Post"}, {"$set": {"comments": ["comment one"]}})
現在,我們的"First Post"這篇文章就有了一條評論了。這里我們使用"$set"操作符來設置一個新的字段,MongoDB的更新操作提供了一些有用的操作符來幫助構造更新語句。updateOne()函數會更新第一個被匹配到的Document。如果要更新多個Document,我們可以用updateMany()函數來支持。
4.2 db.collection.updateMany()
updateMany()函數可以對匹配到的所有的Document進行更新操作。比如我們想更新所有的文章,讓每篇文章都有一個瀏覽數的字段,表示別瀏覽的次數。我們可以這樣做:
db.post.updateMany({}, {"$set": {"view": 0}})
我們可以看到,通過updateMany()函數,我們更新了所有的Document,使得每個Document都包含了一個"view"字段,初始值為0。
4.3 替換Document
上面提到了兩個API,都是對Document中的某個字段進行更新。它們除了可以對Document中的單個字段進行更新外,還可以對匹配到的整個Document進行替換(除了"_id"屬性外,可以替換任何屬性)。只要把更新參數改成一個普通的Document(Document的結構中不包含操作符),就可以對匹配到的Document替換成參數中的Document。進行Document替換更新的時候,需要注意:原先的Document中的"_id"屬性是不能被更改的,所以新的用於替換的Document不能包含"_id"屬性,如果包含了"_id"屬性,那么這個"_id"屬性必須是和被更新的Document的"_id"屬性是相同的。
除了上面的兩個API可以用於替換Document,MongoDB在3.2版本中新增了一個replaceOne()函數來進行替換操作。現在我們用replaceOne()來替換一個Document,我們把"title"是"First Post"的Document替換成新的Document:
db.post.replaceOne({"title": "First Post"}, {"title": "New Post", "content": "new content", "create_time": new Date()})
現在,我們已經把"title"為"First Post"的Document替換為了新的"title"為"New Post"的Document了。
最后,我們來看下上面三個API的集大成者,就是update()函數,它基本包含了上面提到的三個API的所有功能,默認情況下,update()函數會更新匹配到的第一個Document,如果設置了"multi"選項,那么就可以更新匹配到的所有的Document。
下面我們使用update()來更新匹配到的第一個Document:
db.post.update({"title": "New Post"}, {"$set": {"view": 0}})
好了,更新操作介紹到這里,接下來就是最后一個刪除操作了。
5. DELETE操作
到這里,我想大家都已經了解了MongoDB中的"增","改","查"的功能了,接下來我們來看下"刪"這個功能。MongoDB提供了三個用於刪除操作的API,分別是:
- db.collection.deleteOne()
- db.collection.deleteMany() 3.2版本新增
- db.collection.remove() 3.2版本新增
這三個API都支持一個過濾條件參數,用於匹配到滿足條件的Document,然后進行刪除操作。
從三個API的字面意思我們可以看出,deleteOne()會刪除匹配到的所有的Document中的第一個,而deleteMany()和remove()會刪除所有匹配到的Document。
假設我們需要刪除"title"為"New Post"的Document,我們可以用deleteOne()來操作
db.post.deleteOne({"title": "New Post"})
當刪除了"title"為"New Post"的Document以后,我們再次去查詢的時候,發現這個Document確實已經被刪除了。deleteOne()用於刪除單個Document,如果我們需要刪除所有滿足過濾條件的Document的話,我們可以用deleteMany()或者remove()來實現。
現在我們想刪除所有瀏覽數為0的文章,那么我們可以用deleteMany()或者remove來實現:
db.post.remove({"view": 0})
由於我們存儲在集合中的所有Document的view值都是0,所以上面的操作相當於我們清空了我們的Collection。
6. 總結
好了,到這里,我們已經簡單介紹了MongoDB中相關的CRUD操作了。相信大家對MongoDB的基本操作有了一些簡單的認識了。
差不多就先寫到這里了,由於文章的主題就是跟大家介紹MongoDB的CRUD操作,所以上面很多的細節部分沒有展開,感興趣的同學可以去翻閱MongoDB的文檔來了解,有機會,下次單獨拎出來好好總結下。畢竟,我們學東西,得先有個大概的了解以后,才可以有針對的深入,一味的追求細節,往往會迷失方向。