图数据库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”,查询结果如下:

    

    

  怎么样,是不是每个实体之间的关系就很清楚地显示出来了!!!这就是图数据库相对于关系型数据库、非关系型数据库的优势。

 

应用场景


 

  图数据库常用于知识图谱,推荐算法等等领域,用于快速地发现数据实体之间的关系

  

  

  

posted @ 2019-05-29 22:00  bw98  阅读(3624)  评论(0编辑  收藏  举报