圖數據庫neo4j的介紹與入門使用


介紹


 

  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')
View Code

  鏈接模塊 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
View Code

  創建模塊 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
View Code

  更新屬性的方法 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)
View Code

  查找方法有五個,分別是:

    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()
View Code

    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
View Code

    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)
View Code

    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)
View Code

    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')
View Code

  介紹完了上面這些,是不是有點乏味呢?現在讓我們來看看怎么樣去使用吧~

  假如說我想要將“一個名為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': '周傑倫'})
View Code

  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='觀看')
View Code

  隨后連接到 http://localhost:7474/browser/ ,登陸后輸入“MATCH (n) RETURN n”,查詢結果如下:

    

    

  怎么樣,是不是每個實體之間的關系就很清楚地顯示出來了!!!這就是圖數據庫相對於關系型數據庫、非關系型數據庫的優勢。

 

應用場景


 

  圖數據庫常用於知識圖譜,推薦算法等等領域,用於快速地發現數據實體之間的關系

  

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM