介紹
Neo4j 是一款較為領先的圖數據庫,由java編寫,圖數據庫與常用的關系型/非關系型數據庫不同,它沒有表的概念,主要的存儲對象為結點、關系(邊)以及屬性。
存儲形式
1、結點:對應一個實體。
2、關系:對應一個實體間的關系。
3、屬性:每一個結點和關系可以存儲個屬性。
4、標簽、類型:每一個結點和關系可以存儲任意個類型(也成標簽,label或者type)。
Neo4j的特點
- 它擁有簡單的查詢語言 Neo4j CQL
- 它遵循屬性圖數據模型
- 它通過使用 Apache Lucence 支持索引
- 它支持 UNIQUE 約束
- 它包含一個用於執行 CQL 命令的 UI:Neo4j 數據瀏覽器
- 它支持完整的 ACID(原子性,一致性,隔離性和持久性)規則
- 它采用原生圖形庫與本地 GPE(圖形處理引擎)
- 它支持查詢的數據導出到 Json 和 XLS 格式
- 它提供了 REST API,可以被任何編程語言(如 Java,Spring,Scala 等)訪問
- 它提供了可以通過任何 UI MVC 框架(如 Node JS )訪問的 Java 腳本
- 它支持兩種 Java API:Cypher API 和 Native Java API 來開發 Java 應用程序
CQL -- Neo4j的查詢指令
Neo4j的查詢語句是CQL,類似SQL語句,相關入門教程在:https://www.w3cschool.cn/neo4j/neo4j_cql_introduction.html
py2neo -- 對接 Neo4j 的python庫
py2neo 是一款非常方便、對接Neo4j的python庫,它的官方文檔:http://py2neo.org/v3/index.html,GitHub:https://github.com/technige/py2neo
它引入 Node、Relationship、NodeMatcher 等等對接與neo4j存儲結構的類,實現了對neo4j的增刪查改等等功能。
我封裝了一個基於py2neo的類 Neo4jDao,具有一些常用的模塊,能夠加速我們在項目中的開發速度。
完整的類的代碼:
from py2neo import Graph, Node, Relationship, NodeMatcher class Neo4jDao: def __init__(self, username='neo4j', password='123456789'): self.username = username self.password = password self.my_graph = self.connectNeo4j(username=self.username, password=self.password) @staticmethod def connectNeo4j(username: str, password: str): my_graph = Graph( "http://localhost:7474", username=username, password=password ) return my_graph def createNode(self, label: str, properties: dict): """創建結點,如果結點有類型和屬性的話,也一起創建 :param label: 結點的類型 :param properties: 多個屬性鍵值對組成的字典,用於初始化結點的屬性 :return:創建好的結點,類型為Node """ node = Node(label, **properties) self.my_graph.create(node) return node def createRelationship(self, start_node: Node, relation_type: str, end_node: Node, relation_properties=None): """創建關系,如果有關系上屬性的話就一起創建 :param start_node: 起始結點 :param relation_type: 關系類型 :param end_node: 結束結點 :param relation_properties: 屬性字典,如果有傳入的話,則在關系上添加多個形如"屬性名:屬性值"的鍵值對 :return: 創建好的關系對象 """ new_relation = Relationship(start_node, relation_type, end_node) new_relation.update(relation_properties) self.my_graph.create(new_relation) return new_relation def updateProperty(self, node_or_relation, aProperty: tuple): if (not isinstance(node_or_relation, Node)) and (not isinstance((node_or_relation, Relationship))): raise TypeError('node_or_relation 需要是 Node 或 Relationship 類型') node_or_relation[aProperty[0]] = aProperty[1] # tuple的第一位存屬性名,第二位存屬性值 self.my_graph.push(node_or_relation) @staticmethod def updateMultipleProperty(node_or_relation, properties: dict): """同時更新多個屬性 :param node_or_relation: 一個結點或關系對象 :param properties: 多個需要更新的"屬性名:屬性值"鍵值對組成的字典 :return: """ if (not isinstance(node_or_relation, Node)) and (not isinstance((node_or_relation, Relationship))): raise TypeError('node_or_relation 需要是 Node 或 Relationship 類型') node_or_relation.update(properties) def findOneNode(self, node_type=None, properties=None, where=None): """查找一個結點 :param node_type:結點類型,即 label,類型是str :param properties: 多個"屬性名: 屬性值"鍵值對組成的字典,類型是dict :param where: 查詢子句,類型是str :return: 一個Node類型的結點 """ matcher = NodeMatcher(self.my_graph) if not (isinstance(node_type, str)): raise TypeError('查詢的結點的類型必須要指定,而且node_type必須是字符串類型') if not (properties is None): if not (isinstance(properties, dict)): raise TypeError('properties是多個屬性鍵值對組成的字典,它必須是dict類型') if not (where is None): if not (isinstance(where, str)): raise TypeError('where表示的是查詢條件,它必須是字符串類型') if (where is None) and (properties is None): return matcher.match(node_type).first() elif (not (properties is None)) and (where is None): return matcher.match(node_type, **properties).first() elif (properties is None) and (not (where is None)): return matcher.match(node_type).where(where).first() def findAllNode(self, node_type=None, properties=None, where=None): """查找多個結點 :param node_type: node_type:結點類型,即 label,類型是str :param properties: 多個"屬性名: 屬性值"鍵值對組成的字典,類型是dict :param where: 查詢子句,類型是str :return: 多個Node類型的結點組成的list,類型是list """ matcher = NodeMatcher(self.my_graph) if not (isinstance(node_type, str)): raise TypeError('查詢的結點的類型必須要指定,而且node_type必須是字符串形式') if not (where is None): if not (isinstance(where, str)): raise TypeError('where表示的是查詢條件,它必須是字符串形式') if (properties is None) and (where is None): res = matcher.match(node_type) if len(list(res)) > 0: return list(res) else: return None elif (not (properties is None)) and (where is None): res = matcher.match(node_type, **properties) if len(list(res)) > 0: return list(res) else: return None elif (properties is None) and (not (where is None)): res = matcher.match(node_type).where(where) if len(list(res)) > 0: return list(res) else: return None def findOneRelationship(self, nodes=None, r_type=None): """ 查找一條關系 :param nodes: 要查找的結點集合,比如[起點,終點],這個參數可以沒有 :param r_type: 要查找的關系的類型 :return: None 或者 一條查詢結果 """ if (nodes is None) and (r_type is None): raise TypeError('nodes 和 r_type 必須有一個是非空') elif (not (nodes is None)) and (not (r_type is None)): return self.my_graph.match_one(nodes=nodes, r_type=r_type) elif (not (nodes is None)) and (r_type is None): return self.my_graph.match_one(nodes=nodes) elif (nodes is None) and (not (r_type is None)): return self.my_graph.match_one(r_type=r_type) def findAllRelationship(self, nodes=None, r_type=None): """ 查找多條關系 :param nodes: 要查找的結點集合,比如[起點,終點],這個參數可以沒有 :param r_type: 要查找的關系的類型 :return: None 或者 多條查詢結果組成的list """ if (nodes is None) and (r_type is None): raise TypeError('nodes 和 r_type 必須有一個是非空') elif (not (nodes is None)) and (not (r_type is None)): res = self.my_graph.match(nodes=nodes, r_type=r_type) if res is None: return None else: return list(res) elif (not (nodes is None)) and (r_type is None): res = self.my_graph.match(nodes=nodes) if res is None: return None else: return list(res) elif (nodes is None) and (not (r_type is None)): res = self.my_graph.match(r_type=r_type) if res is None: return None else: return list(res) def isExist(self, node=None, relationship=None): if (node is None) and (relationship is None): raise TypeError('要查詢的 node 和 relationship 之中必須有一個存在值') if (not (node is None)) and isinstance(node, Node): return self.my_graph.exists(node) elif (not (relationship is None)) and isinstance(relationship, Relationship): return self.my_graph.exists(relationship) else: raise TypeError('要查詢的 node 或 relationship 的類型並不是 Node 或 Relationship')
鏈接模塊 connectNeo4j ,和 mysql 的連接方法基本一樣,返回的是一個 Graph 實例,它有 create 方法(創建結點與關系),push(更新結點與關系,比如更新某一個結點的屬性);
@staticmethod def connectNeo4j(username: str, password: str): my_graph = Graph( "http://localhost:7474", username=username, password=password ) return my_graph
創建模塊 createNode,createRelationship,用於創建結點和關系
def createNode(self, label: str, properties: dict): """創建結點,如果結點有類型和屬性的話,也一起創建 :param label: 結點的類型 :param properties: 多個屬性鍵值對組成的字典,用於初始化結點的屬性 :return:創建好的結點,類型為Node """ node = Node(label, **properties) self.my_graph.create(node) return node def createRelationship(self, start_node: Node, relation_type: str, end_node: Node, relation_properties=None): """創建關系,如果有關系上屬性的話就一起創建 :param start_node: 起始結點 :param relation_type: 關系類型 :param end_node: 結束結點 :param relation_properties: 屬性字典,如果有傳入的話,則在關系上添加多個形如"屬性名:屬性值"的鍵值對 :return: 創建好的關系對象 """ new_relation = Relationship(start_node, relation_type, end_node) new_relation.update(relation_properties) self.my_graph.create(new_relation) return new_relation
更新屬性的方法 updateProperty 以及 updateMultipleProerty,前者用於更新結點/關系的一個屬性,后者更新結點/關系的多個屬性
def updateProperty(self, node_or_relation, aProperty: tuple): if (not isinstance(node_or_relation, Node)) and (not isinstance((node_or_relation, Relationship))): raise TypeError('node_or_relation 需要是 Node 或 Relationship 類型') node_or_relation[aProperty[0]] = aProperty[1] # tuple的第一位存屬性名,第二位存屬性值 self.my_graph.push(node_or_relation) @staticmethod def updateMultipleProperty(node_or_relation, properties: dict): """同時更新多個屬性 :param node_or_relation: 一個結點或關系對象 :param properties: 多個需要更新的"屬性名:屬性值"鍵值對組成的字典 :return: """ if (not isinstance(node_or_relation, Node)) and (not isinstance((node_or_relation, Relationship))): raise TypeError('node_or_relation 需要是 Node 或 Relationship 類型') node_or_relation.update(properties)
查找方法有五個,分別是:
1、根據類型或屬性查找一個結點(findOneNode)
def findOneNode(self, node_type=None, properties=None, where=None): """查找一個結點 :param node_type:結點類型,即 label,類型是str :param properties: 多個"屬性名: 屬性值"鍵值對組成的字典,類型是dict :param where: 查詢子句,類型是str :return: 一個Node類型的結點 """ matcher = NodeMatcher(self.my_graph) if not (isinstance(node_type, str)): raise TypeError('查詢的結點的類型必須要指定,而且node_type必須是字符串類型') if not (properties is None): if not (isinstance(properties, dict)): raise TypeError('properties是多個屬性鍵值對組成的字典,它必須是dict類型') if not (where is None): if not (isinstance(where, str)): raise TypeError('where表示的是查詢條件,它必須是字符串類型') if (where is None) and (properties is None): return matcher.match(node_type).first() elif (not (properties is None)) and (where is None): return matcher.match(node_type, **properties).first() elif (properties is None) and (not (where is None)): return matcher.match(node_type).where(where).first()
2、根據類型或屬性查找所有結點(findAllNode)
def findAllNode(self, node_type=None, properties=None, where=None): """查找多個結點 :param node_type: node_type:結點類型,即 label,類型是str :param properties: 多個"屬性名: 屬性值"鍵值對組成的字典,類型是dict :param where: 查詢子句,類型是str :return: 多個Node類型的結點組成的list,類型是list """ matcher = NodeMatcher(self.my_graph) if not (isinstance(node_type, str)): raise TypeError('查詢的結點的類型必須要指定,而且node_type必須是字符串形式') if not (where is None): if not (isinstance(where, str)): raise TypeError('where表示的是查詢條件,它必須是字符串形式') if (properties is None) and (where is None): res = matcher.match(node_type) if len(list(res)) > 0: return list(res) else: return None elif (not (properties is None)) and (where is None): res = matcher.match(node_type, **properties) if len(list(res)) > 0: return list(res) else: return None elif (properties is None) and (not (where is None)): res = matcher.match(node_type).where(where) if len(list(res)) > 0: return list(res) else: return None
3、根據結點集合(如 [起始點])或類型查找一條關系(findOneRelationship)
def findOneRelationship(self, nodes=None, r_type=None): """ 查找一條關系 :param nodes: 要查找的結點集合,比如[起點,終點],這個參數可以沒有 :param r_type: 要查找的關系的類型 :return: None 或者 一條查詢結果 """ if (nodes is None) and (r_type is None): raise TypeError('nodes 和 r_type 必須有一個是非空') elif (not (nodes is None)) and (not (r_type is None)): return self.my_graph.match_one(nodes=nodes, r_type=r_type) elif (not (nodes is None)) and (r_type is None): return self.my_graph.match_one(nodes=nodes) elif (nodes is None) and (not (r_type is None)): return self.my_graph.match_one(r_type=r_type)
4、根據結點集合(如 [起始點])或類型查找多條關系(findAllRelationship)
def findAllRelationship(self, nodes=None, r_type=None): """ 查找多條關系 :param nodes: 要查找的結點集合,比如[起點,終點],這個參數可以沒有 :param r_type: 要查找的關系的類型 :return: None 或者 多條查詢結果組成的list """ if (nodes is None) and (r_type is None): raise TypeError('nodes 和 r_type 必須有一個是非空') elif (not (nodes is None)) and (not (r_type is None)): res = self.my_graph.match(nodes=nodes, r_type=r_type) if res is None: return None else: return list(res) elif (not (nodes is None)) and (r_type is None): res = self.my_graph.match(nodes=nodes) if res is None: return None else: return list(res) elif (nodes is None) and (not (r_type is None)): res = self.my_graph.match(r_type=r_type) if res is None: return None else: return list(res)
5、查找某一個結點或者關系是否存在於該數據庫中
def isExist(self, node=None, relationship=None): if (node is None) and (relationship is None): raise TypeError('要查詢的 node 和 relationship 之中必須有一個存在值') if (not (node is None)) and isinstance(node, Node): return self.my_graph.exists(node) elif (not (relationship is None)) and isinstance(relationship, Relationship): return self.my_graph.exists(relationship) else: raise TypeError('要查詢的 node 或 relationship 的類型並不是 Node 或 Relationship')
介紹完了上面這些,是不是有點乏味呢?現在讓我們來看看怎么樣去使用吧~
假如說我想要將“一個名為lwf、現居西安、喜歡歌手為周傑倫的福建人”這條信息存入圖數據庫neo4j,步驟如下:
1、啟動neo4j
2、初始化一個 Dao 實例,同時將圖數據庫的用戶名和密碼作為參數傳入
3、利用 createNode() 創建一個結點,該結點的類型是 person,其余信息作為屬性,比如“名字->lwf”、“喜歡歌手->周傑倫”,將屬性構造成一個具有多個鍵值對的字典,代碼如下:
dao = Neo4jDao(username='neo4j', password='123') node1 = dao.createNode(label='Person', properties={'name': 'lwf', 'living': 'xi an', 'home': '福建', 'favor singer': '周傑倫'})
4、連接到 http://localhost:7474/browser/ ,登陸后輸入“MATCH (n:person) RETURN n”,查詢結果如下:


已經看到了相關結點以及其屬性的信息了吧!!!
假如說我們的項目研究的是人和電影的關系,那么我們還需要用另外一個結點表示電影,一條邊表示“看電影”的關系。所以,我們利用 createNode 創建一個類型為Moive的電影結點,它的名字是“復聯4”,然后利用 createRelationship 創建一個由人指向電影的關系,關系的類型是“觀看”,代碼如下:
node3 = dao.createNode(label='Movie', properties={'name': "復仇者聯盟4:Eng Game"}) relation4 = dao.createRelationship(start_node=node1, end_node=node3, relation_type='觀看')
隨后連接到 http://localhost:7474/browser/ ,登陸后輸入“MATCH (n) RETURN n”,查詢結果如下:


怎么樣,是不是每個實體之間的關系就很清楚地顯示出來了!!!這就是圖數據庫相對於關系型數據庫、非關系型數據庫的優勢。
應用場景
圖數據庫常用於知識圖譜,推薦算法等等領域,用於快速地發現數據實體之間的關系
