MongoDB與關係型數據庫的建模仍是有許多不一樣,由於MongoDB支持內嵌對象和數組類型。MongoDB建模有兩種方式,一種是內嵌(Embed),另外一種是鏈接(Link)。那麼什麼時候Embed什麼時候Link呢?那得看兩個實體之間的關係是什麼類型。java
一對一的關係:Embed,好比用戶信息集合有Address字段,Address字段有省、市、縣三個字段。sql
在關係型數據庫中,經過鏈接運算符能夠實現多個表聯合查詢。而非關係型數據庫的特色是表之間屬於弱關聯,Mongodb做爲Nosql表明,其自己特性不建議對多Collection關聯處理,不過對於有些須要對多表關聯處理的需求,Mongodb也能夠實現。主要分爲幾種方式:簡單手工關聯和DBRef方式關聯、esProcmongodb
1.簡單手工關聯shell
下圖表示帖子和用戶兩個Collection的ER圖:數據庫
首先將authors集合中的用戶對象查詢出來,放在一個變量author中,代碼以下:編程
> author=db.authors.findOne({name:"chenzhou"}) { "_id" : ObjectId("5030ba7621bdee44765b2147"), "name" : "chenzhou", "email" : "chenzhou1025@126.com" }
經過用戶對象author來獲取帖子列表,代碼以下:json
> for(var post=db.posts.find({"author_name":author.name}); post.hasNext();){ ... printjson(post.next().title); ... } "Hello Mongodb" "Hello World" "Hello My Friend"
2.DBRef方式關聯數組
{ $ref : <value>, $id : <value>, $db : <value> }
$ref:集合名稱;$id:引用的id;$db:數據庫名稱,可選參數。
能夠看到DBRef的結構比Manual References的複雜,佔用的空間大,可是功能也強大,若是要跨數據庫鏈接,上面講的評論集合的例子,都得須要使用DBRef,MongoDB提供了函數來解析DBRef,不用像Manual References須要本身手動寫兩次查詢。
DBRef就是在兩個Collection之間定義的一個關聯關係,好比,把CollectionB "_id"列的值存在CollectionA的一個列中,而後經過CollectionA這個列中所存的值在CollectionB中找到相應的記錄。app
示例:模擬用戶發帖的過程,看一看如何將帖子表和用戶表創建關聯。框架
步驟1:取得當前用戶信息,代碼以下:
> author=db.authors.find({name:"chenzhou"})[0] { "_id" : ObjectId("5030ba7621bdee44765b2147"), "name" : "chenzhou", "email" : "chenzhou1025@126.com" }
步驟2:發帖子並作關聯,代碼以下:
> db.posts.insert({"title":"Hello Mongodb DBRef1", ... authors:[new DBRef('authors',author._id)]}) > db.posts.insert({"title":"Hello Mongodb DBRef2", ... authors:[new DBRef('authors',author._id)]}) >
步驟3:通知帖子查找用戶信息,代碼以下:
> db.posts.find({"title":"Hello Mongodb DBRef1"})[0].authors[0].fetch() { "_id" : ObjectId("5030ba7621bdee44765b2147"), "name" : "chenzhou", "email" : "chenzhou1025@126.com" }
經過這個例子能夠看出,DBRef就是從文檔的一個屬性指向另外一個文檔的指針。
關於DBRef詳細信息,能夠參見官網說明:http://docs.mongodb.org/manual/applications/database-references/
$lookup
咱們來看mongodb另外一個很是有意思的東西,那就是$lookup,咱們知道mongodb是一個文檔型的數據庫,並且它也是最像關係型數據庫的
一種nosql,可是呢,既然mongodb是無模式的,天然就很難在關係型數據庫中很是擅長的多表關聯上發揮做用,在這以前,咱們可使用DbRef,但
是呢,在mongodb 3.2 中給你增長了一個至關牛逼的手段,那就是$lookup,並且放到了aggreation這種重量級的pipeline分析框架上,天然就是一等
公民了,牛逼哈~。
$lookup:
db.product.insert({"_id":1,"productname":"商品1","price":15}) db.product.insert({"_id":2,"productname":"商品2","price":36}) db.orders.insert({"_id":1,"pid":1,"ordername":"訂單1"}) db.orders.insert({"_id":2,"pid":2,"ordername":"訂單2"}) db.orders.insert({"_id":3,"pid":2,"ordername":"訂單3"}) db.orders.insert({"_id":4,"pid":1,"ordername":"訂單4"}) db.product.find() db.orders.find()
語法:
db.product.aggregate([ { $lookup: { from: "orders", localField: "_id", foreignField: "pid", as: "inventory_docs" } } ])
而後展現的結果以下:
1 /* 1 */ 2 { 3 "_id" : 1.0, 4 "productname" : "商品1", 5 "price" : 15.0, 6 "inventory_docs" : [ 7 { 8 "_id" : 1.0, 9 "pid" : 1.0, 10 "ordername" : "訂單1" 11 }, 12 { 13 "_id" : 4.0, 14 "pid" : 1.0, 15 "ordername" : "訂單4" 16 } 17 ] 18 } 19 20 /* 2 */ 21 { 22 "_id" : 2.0, 23 "productname" : "商品2", 24 "price" : 36.0, 25 "inventory_docs" : [ 26 { 27 "_id" : 2.0, 28 "pid" : 2.0, 29 "ordername" : "訂單2" 30 }, 31 { 32 "_id" : 3.0, 33 "pid" : 2.0, 34 "ordername" : "訂單3" 35 } 36 ] 37 }
下面我簡單介紹一些$lookup中的參數:
from:須要關聯的表【orders】
localField: 【product】表須要關聯的鍵。
foreignField:【orders】的matching key。
as: 對應的外鍵集合的數據,【由於多是一對多的,對吧】
MongoDB不支持join,其官網上推薦的unity jdbc能夠把數據取出來進行二次計算實現join運算,但收費版纔有這個功能。其餘免費的jdbc drive只能支持最基本的SQL語句,不支持join。若是用Java等編程語言將數據取出後實現join計算,也比較複雜。
三、esProc
用免費的esProc配合MongoDB,能夠實現join計算。這裏經過一個例子來講明一下具體做法。
MongoDB中的文檔orders保存了訂單數據,employee保存了員工數據。以下:
MongoDB shell version: 2.6.4
connecting to: test
> db.orders.find();
{ 「_id」 : ObjectId(「5434f88dd00ab5276493e270″), 「ORDERID」 : 1, 「CLIENT」 : 「UJRNP
」, 「SELLERID」 : 17, 「AMOUNT」 : 392, 「ORDERDATE」 : 「2008/11/2 15:28″ }
{ 「_id」 : ObjectId(「5434f88dd00ab5276493e271″), 「ORDERID」 : 2, 「CLIENT」 : 「SJCH」
, 「SELLERID」 : 6, 「AMOUNT」 : 4802, 「ORDERDATE」 : 「2008/11/9 15:28″ }
{ 「_id」 : ObjectId(「5434f88dd00ab5276493e272″), 「ORDERID」 : 3, 「CLIENT」 : 「UJRNP
」, 「SELLERID」 : 16, 「AMOUNT」 : 13500, 「ORDERDATE」 : 「2008/11/5 15:28″ }
{ 「_id」 : ObjectId(「5434f88dd00ab5276493e273″), 「ORDERID」 : 4, 「CLIENT」 : 「PWQ」,
」SELLERID」 : 9, 「AMOUNT」 : 26100, 「ORDERDATE」 : 「2008/11/8 15:28″ }
…
> db.employee.find();
{ 「_id」 : ObjectId(「5437413513bdf2a4048f3480″), 「EID」 : 1, 「NAME」 : 「Rebecca」, 」
SURNAME」 : 「Moore」, 「GENDER」 : 「F」, 「STATE」 : 「California」, 「BIRTHDAY」 : 「1974-1
1-20″, 「HIREDATE」 : 「2005-03-11″, 「DEPT」 : 「R&D」, 「SALARY」 : 7000 }
{ 「_id」 : ObjectId(「5437413513bdf2a4048f3481″), 「EID」 : 2, 「NAME」 : 「Ashley」, 「S
URNAME」 : 「Wilson」, 「GENDER」 : 「F」, 「STATE」 : 「New York」, 「BIRTHDAY」 : 「1980-07-
19″, 「HIREDATE」 : 「2008-03-16″, 「DEPT」 : 「Finance」, 「SALARY」 : 11000 }
{ 「_id」 : ObjectId(「5437413513bdf2a4048f3482″), 「EID」 : 3, 「NAME」 : 「Rachel」, 「S
URNAME」 : 「Johnson」, 「GENDER」 : 「F」, 「STATE」 : 「New Mexico」, 「BIRTHDAY」 : 「1970-
12-17″, 「HIREDATE」 : 「2010-12-01″, 「DEPT」 : 「Sales」, 「SALARY」 : 9000 }
…
Orders中的sellerid對應employee中的eid。須要查詢出employee的state屬性等於California的全部訂單信息。其中orders數據量較大,不能一次裝入內存。Employee數據量較小,Orders過濾以後的結果數據量也比較小。
查詢條件表達式能夠做爲參數傳遞給esProc,以下圖:
A1: 鏈接MongoDB數據庫,ip和端口號是localhost:27017,數據庫是test,用戶名和密碼都是test。
A2: 使用find函數從MongoDB中取數,造成遊標。集合是orders,過濾條件是空,指定鍵_id不取出。esProc在find函數中採用了和mongdb的find語句同樣的參數格式。esProc的遊標支持分批讀取和處理數據,能夠避免數據量過大,內存出現溢出的狀況。
A3: 取得employee中的數據。由於數據量不大,因此用fetch函數一次取出。
A4: 使用switch函數,將遊標A2中SELLERID字段的值,轉換爲A3(employee)中的記錄引用。
A5: 按照條件過濾。這裏使用宏來實現動態解析表達式,其中的where就是傳入參數。集算器將先計算${…}裏的表達式,將計算結果做爲宏字符串值替換${…}以後解釋執行。這個例子中最終執行的是:=A4.select(SELLERID.STATE==」California」)。因爲SELLERID已經轉化爲employee的對應記錄的引用,因此能夠直接寫SELLERID.STATE。過濾以後的結果數據量較小,因此一次取出。若是結果數據量仍然比較大的話,能夠分批取出,好比每次取出10000條:fetch(10000)。
A6:將過濾結果中的SELLERID從新切換爲普通值。
A6的計算結果是: 過濾條件發生變化時不用改變程序,只需改變where參數便可。例如,條件變爲:state等於California的訂單,或者CLIENT等於PWQ的訂單。Where的參數值能夠寫爲:CLIENT==」PWQ」|| SELLERID.STATE==」California」。 esProc並不包含MongoDB的java驅動包。用esProc來訪問MongoDB,必須提早將MongoDB的java驅動包(esProc要求2.12.2或以上版本的驅動,mongo-java-driver-2.12.2.jar)放到[esProc安裝目錄]\common\jdbc中。 esProc協助MongoDB計算的腳本很容易集成到java中,只要增長一行A7,寫成result A6便可向java輸出resultset形式的結果,具體的代碼請參考esProc教程。一樣,用java調用esProc訪問MongoDB也必須將mongdb的java驅動包放到java程序的classpath中。