介绍
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,具有一些常用的模块,能够加速我们在项目中的开发速度。
完整的类的代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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(更新结点与关系,比如更新某一个结点的属性);
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@staticmethod def connectNeo4j(username: str, password: str): my_graph = Graph( "http://localhost:7474", username=username, password=password ) return my_graph
创建模块 createNode,createRelationship,用于创建结点和关系
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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,前者用于更新结点/关系的一个属性,后者更新结点/关系的多个属性
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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、查找某一个结点或者关系是否存在于该数据库中
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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”、“喜欢歌手->周杰伦”,将属性构造成一个具有多个键值对的字典,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 创建一个由人指向电影的关系,关系的类型是“观看”,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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”,查询结果如下:
怎么样,是不是每个实体之间的关系就很清楚地显示出来了!!!这就是图数据库相对于关系型数据库、非关系型数据库的优势。
应用场景
图数据库常用于知识图谱,推荐算法等等领域,用于快速地发现数据实体之间的关系