zoukankan      html  css  js  c++  java
  • 图数据库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”,查询结果如下:

        

        

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

    应用场景


     

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

      

      

      

  • 相关阅读:
    RestTemplate与Gzip压缩
    在浏览器中异步下载文件监听下载进度
    springBoot中的所有配置属性(中文)
    Springboot应用中设置Cookie的SameSite属性
    客户端解析服务器响应的multipart/form-data数据
    springboot + querydsl 入门到会用
    MyBatis通过TypeHandler自动编解码对象的Json属性
    @Transaction注解失效的几种场景
    Elasticsearch 7.x配置用户名密码访问 开启x-pack验证
    搭建Elasticsearch可视化界面 Kibana
  • 原文地址:https://www.cnblogs.com/Bw98blogs/p/10946569.html
Copyright © 2011-2022 走看看