文章目录
- 一、Neo4J相关介绍
- 1.为什么需要图数据库
- 方案1:Google+
- 方案2:Facebook
- 2.特定和优势
- 3.什么是Neo4j
- 4.Neo4j数据模型
- 图论基础
- 属性图模型
- Neo4j的构建元素
- 5.软件安装
- 二、CQL语句
- 1.CQL简介
- 2.CREATE 命令
- 3.MATCH 命令
- 4.RETURN 子句
- 5.MATCH和RETURN
- 6.CREATE+MATCH+RETURN命令
- 7.关系
- 8.CREATE创建标签
- 9.WHERE子句
- 10.DELETE命令
- 11.REMOVE命令
- 12.SET子句
- 13.ORDER BY排序
- 14.UNION合并
- 15.LIMIT和SKIP子句
- 16.合并
- 17.NULL值
- 18.IN操作符
- 三、CQL函数
- 1.字符串函数
- 2.AGGEGATION聚合
- 3.关系函数
- 四、使用实例
- 1.查询
- 2.构造数据
- 3.基本查询
- 4.关系深度查询
- 5.分页查询
- 6.更新数据
- 7.删除数据
- 8.索引
- 五、Neo4J和SpringBoot整合
- 1.Node的操作
- 2.Node关系的维护
- 3.JPA自定义方法规则
一、Neo4J相关介绍
1.为什么需要图数据库
随着社交、电商、金融、零售、物联网等行业的快速发展,现实社会织起了了一张庞大而复杂的关系网,传统数据库很难处理关系运算。大数据行业需要处理的数据之间的关系随数据量呈几何级数增长,急需一种支持海量复杂数据关系运算的数据库,图数据库应运而生。世界上很多著名的公司都在使用图数据库,比如:
- 社交领域:Facebook, Twitter,Linkedin用它来管理社交关系,实现好友推荐
- 零售领域:eBay,沃尔玛使用它实现商品实时推荐,给买家更好的购物体验
- 金融领域:摩根大通,花旗和瑞银等银行在用图数据库做风控处理
- 汽车制造领域:沃尔沃,戴姆勒和丰田等顶级汽车制造商依靠图数据库推动创新制造解决方案
- 电信领域:Verizon, Orange和AT&T 等电信公司依靠图数据库来管理网络,控制访问并支持客户
360 - 酒店领域:万豪和雅高酒店等顶级酒店公司依使用图数据库来管理复杂且快速变化的库存
图数据库并非指存储图片的数据库,而是以图数据结构存储和查询数据。
图数据库是基于图论实现的一种NoSQL数据库,其数据存储结构和数据查询方式都是以图论为基础的,图数据库主要用于存储更多的连接数据.
图论〔Graph Theory〕是数学的一个分支。它以图为研究对象图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。
方案1:Google+
使用 Google+(GooglePlus)应用程序来了解现实世界中 Graph 数据库的需求。 观察下面的图表。
在这里,我们用圆圈表示了 Google+应用个人资料。
在上图中,轮廓“A”具有圆圈以连接到其他轮廓:家庭圈(B,C,D)和朋友圈(B,C)。
再次,如果我们打开配置文件“B”,我们可以观察以下连接的数据。
像这样,这些应用程序包含大量的结构化,半结构化和非结构化的连接数据。 在 RDBMS 数据库中表示这种非结构化连接数据并不容易。
如果我们在 RDBMS 数据库中存储这种更多连接的数据,那么检索或遍历是非常困难和缓慢的。
所以要表示或存储这种更连接的数据,我们应该选择一个流行的图数据库。
图形DBMS非常容易地存储这种更多连接的数据。 它将每个配置文件数据作为节点存储在内部,它与相邻节点连接的节点,它们通过关系相互连接。
他们存储这种连接的数据与上面的图表中的相同,这样检索或遍历是非常容易和更快的。
方案2:Facebook
利用 Facebook 应用程序了解现实世界中 Graph 数据库的需求。
在上面的图中,Facebook Profile“A”已经连接到他的朋友,喜欢他的一些朋友,发送消息给他的一些朋友,跟随他喜欢的一些名人。
这意味着大量的连接数据配置文件A.如果我们打开其他配置文件,如配置文件B,我们将看到类似的大量的连接数据。
注- 通过观察上述两个应用程序,它们有很多更多的连接数据。 它是非常容易存储和检索,这种更连接的数据与图形数据库。
2.特定和优势
关系查询性能对比 在数据关系中心,图形数据库在查询速度方面非常高效,即使对于深度和复杂的查询也是如此。在关系型数据库和图数据库(Neo4j)之间进行了实验:在一个社交网络里找到最大深度为5的朋友的朋友,他们的数据集包括100万人,每人约有50个朋友。
实验结果如下:
对比关系型数据库
各种NOSQL对比
分类 | 数据模型 | 优势 | 劣势 | 举例 |
---|---|---|---|---|
键值对数据库 | 哈希表 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 | Redis |
列存储数据库 | 列式数据存储 | 查找速度快;支持分布横向扩展;数据压缩率高 | 功能相对受限 | HBase |
文档型数据库 | 键值对扩展 | 数据结构要求不严格;表结构可变;不需要预先定义表结构 | 查询性能不高,缺乏统一的查询语法 | MongoDB |
图数据库 | 节点和关系组成的图 | 利用图结构相关算法(最短路径、节点度关系查找等) | 可能需要对整个图做计算,不利于图数据分布存储 | Neo4j |
3.什么是Neo4j
Neo4j是一个开源的NoSQL图形数据库,2003 年开始开发,使用 scala和java 语言,2007年开始发布。
- 是世界上最先进的图数据库之一,提供原生的图数据存储,检索和处理;
- 采用属性图模型(Property graph model),极大的完善和丰富图数据模型;
- 专属查询语言 Cypher,直观,高效;
官网: https://neo4j.com/
Neo4j的特性:
- SQL就像简单的查询语言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应用程序
Neo4j的优点:
- 它很容易表示连接的数据
- 检索/遍历/导航更多的连接数据是非常容易和快速的
- 它非常容易地表示半结构化数据
- Neo4j CQL查询语言命令是人性化的可读格式,非常容易学习
- 使用简单而强大的数据模型
- 它不需要复杂的连接来检索连接的/相关的数据,因为它很容易检索它的相邻节点或关系细节没有连接或索引
4.Neo4j数据模型
图论基础
图是一组节点和连接这些节点的关系,图形以属性的形式将数据存储在节点和关系中,属性是用于表示数据的键值对。
在图论中,我们可以表示一个带有圆的节点,节点之间的关系用一个箭头标记表示。最简单的可能图是单个节点:
我们可以使用节点表示社交网络(如Google+(GooglePlus)个人资料),它不包含任何属性。向 Google+个人资料添加一些属性:
在两个节点之间创建关系:
此处在两个配置文件之间创建关系名称“跟随”。 这意味着 Profile-I 遵循 Profile-II。
属性图模型
Neo4j图数据库遵循属性图模型来存储和管理其数据。
- 属性图模型规则
- 表示节点,关系和属性中的数据
- 节点和关系都包含属性
- 关系连接节点
- 属性是键值对
- 节点用圆圈表示,关系用方向键表示。
- 关系具有方向:单向和双向。
- 每个关系包含“开始节点”或“从节点”和“到节点”或“结束节点”
在属性图数据模型中,关系应该是定向的。如果我们尝试创建没有方向的关系,那么它将抛出一个错误消息。在Neo4j中,关系也应该是有方向性的。如果我们尝试创建没有方向的关系,那么Neo4j会抛出一个错误消息,“关系应该是方向性的”。
Neo4j图数据库将其所有数据存储在节点和关系中,我们不需要任何额外的RDBMS数据库或NoSQL数据库来存储Neo4j数据库数据,它以图的形式存储数据。Neo4j使用本机GPE(图形处理引擎)来使用它的本机图存储格式。
图数据库数据模型的主要构建块是:
- 节点
- 关系
- 属性
简单的属性图的例子:
这里我们使用圆圈表示节点。 使用箭头表示关系,关系是有方向性的。 我们可以用Properties(键值对)来表示Node的数据。 在这个例子中,我们在Node的Circle中表示了每个Node的Id属性。
Neo4j的构建元素
Neo4j图数据库主要有以下构建元素:
- 节点
- 属性
- 关系
- 标签
- 数据浏览器
有一个或多个标签,用于描述其在图表中的作用
属性
属性(Property)是用于描述图节点和关系的键值对。其中Key是一个字符串,值可以通过使用任何
- Neo4j数据类型来表示
- 属性是命名值,其中名称(或键)是字符串
- 属性可以被索引和约束
- 可以从多个属性创建复合索引
关系
关系(Relationship)同样是图数据库的基本元素。当数据库中已经存在节点后,需要将节点连接起来构成图。关系就是用来连接两个节点,关系也称为图论的边(Edge) ,其始端和末端都必须是节点,关系不能指向空也不能从空发起。关系和节点一样可以包含多个属性,但关系只能有一个类型(Type) 。
- 关系连接两个节点
- 关系是方向性的
- 节点可以有多个甚至递归的关系
- 关系可以有一个或多个属性(即存储为键/值对的属性)
基于方向性,Neo4j关系被分为两种主要类型:
- 单向关系
- 双向关系
标签
标签(Label)将一个公共名称与一组节点或关系相关联, 节点或关系可以包含一个或多个标签。 我们
可以为现有节点或关系创建新标签, 我们可以从现有节点或关系中删除标签。
- 标签用于将节点分组
- 一个节点可以具有多个标签
- 对标签进行索引以加速在图中查找节点
- 本机标签索引针对速度进行了优化
Neo4j Browser
一旦我们安装Neo4j,我们就可以访问Neo4j数据浏览器
5.软件安装
下载地址:https://neo4j.com/download-center/
安装方式:
- Neo4j Enterprise Server
- Neo4j Community Server
- Neo4j Desktop
下载相关软件
解压缩即可
相关的指令
console: 直接启动 neo4j 服务器
install-service | uninstall-service | update-service : 安装/卸载/更新 neo4j 服务
start/stop/restart/status: 启动/停止/重启/状态
-V 输出更多信息
进入到bin目录,执行
neo4j console
在浏览器中访问http://localhost:7474
使用用户名neo4j和默认密码neo4j进行连接,然后会提示更改密码。
Neo4j Browser是开发人员用来探索Neo4j数据库、执行Cypher查询并以表格或图形形式查看结果的工具。
当然也可以通过 Docker 来安装
拉取镜像
docker pull neo4j:3.5.22-community
运行镜像
docker run -d -p 7474:7474 -p 7687:7687 --name neo4j \
-e "NEO4J_AUTH=neo4j/123456" \
-v /usr/local/soft/neo4j/data:/data \
-v /usr/local/soft/neo4j/logs:/logs \
-v /usr/local/soft/neo4j/conf:/var/lib/neo4j/conf \
-v /usr/local/soft/neo4j/import:/var/lib/neo4j/import \
neo4j:3.5.22-community
二、CQL语句
1.CQL简介
Neo4j的Cypher语言是为处理图形数据而构建的,CQL代表Cypher查询语言。像Oracle数据库具有查询语言SQL,Neo4j具有CQL作为查询语言。
- 它是Neo4j图形数据库的查询语言。
- 它是一种声明性模式匹配语言
- 它遵循SQL语法。
- 它的语法是非常简单且人性化、可读的格式。
CQL命令 | 用法 |
---|---|
CREATE | 创建节点,关系和属性 |
MATCH | 检索有关节点,关系和属性数据 |
RETURN | 返回查询结果 |
WHERE | 提供条件过滤检索数据 |
DELETE | 删除节点和关系 |
REMOVE | 删除节点和关系的属性 |
ORDER BY | 排序检索数据 |
SET | 添加或更新标签 |
2.CREATE 命令
Neo4j使用CQL“CREATE”命令
- 创建没有属性的节点
- 使用属性创建节点
- 在没有属性的节点之间创建关系
- 使用属性创建节点之间的关系
- 为节点或关系创建单个或多个标签
语法命令
CREATE (<node-name>:<label-name>)
语法说明
注意事项 -
1、Neo4j数据库服务器使用此<node-name>将此节点详细信息存储在Database.As中作为Neo4j DBA或Developer,我们不能使用它来访问节点详细信息。
2、Neo4j数据库服务器创建一个<label-name>作为内部节点名称的别名。作为Neo4j DBA或Developer,我们应该使用此标签名称来访问节点详细信息。
3.MATCH 命令
Neo4j CQL MATCH 命令用于
- 从数据库获取有关节点和属性的数据
- 从数据库获取有关节点,关系和属性的数据
语法格式:
MATCH
(<node-name>:<label-name>
)
语法说明:
4.RETURN 子句
Neo4j CQL RETURN子句用于 -
- 检索节点的某些属性
- 检索节点的所有属性
- 检索节点和关联关系的某些属性
- 检索节点和关联关系的所有属性
语法结构
RETURN <node-name>.<property1-name>,........<node-name>.<propertyn-name>
语法说明:
5.MATCH和RETURN
在Neo4j CQL中,我们不能单独使用MATCH或RETURN命令,因此我们应该合并这两个命令以从数据库检索数据。
Neo4j使用CQL MATCH + RETURN命令 -
- 检索节点的某些属性
- 检索节点的所有属性
- 检索节点和关联关系的某些属性
- 检索节点和关联关系的所有属性
语法结构
MATCH Command
RETURN Command
语法说明
6.CREATE+MATCH+RETURN命令
先创建一个客户
create (e:Customer {id:"1001",name:"boge",location:"cs"})
创建一个信用卡节点
create (cc:CreditCard {id:"9999",number:"1234567890",cvv:"888",expiredate:"22/17"})
然后我们可以查询对应的信息
match (k:customer) return k.name,k.location,k.id
还可以查询信用卡的信息
match (m:CreditCard) return m.number,m.cvv,m.id,m.expiredate
7.关系
Neo4j图数据库遵循属性图模型来存储和管理其数据。
根据属性图模型,关系应该是定向的。 否则,Neo4j将抛出一个错误消息。
基于方向性,Neo4j关系被分为两种主要类型。
- 单向关系
- 双向关系
在以下场景中,我们可以使用Neo4j CQL CREATE命令来创建两个节点之间的关系。 这些情况适用于Uni和双向关系。
- 在两个现有节点之间创建无属性的关系
- 在两个现有节点之间创建有属性的关系
- 在两个新节点之间创建无属性的关系
- 在两个新节点之间创建有属性的关系
- 在具有WHERE子句的两个退出节点之间创建/不使用属性的关系
注意 -
我们将创建客户和CreditCard之间的关系,如下所示:
8.CREATE创建标签
CREATE标签可以创建单个标签或者多个标签
CREATE(node-name:lable-name1:lable-name2)
还有就是可以根据CREATE语句来创建标签之间的关系
CREATE (node1-name:lable1-name) - [relationship-name:relationship-lable-name]->(node2-name:lable2-name)
案例:
create (p1:Profile1)-[r1:喜欢]->(p2:Profile2)
9.WHERE子句
像SQL一样,Neo4j CQL在CQL MATCH命令中提供了WHERE子句来过滤MATCH查询的结果。
语法结构
WHERE <condition>
复杂的语法结构
WHERE <condition> <boolean-operator> <condition>
Neo4j支持以下布尔运算符在Neo4j CQL WHERE子句中使用以支持多个条件。
Neo4j 支持以下的比较运算符,在 Neo4j CQL WHERE 子句中使用来支持条件。
案例:
match (m:Employee) where m.age > 18 or m.id = 1002 return m
多个节点关联查询
where子句也可以创建关系
语法结构
MATCH (<node1-label-name>:<node1-name>),(<node2-label-name>:<node2-name>)
WHERE <condition>
CREATE (<node1-label-name>)-[<relationship-label-name>:<relationship-name>{<relationship-properties>}]->(<node2-label-name>)
案例
match (c:customer) , (d:CreditCard) where c.id = "1001" and d.id = "9999" create (c)-[r:消费{shopdate:"2022/09/28",price:6000}]->(d) return r
10.DELETE命令
Neo4j使用CQL DELETE子句
- 删除节点。
- 删除节点及相关节点和关系。
对应的语法结构
DELETE <node-name-list>
注意 -
我们应该使用逗号(,)运算符来分隔节点名。
在Neo4j中,关系必须始终连接两个节点。如果删除了一个节点而没有同时删除其关系,那么这些关系就会变成孤立关系,导致数据不一致。使用DETACH DELETE可以避免这种情况的发生。
11.REMOVE命令
有时基于我们的客户端要求,我们需要向现有节点或关系添加或删除属性。
我们使用Neo4j CQL SET子句向现有节点或关系添加新属性。
我们使用Neo4j CQL REMOVE子句来删除节点或关系的现有属性。
Neo4j CQL REMOVE命令用于
- 删除节点或关系的标签
- 删除节点或关系的属性
Neo4j CQL DELETE和REMOVE命令之间的主要区别 -
- DELETE操作用于删除节点和关联关系。
- REMOVE操作用于删除标签和属性。
Neo4j CQL DELETE和REMOVE命令之间的相似性 -
- 这两个命令不应单独使用。
- 两个命令都应该与MATCH命令一起使用。
通过remove来移除标签
match (d:`电影`) remove d:Movie
12.SET子句
有时,根据我们的客户端要求,我们需要向现有节点或关系添加新属性。
要做到这一点,Neo4j CQL 提供了一个SET子句。
Neo4j CQL 已提供 SET 子句来执行以下操作。
- 向现有节点或关系添加新属性
- 添加或更新属性值
语法结构
SET <property-name-list>
添加属性:
MATCH (book:Book)
SET book.title = 'superstar'
RETURN book
13.ORDER BY排序
Neo4j CQL在MATCH命令中提供了“ORDER BY”子句,对MATCH查询返回的结果进行排序。
我们可以按升序或降序对行进行排序。
默认情况下,它按升序对行进行排序。 如果我们要按降序对它们进行排序,我们需要使用DESC子句。
语法结构
ORDER BY <property-name-list> [DESC]
举例:
MATCH (emp:Employee)
RETURN emp.empid,emp.name,emp.salary,emp.deptno
ORDER BY emp.name
14.UNION合并
与SQL一样,Neo4j CQL有两个子句,将两个不同的结果合并成一组结果
- UNION
- UNION ALL
UNION子句
它将两组结果中的公共行组合并返回到一组结果中。 它不从两个节点返回重复的行。
限制:
结果列类型和来自两组结果的名称必须匹配,这意味着列名称应该相同,列的数据类型应该相同。
语法结构
<MATCH Command1>UNION
<MATCH Command2>
注意 -
如果这两个查询不返回相同的列名和数据类型,那么它抛出一个错误。
as 来处理不同的前缀
MATCH (cc:CreditCard)
RETURN cc.id as id,cc.number as number,cc.name as name,cc.valid_from as valid_from,cc.valid_to as valid_to
UNION
MATCH (dc:DebitCard)
RETURN dc.id as id,dc.number as number,dc.name as name,dc.valid_from as valid_from,dc.valid_to as valid_to
UNION ALL子句
它结合并返回两个结果集的所有行成一个单一的结果集。它还返回由两个节点重复行。
限制
结果列类型,并从两个结果集的名字必须匹配,这意味着列名称应该是相同的,列的数据类型应该是相同的。
union all 语法
<MATCH Command1>
UNION ALL
<MATCH Command2>
15.LIMIT和SKIP子句
Neo4j CQL已提供“LIMIT”子句来过滤或限制查询返回的行数。 它修剪CQL查询结果集底部的结果。
如果我们要修整CQL查询结果集顶部的结果,那么我们应该使用CQL SKIP子句
skip跳过
skip和limit可以结合使用达到分页的效果
16.合并
Neo4j使用CQL MERGE命令 -
- 创建节点,关系和属性
- 为从数据库检索数据
MERGE命令是CREATE命令和MATCH命令的组合。
MERGE = CREATE + MATCH
merge语法
MERGE (<node-name>:<label-name>
{<Property1-name>:<Pro<rty1-Value>.....<Propertyn-name>:<Propertyn-Value>
})
注意 -
Neo4j CQL MERGE命令语法与CQL CREATE命令类似。
17.NULL值
Neo4j CQL将空值视为对节点或关系的属性的缺失值或未定义值。
当我们创建一个具有现有节点标签名称但未指定其属性值的节点时,它将创建一个具有NULL属性值的新节点。
还可以用null 作为查询的条件
18.IN操作符
与SQL一样,Neo4j CQL提供了一个IN运算符,以便为CQL命令提供值的集合。
IN[<Collection-of-values>]
案例:
MATCH (e:Employee)
WHERE e.id IN [123,124]
RETURN e.id,e.name,e.sal,e.deptno
三、CQL函数
1.字符串函数
与SQL一样,Neo4J CQL提供了一组String函数,用于在CQL查询中获取所需的结果。
列举几个常用的
案例:
2.AGGEGATION聚合
和SQL一样,Neo4j CQL提供了一些在RETURN子句中使用的聚合函数。 它类似于SQL中的GROUP BY子句。
我们可以使用MATCH命令中的RETURN +聚合函数来处理一组节点并返回一些聚合值。
3.关系函数
Neo4j CQL提供了一组关系函数,以在获取开始节点,结束节点等细节时知道关系的细节。
案例:
四、使用实例
1.查询
[MATCH WHERE] //条件查询
[WITH [ORDER BY] [SKIP] [LIMIT]] //查询的结果以管道的形式传递给下面的语句,聚合查询必须使用WITH
RETURN [ORDER BY] [SKIP] [LIMIT] //返回、排序、跳过、返回个数
2.构造数据
CREATE (北京市转运中心:OLT {bid: 8001, name: "北京市转运中心", address: "北京市转运中心", location : point({latitude:39.904179, longitude:116.407387})})
CREATE (上海市转运中心:OLT {bid: 8002, name: "上海市转运中心", address: "上海市转运中心", location : point({latitude:31.230525, longitude:121.473667})})
CREATE (南京市转运中心:OLT {bid: 8003, name: "南京市转运中心", address: "南京市转运中心", location : point({latitude:32.059344, longitude:118.796624})})
CREATE (太原市转运中心:OLT {bid: 8004, name: "太原市转运中心", address: "太原市转运中心", location : point({latitude:37.870451, longitude:112.549656})})
CREATE (郑州市转运中心:OLT {bid: 8005, name: "郑州市转运中心", address: "郑州市转运中心", location : point({latitude:34.745551, longitude:113.624321})})
CREATE(北京市转运中心)-[:IN_LINE {cost:10684.9}]->(上海市转运中心),(北京市转运中心)<-[:OUT_LINE {cost:10684.9}]-(上海市转运中心),(北京市转运中心)-[:IN_LINE {cost:8993.1}]->(南京市转运中心),(北京市转运中心)<-[:OUT_LINE {cost:8993.1}]-(南京市转运中心),(南京市转运中心)-[:IN_LINE {cost:2699.4}]->(上海市转运中心),(南京市转运中心)<-[:OUT_LINE {cost:2699.4}]-(上海市转运中心),(太原市转运中心)-[:IN_LINE {cost:3609.7}]->(郑州市转运中心),(太原市转运中心)<-[:OUT_LINE {cost:3609.7}]-(郑州市转运中心),(郑州市转运中心)-[:IN_LINE {cost:5659.7}]->(南京市转运中心),(郑州市转运中心)<-[:OUT_LINE {cost:5659.7}]-(南京市转运中心)
CREATE (昌平区转运中心:TLT {bid: 90001, name: "昌平区转运中心", address: "昌平区转运中心", location : point({latitude:40.220952, longitude:116.231034})})
CREATE (北京市昌平区新龙城:AGENCY {bid: 100260, name: "北京市昌平区新龙城", address: "龙跃苑四区3号楼底商", phone : "010-53049073,010-53576707", location : point({latitude:40.07544443596149, longitude:116.3470535709328})})
CREATE(北京市昌平区新龙城)-[:IN_LINE {cost:189.7}]->(昌平区转运中心),(北京市昌平区新龙城)<-[:OUT_LINE {cost:189.7}]-(昌平区转运中心)
CREATE (北京市昌平区定泗路:AGENCY {bid: 100280, name: "北京市昌平区定泗路", address: "北七家镇定泗路苍龙街交叉口", phone : "010-86392987", location : point({latitude:40.11765281246394, longitude:116.37212849638287})})
CREATE(北京市昌平区定泗路)-[:IN_LINE {cost:166.2}]->(昌平区转运中心),(北京市昌平区定泗路)<-[:OUT_LINE {cost:166.2}]-(昌平区转运中心)
CREATE (海淀区转运中心:TLT {bid: 90002, name: "海淀区转运中心", address: "海淀区转运中心", location : point({latitude:39.959893, longitude:116.2977})})
CREATE (北京市海淀区小营:AGENCY {bid: 100347, name: "北京市海淀区小营", address: "北京市昌平区回龙观街道金燕龙大厦停车场", phone : "010-86483817,010-86483817,010-86483817", location : point({latitude:40.06177798692319, longitude:116.32706587559049})})
CREATE(北京市海淀区小营)-[:IN_LINE {cost:116.1}]->(海淀区转运中心),(北京市海淀区小营)<-[:OUT_LINE {cost:116.1}]-(海淀区转运中心)
CREATE (北京市海淀区万泉河:AGENCY {bid: 100227, name: "北京市海淀区万泉河", address: "北京市海淀区四季青镇杏石口路47号院", phone : "18521852356", location : point({latitude:39.94882822425318, longitude:116.25707017441161})})
CREATE(北京市海淀区万泉河)-[:IN_LINE {cost:36.8}]->(海淀区转运中心),(北京市海淀区万泉河)<-[:OUT_LINE {cost:36.8}]-(海淀区转运中心)
CREATE(昌平区转运中心)-[:IN_LINE {cost:383.3}]->(北京市转运中心),(昌平区转运中心)<-[:OUT_LINE {cost:383.3}]-(北京市转运中心),(海淀区转运中心)-[:IN_LINE {cost:112.3}]->(北京市转运中心),(海淀区转运中心)<-[:OUT_LINE {cost:112.3}]-(北京市转运中心)
CREATE (浦东新区转运中心:TLT {bid: 90003, name: "浦东新区转运中心", address: "浦东新区转运中心", location : point({latitude:31.221461, longitude:121.544346})})
CREATE (上海市浦东新区南汇:AGENCY {bid: 210057, name: "上海市浦东新区南汇", address: "园春路8号", phone : "18821179169", location : point({latitude:31.035240152911637, longitude:121.73459966751048})})
CREATE(上海市浦东新区南汇)-[:IN_LINE {cost:275.4}]->(浦东新区转运中心),(上海市浦东新区南汇)<-[:OUT_LINE {cost:275.4}]-(浦东新区转运中心)
CREATE (上海市浦东新区周浦:AGENCY {bid: 210127, name: "上海市浦东新区周浦", address: "川周公路3278-8号", phone : "021-68060322", location : point({latitude:31.132409729356993, longitude:121.59815370294322})})
CREATE(上海市浦东新区周浦)-[:IN_LINE {cost:111.6}]->(浦东新区转运中心),(上海市浦东新区周浦)<-[:OUT_LINE {cost:111.6}]-(浦东新区转运中心)
CREATE (奉贤区转运中心:TLT {bid: 90004, name: "奉贤区转运中心", address: "奉贤区转运中心", location : point({latitude:30.918406, longitude:121.473945})})
CREATE (上海市奉贤区东部:AGENCY {bid: 210017, name: "上海市奉贤区东部", address: "上上海市奉贤区洪庙镇洪兰路351", phone : "021-57171717", location : point({latitude:30.917752751719863, longitude:121.67587819184698})})
CREATE(上海市奉贤区东部)-[:IN_LINE {cost:192.9}]->(奉贤区转运中心),(上海市奉贤区东部)<-[:OUT_LINE {cost:192.9}]-(奉贤区转运中心)
CREATE (上海市奉贤区青村:AGENCY {bid: 210442, name: "上海市奉贤区青村", address: "姚家村1127号", phone : "021-57566663,021-57566606", location : point({latitude:30.916946897994983, longitude:121.57954144207972})})
CREATE(上海市奉贤区青村)-[:IN_LINE {cost:100.9}]->(奉贤区转运中心),(上海市奉贤区青村)<-[:OUT_LINE {cost:100.9}]-(奉贤区转运中心)
CREATE(浦东新区转运中心)-[:IN_LINE {cost:68.0}]->(上海市转运中心),(浦东新区转运中心)<-[:OUT_LINE {cost:68.0}]-(上海市转运中心),(奉贤区转运中心)-[:IN_LINE {cost:347.4}]->(上海市转运中心),(奉贤区转运中心)<-[:OUT_LINE {cost:347.4}]-(上海市转运中心)
CREATE (玄武区转运中心:TLT {bid: 90004, name: "玄武区转运中心" , location : point({latitude:32.048644, longitude:118.797779})})
CREATE (江苏省南京市玄武区紫金墨香苑:AGENCY {bid: 25073, name: "江苏省南京市玄武区紫金墨香苑", address: "栖霞区燕尧路100号", phone : "025-58765331,025-83241955,025-83241881", location : point({latitude:32.117016089520305, longitude:118.86319310255513})})
CREATE(江苏省南京市玄武区紫金墨香苑)-[:IN_LINE {cost:98.0}]->(玄武区转运中心),(江苏省南京市玄武区紫金墨香苑)<-[:OUT_LINE {cost:98.0}]-(玄武区转运中心)
CREATE (江苏省南京市玄武区长江路:AGENCY {bid: 25023, name: "江苏省南京市玄武区长江路", address: "观音阁10号", phone : "18521133265,18695799166", location : point({latitude:32.04803554410631, longitude:118.79190455263355})})
CREATE(江苏省南京市玄武区长江路)-[:IN_LINE {cost:5.6}]->(玄武区转运中心),(江苏省南京市玄武区长江路)<-[:OUT_LINE {cost:5.6}]-(玄武区转运中心)
CREATE(玄武区转运中心)-[:IN_LINE {cost:12.0}]->(南京市转运中心),(玄武区转运中心)<-[:OUT_LINE {cost:12.0}]-(南京市转运中心)
CREATE (小店区转运中心:TLT {bid: 90005, name: "小店区转运中心" , location : point({latitude:37.736865, longitude:112.565524})})
CREATE (山西省太原市青龙:AGENCY {bid: 351068, name: "山西省太原市青龙", address: "长治路33号经典家园停车场内13号商铺", phone : "0351-2025888", location : point({latitude:37.83589608758359, longitude:112.56059258109424})})
CREATE(山西省太原市青龙)-[:IN_LINE {cost:110.3}]->(小店区转运中心),(山西省太原市青龙)<-[:OUT_LINE {cost:110.3}]-(小店区转运中心)
CREATE (山西省太原市长风街:AGENCY {bid: 351045, name: "山西省太原市长风街", address: "平阳路104号省农机公司院内", phone : "18636100730", location : point({latitude:37.809964384001226, longitude:112.55299317699505})})
CREATE(山西省太原市长风街)-[:IN_LINE {cost:82.1}]->(小店区转运中心),(山西省太原市长风街)<-[:OUT_LINE {cost:82.1}]-(小店区转运中心)
CREATE(小店区转运中心)-[:IN_LINE {cost:149.4}]->(太原市转运中心),(小店区转运中心)<-[:OUT_LINE {cost:149.4}]-(太原市转运中心)
CREATE (中原区转运中心:TLT {bid: 90006, name: "中原区转运中心" , location : point({latitude:34.74828, longitude:113.612966})})
CREATE (河南省郑州市郑上路:AGENCY {bid: 371067, name: "河南省郑州市郑上路", address: "中原西路西四环西北角", phone : "0371-55116757,0371-68014786", location : point({latitude:34.74753024533005, longitude:113.57428550005442})})
CREATE(河南省郑州市郑上路)-[:IN_LINE {cost:35.4}]->(中原区转运中心),(河南省郑州市郑上路)<-[:OUT_LINE {cost:35.4}]-(中原区转运中心)
CREATE (河南省郑州市颍河路:AGENCY {bid: 371086, name: "河南省郑州市颍河路", address: "航海西路与西三环交叉口向南300米路西中贸商务", phone : "19139415556", location : point({latitude:34.71593280680163, longitude:113.60398506929064})})
CREATE(河南省郑州市颍河路)-[:IN_LINE {cost:36.9}]->(中原区转运中心),(河南省郑州市颍河路)<-[:OUT_LINE {cost:36.9}]-(中原区转运中心)
CREATE(中原区转运中心)-[:IN_LINE {cost:11.5}]->(郑州市转运中心),(中原区转运中心)<-[:OUT_LINE {cost:11.5}]-(郑州市转运中心)
3.基本查询
MATCH (n) RETURN n //查询所有的数据,数据量大是勿用
MATCH (n:AGENCY) RETURN n //查询所有的网点(AGENCY)
MATCH (n:OLT {name: "北京市转运中心"}) -- (m) RETURN n,m //查询所有与“北京市转运中心”有关系的节点
MATCH (n:OLT {name:"北京市转运中心"}) --> (m:OLT) RETURN n,m //查询所有"北京市转运中心"关联的一级转运中心
MATCH (n:OLT {name:"北京市转运中心"}) -[r:IN_LINE]- (m) RETURN n,r,m //可以指定关系标签查询
MATCH p = (n:OLT {name:"北京市转运中心"}) --> (m:OLT) RETURN p //将查询赋值与变量
//通过 type()函数查询关系类型
MATCH (n:OLT {name:"北京市转运中心"}) -[r]-> (m:OLT {name:"南京市转运中心"}) RETURN type(r)
//模糊查询相关节点
MATCH (n) WHERE n.name CONTAINS '模糊查询节点名称' RETURN n
4.关系深度查询
可以指定关系的深度进行查询,语法格式:-[:TYPE*minHops..maxHops]->
六度分隔(Six Degrees of Separation)理论。
1967年,哈佛大学的心理学教授Stanley Milgram(1933~1984)想要描绘一个连结人与社区的人际连系网。做过一次连锁信实验,结果发现了“六度分隔”现象。简单地说:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过六个人你就能够认识任何一个陌生人。”
//查询【北京市转运中心】关系中深度为1~2层关系的节点
MATCH (n:OLT {name:"北京市转运中心"}) -[*1..2]->(m) RETURN *
//也可以这样
MATCH (n:OLT {name:"北京市转运中心"}) -[*..2]->(m) RETURN *
//也可以通过变量的方式查询
MATCH path = (n:OLT {name:"北京市转运中心"}) -[*..2]->(m)
RETURN path
//查询关系,relationships()获取结果中的关系,WITH向后传递数据
MATCH path = (n:OLT {name:"北京市转运中心"}) -[*..2]->(m)
WITH n,m, relationships(path) AS r
RETURN r
//查询两个网点之间所有的路线,最大深度为6,可以查询到2条路线
MATCH path = (n:AGENCY) -[*..6]->(m:AGENCY)
WHERE n.name = "北京市昌平区定泗路" AND m.name = "上海市浦东新区南汇"
RETURN path
//查询两个网点之间最短路径,查询深度最大为10
MATCH path = shortestPath((n:AGENCY) -[*..10]->(m:AGENCY))
WHERE n.name = "北京市昌平区定泗路" AND m.name = "上海市浦东新区南汇"
RETURN path
//查询两个网点之间所有的路线中成本最低的路线,最大深度为10(如果成本相同,转运节点最少)
MATCH path = (n:AGENCY) -[*..10]->(m:AGENCY)
WHERE n.name = "北京市昌平区定泗路" AND m.name = "上海市浦东新区南汇"
UNWIND relationships(path) AS r
WITH sum(r.cost) AS cost, path
RETURN path ORDER BY cost ASC, LENGTH(path) ASC LIMIT 1
//UNWIND是将列表数据展开操作
//sum()是聚合统计函数,类似还有:avg()、max()、min()等
5.分页查询
//分页查询网点,按照bid正序排序,每页查询2条数据,第一页
MATCH (n:AGENCY)
RETURN n ORDER BY n.bid ASC SKIP 0 LIMIT 2
//第二页
MATCH (n:AGENCY)
RETURN n ORDER BY n.bid ASC SKIP 2 LIMIT 2
//……
6.更新数据
更新数据是使用SET语句进行标签、属性的更新。SET操作是幂等性的。
// 更新/设置 属性
MATCH (n:AGENCY {name:"北京市昌平区新龙城"})
SET n.address = "龙跃苑四区3号楼底商101号"
RETURN n
//通过remove移除属性
MATCH (n:AGENCY {name:"北京市昌平区新龙城"}) REMOVE n.address RETURN n
//没有address属性的增加属性
MATCH (n:AGENCY) WHERE n.address IS NULL SET n.address = "暂无地址" RETURN n
7.删除数据
删除数据通过DELETE、DETACH DELETE完成。其中DELETE不能删除有关系的节点,删除关系就需要DETACH DELETE了。
//删除节点
MATCH (n:AGENCY {name:"航头营业部"}) DELETE n
//有关系的节点是不能直接删除的
MATCH (n:AGENCY {name:"北京市昌平区新龙城"}) DELETE n
//删除节点和关系
MATCH (n:AGENCY {name:"北京市昌平区新龙城"}) DETACH DELETE n
//删除所有节点和关系,慎用!
MATCH (n) DETACH DELETE n
8.索引
在Neo4j中同样也支持索引,对字段做索引可以提升查询速度。
//创建索引语法:
//OPTIONS子句指定索引提供程序和配置。
CREATE [TEXT] INDEX [index_name] [IF NOT EXISTS]
FOR (n:LabelName)
ON (n.propertyName)
[OPTIONS "{" option: value[, ...] "}"]
//示例:
CREATE TEXT INDEX agency_index_bid IF NOT EXISTS FOR (n:AGENCY) ON (n.bid)
//删除索引语法:
DROP INDEX index_name
//示例:
DROP INDEX agency_index_bid
五、Neo4J和SpringBoot整合
Spring Data Neo4j简称SDN,是Spring对Neo4j数据库操作的封装,其底层基于neo4j-java-driver实现。Spring Data Neo4j,官方文档:https://docs.spring.io/spring-data/neo4j/docs/6/reference/html/——SpringBoot版本较高时集成的Neo4j的API规则方法在变化,所以有新老两种版本的差异。
添加对应的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-neo4j</artifactId></dependency>
然后添加对应的配置文件——Old:
# neo4j配置
spring.data.neo4j.uri= bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=123456
New:
spring.data.neo4j.database=neo4j
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=123456
spring.neo4j.uri=neo4j://192.168.150.101:7687
1.Node的操作
然后创建对应的实体对象——Old:
@Data
@NodeEntity("Person")//新版使用@Node("Person"),labels=可以缺省
public class Person {@Id@GeneratedValueprivate Long id;@Property("name")private String name;
}
下面是老的SpringBoot集成时的注解——Old:
@NodeEntity:标明是一个节点实体
@RelationshipEntity:标明是一个关系实体
@Id:实体主键
@Property:实体属性
@GeneratedValue:实体属性值自增
@StartNode:开始节点(可以理解为父节点)
@EndNode:结束节点(可以理解为子节点)
创建实体对象——New:
@Data//新版使用
@Node("Person")//labels=可以缺省
public class Person {@Id@GeneratedValueprivate Long id;@Property("name")private String name;// 定义一个关系@Relationship(type = "DIRECTED", direction = Relationship.Direction.OUTGOING)//INCOMINGprivate List<PersonRelation> directors = new ArrayList<>();public Person() {}public Person(String name) {this.name = name;}
}
下述为Spring Boot目前版本兼容的API——详细参考上述提供的官方文档网址:
@Node:在类级别应用,以指示此类是映射到数据库的候选项。
@Id:在字段级别应用,以标记用于身份目的的字段。
@GeneratedValue:在字段级别应用,以指定应如何生成唯一标识符。@Id
@Property:在字段级别应用,以修改从属性到属性的映射。
@CompositeProperty:在字段级别应用于 Map 类型的属性,该属性应作为复合读回。请参阅复合属性。
@Relationship:在字段级别应用以指定关系的详细信息。
@DynamicLabels:在字段级别应用以指定动态标签的来源。
@RelationshipProperties:在类级别应用,以指示此类作为关系属性的目标。
@TargetNode:应用于批注的类的字段,以从另一端的角度标记该关系的目标。@RelationshipProperties
然后创建对应的Repository接口:
SDN也是遵循了 Spring Data JPA 规范,同时也提供了
Neo4jRepository
,该接口中提供了基本的CRUD操作,我们定义Repository需要继承该接口。JPA使用可参考此文Spring Data JPA 整合。
@Repository//由于自动代理,新API可以不加
public interface PersonRepository extends Neo4jRepository<Person,Long> {
}
然后我们就可以测试Node的创建了
@Autowiredprivate PersonRepository personRepository;@Testvoid contextLoads() {Person person = new Person();person.setName("shenyang");personRepository.save(person);}
创建成功
2.Node关系的维护
创建关系实体——Old:
@Data
@RelationshipEntity(type = "徒弟")
public class PersonRelation implements Serializable {@Id@GeneratedValueprivate Long id;@StartNodeprivate Person parent;@EndNodeprivate Person child;@Propertyprivate String relation;public PersonRelation(Person parent, Person child, String relation) {this.parent = parent;this.child = child;this.relation = relation;}
}
创建关系实体——New:
@Data
@RelationshipProperties
public class PersonRelation implements Serializable {@RelationshipIdprivate Long id;@TargetNode // 相当于@StartNode | @EndNode 由Relationship.Direction.OUTGOING决定private Person parent;private String relation;public PersonRelation(Person parent, String relation) {this.parent = parent;this.relation = relation;}
}
创建对应的Dao持久层——Old。New测试不需要:
@Repository
public interface PersonRelationRepository extends Neo4jRepository<PersonRelation,Long> {}
然后测试——Old:
/*** 节点关系*/@Testvoid nodeRelation(){Person p1 = new Person("唐僧",6666);Person p2 = new Person("孙悟空",5555);Person p3 = new Person("猪八戒",3333);Person p4 = new Person("沙僧",2222);Person p5 = new Person("白龙马",1111);// 维护 关系PersonRelation pr1 = new PersonRelation(p1,p2,"徒弟");PersonRelation pr2 = new PersonRelation(p1,p3,"徒弟");PersonRelation pr3 = new PersonRelation(p1,p4,"徒弟");PersonRelation pr4 = new PersonRelation(p1,p5,"徒弟");personRelationRepository.save(pr1);personRelationRepository.save(pr2);personRelationRepository.save(pr3);personRelationRepository.save(pr4);}
New:
@Testvoid nodeRelation(){Person p1 = new Person("唐僧");Person p2 = new Person("孙悟空");Person p3 = new Person("猪八戒");Person p4 = new Person("沙僧");Person p5 = new Person("白龙马");// 创建 关系PersonRelation pr1 = new PersonRelation(p2,"徒弟");PersonRelation pr2 = new PersonRelation(p3,"徒弟");PersonRelation pr3 = new PersonRelation(p4,"徒弟");PersonRelation pr4 = new PersonRelation(p5,"徒弟");p1.getDirectors().add(pr1);p1.getDirectors().add(pr2);p1.getDirectors().add(pr3);p1.getDirectors().add(pr4);personRepository.saveAll(List.of(p1,p2,p3,p4,p5));}
运行后的效果:
3.JPA自定义方法规则
使用jpa中的规则,进行自定义查询:
Keyword | Sample | Cypher snippet |
---|---|---|
After | findByLaunchDateAfter(Date date) | n.launchDate > date |
Before | findByLaunchDateBefore(Date date) | n.launchDate < date |
Containing (String) | findByNameContaining(String namePart) | n.name CONTAINS namePart |
Containing (Collection) | findByEmailAddressesContains(Collection addresses) findByEmailAddressesContains(String address) | ANY(collectionFields IN [addresses] WHERE collectionFields in n.emailAddresses) ANY(collectionFields IN address WHERE collectionFields in n.emailAddresses) |
In | findByNameIn(Iterable names) | n.name IN names |
Between | findByScoreBetween(double min, double max) findByScoreBetween(Range range) | n.score >= min AND n.score <= max Depending on the Range definition n.score >= min AND n.score <= max or n.score > min AND n.score < max |
StartingWith | findByNameStartingWith(String nameStart) | n.name STARTS WITH nameStart |
EndingWith | findByNameEndingWith(String nameEnd) | n.name ENDS WITH nameEnd |
Exists | findByNameExists() | EXISTS(n.name) |
True | findByActivatedIsTrue() | n.activated = true |
False | findByActivatedIsFalse() | NOT(n.activated = true) |
Is | findByNameIs(String name) | n.name = name |
NotNull | findByNameNotNull() | NOT(n.name IS NULL) |
Null | findByNameNull() | n.name IS NULL |
GreaterThan | findByScoreGreaterThan(double score) | n.score > score |
GreaterThanEqual | findByScoreGreaterThanEqual(double score) | n.score >= score |
LessThan | findByScoreLessThan(double score) | n.score < score |
LessThanEqual | findByScoreLessThanEqual(double score) | n.score <= score |
Like | findByNameLike(String name) | n.name =~ name |
NotLike | findByNameNotLike(String name) | NOT(n.name =~ name) |
Near | findByLocationNear(Distance distance, Point point) | distance( point(n),point({latitude:lat, longitude:lon}) ) < distance |
Regex | findByNameRegex(String regex) | n.name =~ regex |
And | findByNameAndDescription(String name, String description) | n.name = name AND n.description = description |
Or | findByNameOrDescription(String name, String description) | n.name = name OR n.description = description (Cannot be used to OR nested properties) |