幾十年來,關係數據庫一直主導着數據管理,但它們最近已經失去了NoSQL的替代品。雖然NoSQL數據存儲不適合每一個用例,但它們一般更適合html
本文介紹Neo4j,它是用於與java
顧名思義,圖形數據庫擅長表示數據圖形。這對社交軟件特別有用,每次與某人聯繫時,大家之間就會創建關係。可能在你上次求職時,你選擇了一些你感興趣的公司,而後搜索你的社交網絡以獲取與他們的聯繫。雖然你可能不知道有那些人爲這些公司工做,但你的社交網絡中的某些人可能會這樣作。很容易在一個或兩個分離度(你的朋友或朋友的朋友)內解決這樣的問題,但當你開始在網絡中擴展搜索時會發生什麼?node
在他們的書中,Neo4j In Action,Aleksa Vukotic和Nicki Watt探討了關係數據庫和圖形數據庫之間的差別,以解決社交網絡問題。爲了向你展現爲何圖形數據庫正成爲關係數據庫日益流行的替代方案,我將在接下來的幾個示例中使用它們的工做。docker
從計算機科學的角度來看,當咱們考慮在社交網絡中建模用戶之間的關係時,咱們可能會繪製如圖1所示的圖形。數據庫
用戶與其餘用戶有IS_FRIEND_OF
關係,這些用戶與其餘用戶也有IS_FRIEND_OF
關係,等等。圖2顯示了咱們如何在關係數據庫中表示這一點。編程
USER
表與USER_FRIEND
表具備一對多的關係,USER_FRIEND
表模擬兩個用戶之間的「朋友」關係。如今咱們已經創建了關係模型,咱們將如何查詢數據?Vukotic和Watt測量了查詢性能,用於計算出五個級別深度的不一樣朋友的數量(朋友的朋友的朋友的朋友)。在關係數據庫中,查詢看起來以下:瀏覽器
# Depth 1
select count(distinct uf.*) from user_friend uf where uf.user_1 = ?
# Depth 2
select count(distinct uf2.*) from user_friend uf1
inner join user_friend uf2 on uf1.user_1 = uf2.user_2
where uf1.user_1 = ?
# Depth 3
select count(distinct uf3.*) from t_user_friend uf1
inner join t_user_friend uf2 on uf1.user_1 = uf2.user_2
inner join t_user_friend uf3 on uf2.user_1 = uf3.user_2
where uf1.user_1 = ?
# And so on...
複製代碼
這些查詢的有趣之處在於,每次咱們再出一個級別時,咱們都須要本身加入USER_FRIEND
表格。表1顯示了研究人員Vukotic和Watt在插入1,000個用戶時發現了什麼,每一個用戶大約有50個關係(50,000個關係)並運行查詢。bash
深度執行時間(秒)計數結果網絡
2 0.028〜900架構
3 0.213〜999
4 10.273〜999
5 92.613〜999
MySQL能夠很好地將數據鏈接到三個級別,但以後性能會迅速降低。緣由是每次USER_FRIEND
表與自身鏈接時,MySQL必須計算表的笛卡爾積,即便大部分數據將被丟棄。例如,當執行該鏈接五次時,笛卡爾積產生50,000 ^ 5行,或102.4 * 10 ^ 21行。當咱們只對其中的1000個感興趣時,這是一種浪費!
接下來,Vukotic和Watt嘗試對Neo4j執行相同類型的查詢。這些徹底不一樣的結果如表2所示。
深度執行時間(秒)計數結果
2 0.04〜900
3 0.06〜999
4 0.07〜999
5 0.07〜999
從這些執行比較中得出的結論
Neo4j比MySQL更好。相反,當遍歷這些類型的關係時,Neo4j的性能取決於檢索的記錄數,而MySQL的性能取決於USER_FRIEND
表中的記錄數。所以,隨着關係數量的增長,MySQL查詢的響應時間也會增長,而Neo4j查詢的響應時間將保持不變。這是由於Neo4j的響應時間取決於特定查詢的關係數,而不取決於關係總數。
將這個思想項目進一步擴展,Vukotic和Watt接下來建立了一百萬用戶,他們之間有5000萬個關係。表3顯示了該數據集的結果。
深度執行時間(秒)計數結果
2 0.01〜2500
3 0.168〜11萬
4 1.359〜60萬
5 2.132〜80萬
毋庸置疑,我很是感謝Aleksa Vukotic和Nicki Watt,並強烈建議您查看他們的做品。我從本書的第一章
提取了本節中的全部測試。
您已經看到Neo4j可以很是快速地執行大量高度相關的數據,毫無疑問,它比MySQL(或任何關係數據庫)更適合某些類型的問題。若是您想了解有關Neo4j如何工做的更多信息,最簡單的方法是經過Web控制檯與其進行交互。
首先下載Neo4j。對於本文,您將須要Community Edition,在撰寫本文時版本爲3.2.3。
安裝Neo4j後,啓動它並打開瀏覽器窗口到如下URL:
http://127.0.0.1:7474/browser/複製代碼
使用默認用戶名neo4j
和默認密碼登陸neo4j
。您應該看到相似於圖3的屏幕。
Neo4j是圍繞節點和關係的概念設計的:
舉個例子,咱們能夠定義像鋼鐵俠和美國隊長這樣的角色節點; 定義一個名爲「復仇者」的電影節點; 而後定義APPEARS_IN
爲鋼鐵俠和復仇者之間以及美國隊長和復仇者之間的關係。全部這些都顯示在圖4中。
圖4顯示了三個節點(兩個Character節點和一個Movie節點)和兩個關係(兩種類型APPEARS_IN
)。
與關係數據庫如何使用結構化查詢語言(SQL)與數據交互相似,Neo4j使用Cypher查詢語言與節點和關係進行交互。
讓咱們使用Cypher建立一個簡單的家庭表示。在Web界面的頂部,查找美圓符號。這表示容許您直接對Neo4j執行Cypher查詢的字段。在該字段中輸入如下Cypher查詢(我以個人家人爲例,但若是您願意,能夠隨意更改細節以建模您本身的家庭):
CREATE (person:Person {name: "Steven", age: 45}) RETURN person複製代碼
結果如圖5所示。
在圖5中,您能夠看到一個標記爲Person且名稱爲Steven的新節點。若是將鼠標懸停在Web控制檯中的節點上,您將在底部看到其屬性。在這種狀況下,屬性是ID:19,名稱:Steven,年齡:45。如今讓咱們分解Cypher查詢:
CREATE
關鍵字用於建立節點和關係。在這種狀況下,咱們傳遞一個參數,它Person
括在括號中,所以它意味着建立一個單獨的節點。person
」是一個變量名稱,經過它咱們能夠訪問正在建立的人,而大寫「 Person
」是標籤。請注意,冒號將變量名稱與標籤分開。該CREATE
命令(不區分大小寫)用於建立節點,能夠按以下方式讀取:
。
接下來咱們想嘗試一下Cypher的查詢。首先,咱們須要建立更多人,以便咱們能夠定義它們之間的關係。
CREATE (person:Person {name: "Michael", age: 16}) RETURN person
CREATE (person:Person {name: "Rebecca", age: 7}) RETURN person
CREATE (person:Person {name: "Linda"}) RETURN person
複製代碼
建立四我的後,您能夠單擊節點標籤下的「 人員」按鈕(若是單擊網頁左上角的數據庫圖標,則可見)或執行如下Cypher查詢:
MATCH (person: Person) RETURN person複製代碼
Cypher使用MATCH
關鍵字在Neo4j中查找內容。在此示例中,咱們要求Cypher匹配全部標記爲Person的節點,將這些節點分配給
變量,並返回與該變量關聯的值。所以,你應該看到您建立的四個節點。若是將鼠標懸停在Web控制檯中的每一個節點上,你將看到每一個人的屬性。(你可能會注意到我將我妻子的年齡排除在她的節點以外,說明屬性不須要在節點之間保持一致,即便是相同的標籤。我也不會愚蠢地公佈我妻子的年齡。)
咱們能夠經過MATCH
向咱們想要返回的節點添加條件來進一步擴展此示例。例如,若是咱們只想要「Steven」節點,咱們能夠經過匹配name屬性來檢索它:
MATCH (person: Person {name: "Steven"}) RETURN person複製代碼
或者,若是咱們想要歸還全部孩子,咱們能夠要求全部18歲如下的人:
MATCH (person: Person) WHERE person.age < 18 RETURN person複製代碼
在此示例中,咱們WHERE
在查詢中添加了子句以縮小結果範圍。WHERE
與其SQL等價物很是類似:MATCH (person: Person)
查找具備Person標籤的全部節點,而後該WHERE
子句過濾結果集中的值。
咱們有四個節點,因此讓咱們建立一些關係。首先,讓咱們建立史蒂文和琳達之間的IS_MARRIED_TO
關係:
MATCH (steven:Person {name: "Steven"}), (linda:Person {name: "Linda"}) CREATE (steven)-[:IS_MARRIED_TO]->(linda) return steven, linda複製代碼
在這個例子中,咱們匹配標記爲Steven和Linda的兩個Person節點,而且咱們建立了一個從Steven到Linda 的IS_MARRIED_TO
類型關係。建立關係的格式以下:
(node1)-[relationshipVariable:RELATIONSHIP_TYPE->(node2)複製代碼
這relationshipVariable
是可選的,但若是您但願可以在RETURN語句(或WHERE子句)中訪問它,則須要它。箭頭()-[]->()
表示Cypher要求的關係方向。若是你想表達Linda與Steven結婚,那麼你能夠按照如下方式在另外一個方向寫下這段關係:()<-[]-()
。若是你想建立一個雙向關係,代表Linda和Steve彼此結婚,那麼你須要建立兩個獨立的關係。雖然Cypher要求您定義關係的方向,但您可使用方向查詢,也能夠不使用方向查詢。
如下查詢查找此係列中已結婚的全部人(請注意查詢中缺乏任何方向):
MATCH (p1:Person)-[:IS_MARRIED_TO]-(p2:Person) RETURN p1, p2複製代碼
結果如圖6所示。
如今讓咱們建立一些關係:
MATCH (michael:Person {name: "Michael"}), (rebecca:Person {name: "Rebecca"}) CREATE (michael)-[:IS_SIBLILNG]->(rebecca) return michael, rebecca
MATCH (steven:Person {name: "Steven"}), (michael:Person {name: "Michael"}) CREATE (steven)-[:HAS_CHILD]->(michael) return steven, michael
MATCH (steven:Person {name: "Steven"}), (rebecca:Person {name: "Rebecca"}) CREATE (steven)-[:HAS_CHILD]->(rebecca) return steven, rebecca
MATCH (linda:Person {name: "Linda"}), (michael:Person {name: "Michael"}) CREATE (linda)-[:HAS_CHILD]->(michael) return linda, michael
MATCH (linda:Person {name: "Linda"}), (rebecca:Person {name: "Rebecca"}) CREATE (linda)-[:HAS_CHILD]->(rebecca) return linda, rebecca
複製代碼
咱們如今能夠經過如下查詢查看全部人及其關係:
MATCH (p:Person) RETURN p複製代碼
結果如圖7所示。
要真正探索圖數據庫的力量,咱們須要擴展咱們的社交圖。首先,讓咱們添加一些FRIEND
關係:
MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(charlie:Person {name: "Charlie", age: 16}) RETURN michael, charlie
MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(koby:Person {name: "Koby"}) RETURN michael, koby
MATCH (michael:Person {name: "Michael"}) CREATE (michael)-[:FRIEND]->(grant:Person {name: "Grant"}) RETURN michael, grant
MATCH (rebecca:Person {name: "Rebecca"}) CREATE (rebecca)-[:FRIEND]->(jordyn:Person {name: "Jordyn"}) RETURN rebecca, jordyn
MATCH (rebecca:Person {name: "Rebecca"}) CREATE (rebecca)-[:FRIEND]->(katie:Person {name: "Katie"}) RETURN rebecca, katie
複製代碼
關於這些關係的一些有趣的事情是朋友節點與FRIEND
關係同時建立。例如,執行第一個語句時,「Charlie」Person節點不存在,但該語句建立了從現有「Michael」Person節點到名爲「Charlie」的新Person節點的FRIEND
關係。您能夠拉出全部Person節點並驗證節點是否已建立,如圖8所示。
咱們已經啓動了一個很是好的社交圖,因此讓咱們嘗試編寫一個更復雜的查詢來查找我孩子的全部朋友:
MATCH (steven:Person {name:"Steven"})-[:HAS_CHILD]-(:Person)-[:FRIEND]-(friend:Person) RETURN friend複製代碼
結果如圖9所示。
在此查詢中,咱們從名爲「Steven」的HAS_CHILD
Person節點開始,遍歷全部與Person節點的FRIEND
關係,遍歷全部Person節點的關係,並返回朋友列表。咱們能夠包含方向關係,但省略箭頭可讓咱們遍歷兩個方向。
除了定義兩個節點之間的關係以外,關係自己能夠具備鍵/值對。例如,咱們可能決定建立Movie節點,而後HAS_SEEN
在他們看到的人和電影之間建立關係。在這些HAS_SEEN
關係中,咱們還能夠添加「評級」屬性。下面的代碼建立一個標題爲Avengers的電影,而後HAS_SEEN
在Michael和電影復仇者之間建立一個關係,評級爲5。
CREATE (movie:Movie {title:"Avengers"}) RETURN movie
MATCH (michael:Person {name:"Michael"}), (avengers:Movie {title:"Avengers"}) CREATE (michael)-[:HAS_SEEN {rating:5}]->(avengers) return michael, avengers
複製代碼
圖10顯示告終果。
Java中的圖形分析對於咱們在進入Java代碼以前的最後一個例子,讓咱們嘗試使用圖形分析進行簡單的實驗。咱們會給孩子們的朋友添加一些電影,設置我孩子的性別,而後查詢個人一個孩子(邁克爾)可能想要看的電影。結果如圖11所示。
CREATE (movie:Movie {title:"Batman"}) RETURN movie
CREATE (movie:Movie {title:"Gone with the Wind"}) RETURN movie
CREATE (movie:Movie {title:"Spongebob Square Pants"}) RETURN movie
CREATE (movie:Movie {title:"Avengers 2"}) RETURN movie
MATCH (charlie:Person {name:"Charlie"}), (movie:Movie {title:"Batman"}) CREATE (charlie)-[:HAS_SEEN {rating:4}]->(movie) return charlie, movie
MATCH (charlie:Person {name:"Charlie"}), (movie:Movie {title:"Gone with the Wind"}) CREATE (charlie)-[:HAS_SEEN {rating:0}]->(movie) return charlie, movie
MATCH (koby:Person {name:"Koby"}), (movie:Movie {title:"Batman"}) CREATE (koby)-[:HAS_SEEN {rating:4}]->(movie) return koby, movie
MATCH (koby:Person {name:"Koby"}), (movie:Movie {title:"Avengers 2"}) CREATE (koby)-[:HAS_SEEN {rating:5}]->(movie) return koby, movie
MATCH (grant:Person {name:"Grant"}), (movie:Movie {title:"Spongebob Square Pants"}) CREATE (grant)-[:HAS_SEEN {rating:1}]->(movie) return grant, movie
MATCH (jordyn:Person {name:"Jordyn"}), (movie:Movie {title:"Spongebob Square Pants"}) CREATE (jordyn)-[:HAS_SEEN {rating:5}]->(movie) return jordyn, movie
MATCH (michael:Person {name: "Michael"}) SET michael.gender = "male" RETURN michael
MATCH (rebecca:Person {name: "Rebecca"}) SET rebecca.gender = "female" RETURN rebecca
MATCH (steven:Person {name:"Steven"})-[:HAS_CHILD]-(child:Person)-[:FRIEND]-(friend:Person)-[hasSeen:HAS_SEEN]-(movie:Movie) WHERE child.gender = "male" AND hasSeen.rating > 3 RETURN DISTINCT movie.title
複製代碼
上面的前四個陳述創造了四部電影。接下來的六個陳述創造了個人孩子的朋友和他們看過的電影之間的HAS_SEEN
關係,具備不一樣的評級。接下來的兩個語句爲個人孩子添加了一個性別,這是經過按名稱查找Person節點而後調用來完成的SET childName.gender = "male|female"
。在Cypher中,該SET
語句容許您經過將值設置爲更改現有屬性,添加新屬性或刪除屬性NULL
。最後的查詢須要一些工做才能理解。咱們從名爲「Steven」的Person開始,跟隨他與子節點Person關係的HAS_CHILD
關係,跟隨那些Person節點到FRIEND
Person節點,經過HAS_SEEN
關係跟隨那些朋友Person節點到Movie節點,而後添加一個WHERE
檢查二者性別的子句史蒂文的孩子和評級屬性的HAS_SEEN
價值。最後,由於有些孩子看過同一部電影(蝙蝠俠),咱們只想要回歸DISTINCT
電影片頭。在這種狀況下,咱們不返回電影節點,而是返回電影的標題屬性,這就是輸出顯示在表格中的緣由。對於聰明的觀察者,咱們能夠經過將性別添加到子節點查詢來簡化這一點,以下所示:
MATCH (steven:Person {name:"Steven"})-[:HAS_CHILD]-(child:Person {gender:"male"})-[:FRIEND]-(friend:Person)-[hasSeen:HAS_SEEN]<-(movie:Movie) WHERE hasSeen.rating > 3 RETURN DISTINCT movie.title
複製代碼
Cypher是一種考慮編寫查詢的不一樣方式,我鼓勵您閱讀正式文檔以瞭解更多信息。一旦掌握了編寫Cypher查詢的過程,Java編程將是最簡單的部分!咱們將在本簡介的後半部分中對圖形數據和與Neo4j的關係進行選擇。
英文原文:www.javaworld.com/article/325…
更多文章歡迎訪問: http://www.apexyun.com
公衆號:銀河系1號
聯繫郵箱:public@space-explore.com
(未經贊成,請勿轉載)