1概念
Neo4j是一个NosSql的图形数据库。这里的图不是图片,而是一种数据结构。以图的方式存储数据。
1.1 图论了解
众所周知,图论起源于一个非常经典的问题——柯尼斯堡(Konigsberg)七桥问题。1738年,瑞典数 学家欧拉( Leornhard Euler)解决了柯尼斯堡七桥问题。由此图论诞生,欧拉也成为图论的创始人。
欧拉把问题的实质归于”一笔画”问题,即判断一个图是否能够遍历完所有的边(Edge)而没有重复,而柯 尼斯堡七桥问题则是一笔画问题的一个具体情境。欧拉证明这个问题不成立。
满足一笔画的图满足两个条件:
- 图必须是一个完整图 (每个节点都有一条一条边相连)
- 有零个或二个奇数点
图和节点:
图是一组节点和连接这些节点(边)的关系组成。
图形数据存储在节点和关系所在的属性上。属性是键值对表 示的数据。
在图形理论中,我们可以使用圆表示一个节点 并且可以向里面添加键值对形式的数据
节点关系:
简单关系
此处在两个节点之间创建关系名称“跟随”。 这意味着Profile1 跟随 Profile2。复杂关系
这里节点用关系连接。 关系是单向或双向的。
从XYZ到PQR的关系是单向关系。
从ABC到XYZ的关系是双向关系。
图属性规则:
- 图表示节点,关系和属性中的数据
- 节点和关系都包含属性
- 关系连接节点
- 属性是键值对
- 节点用圆圈表示,关系用方向键表示。
- 关系具有方向:单向和双向。
- 每个关系包含“开始节点”或“从节点” 和 “到节点”或“结束节点”
1.2 知识图谱和图库
知识图谱:
一种基于图的数据结构,由节点(Node)和边(Edge)组成。其中节点即实体,由一个全局唯一的ID标示, 边就是关系用于连接两个节点。通俗地讲,知识图谱就是把所有不同种类的信息(Heterogeneous Information)连接在一起而得到的一个关系网络。知识图谱提供了从“关系”的角度去分析问题的能力。
互联网、大数据的背景下,谷歌、百度、搜狗等搜索引擎纷纷基于该背景,创建自己的知识图谱 Knowledge Graph(谷歌)、知心(百度)和知立方(搜狗),主要用于改进搜索质量。
图数据库:
一般情况下,我们使用数据库查找事物间的联系的时候,只需要短程关系的查询(两层以内的关联)。 当需要进行更长程的,更广范围的关系查询时,就需要图数据库的功能。
而随着社交、电商、金融、零 售、物联网等行业的快速发展,现实世界的事物之间织起了一张巨大复杂的关系网,传统数据库面对这 样复杂关系往往束手无策。因此,图数据库应运而生。
图数据库(Graph database)指的是以图数据结构的形式来存储和查询数据的数据库。
知识图谱中,知识的组织形式采用的就是图结构,所以非常适合用图库进行存储。
图形数据库的优势:
在需要表示多对多关系时,我们常常需要创建一个关联表来记录不同实体的多对多关系。如果两个实体 之间拥有多种关系,那么我们就需要在它们之间创建多个关联表。而在一个图形数据库中,我们只需要 标明两者之间存在着不同的关系。如果希望在两个结点集间建立双向关系,我们就需要为每个方向定义 一个关系。 也就是说,相对于关系型数据库中的各种关联表,图形数据库中的关系可以通过关系属性这 一功能来提供更为丰富的关系展现方式。因此相较于关系型数据库,图形数据库的用户在对现实进行抽 象时将拥有一个额外的武器,那就是丰富的关系。

优势总结:
性能上,对长程关系的查询速度快
擅于发现隐藏的关系,例如通过判断图上两点之间有没有走的通的路径,就可以发现事物间的关联
1.3 什么是Neo4j
Neo4j是一个开源的 无Shcema的 基于java开发的 图形数据库,它将结构化数据存储在图中而不 是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎。程序数据是 在一个面向对象的、灵活的网络结构下,而不是严格、静态的表中,但可以享受到具备完全的事务 特性、企业级的数据库的所有好处。
1.4 Neo4j 模块
Neo4j 主要构建块 节点,属性,关系, 标签,数据浏览器
节点
节点是图表的基本单位。 它包含具有键值对的属性属性
属性是用于描述图节点和关系的键值对
Key =值 其中Key是一个字符串 值可以通过使用任何Neo4j数据类型来表示关系
关系是图形数据库的另一个主要构建块。 它连接两个节点,如下所示。
这里Emp和Dept是两个不同的节点。 “WORKS_FOR”是Emp和Dept节点之间的关系。 因为它表示从Emp到Dept的箭头标记,那么这种关系描述的一样 Emp WORKS_FOR Dept 每个关系包含一个起始节点和一个结束节点。 这里“Emp”是一个起始节点。 “Dept”是端节点。 由于该关系箭头标记表示从“Emp”节点到“Dept”节点的关系,该关系被称为“进入关系”到“Dept”节点。 并且“外向关系”到“Emp”节点。 像节点一样,关系也可以包含属性作为键值对。标签
Label将一个公共名称与一组节点或关系相关联。
节点或关系可以包含一个或多个标签。
我们可以为现有节点或关系创建新标签。
我们可以从现有节点或关系中删除现有标签。 从前面的图中,我们可以观察到有两个节点。 左侧节点都有一个标签:“EMP”,而右侧节点都有一个标签:“Dept”。 这两个节点之间的关系,也有一个签:“WORKS_FOR”
注: Neo4j将数据存储在节点或关系的属性中。数据浏览器
1.5 Neo4j应用场景
社交媒体和社交网络
当使用图形数据库为社交网络应用程序提供动力时,可以轻松利用社交关系或根据活动推断关系。 查询社区聚类分析,朋友的朋友推荐,影响者分析,共享和协作关系分析等 。推荐引擎和产品推荐系统
图形驱动的推荐引擎通过实时利用多种连接,帮助公司个性化产品,内容和服务。 内容和媒体推荐,图形辅助搜索引擎,产品推荐,专业网络,社会推荐。身份和访问管理
使用图形数据库进行身份和访问管理时,可以快速有效地跟踪用户,资产,关系和授权。 查询访问管理,资产来源,数据所有权,身份管理,互连组织,主数据,资源授权 。金融反欺诈多维关联分析场景
通过图分析可以清楚地知道洗钱网络及相关嫌疑,例如对用户所使用的帐号、发生交易时的IP地址、 MAC地址、手机IMEI号等进行关联分析。
2 安装
2.1 linux
(1). 下载到本地然后利用lrzsz或者ftp工具上传
或者 wget https://neo4j.com/artifact.php?name=neo4j-community-3.5.17-unix.tar.gz
(2). 解压 tar -xvf neo4j-community-3.5.17.tar
(3). 修改配置文件 neo4j.conf vi conf/neo4j.conf 主要是修改 允许远程访问的地址 把对应的注释打开即可 dbms.connectors.default_listen_address=0.0.0.0
(4).开放对应的访问端口 默认要开放7474 和 7687
firewall-cmd --zone=public --add-port=7474/tcp --permanent
firewall-cmd --zone=public --add-port=7687/tcp --permanent
systemctl reload firewalld
(5).启动
./bin/neo4j start
(6).使用浏览器 访问服务器上的 neo4j http://192.168.211.133:7474
默认的账号是 neo4j 密码 neo4j 这里第一次登录的话会要求修改密码
2.2 windows
(1).从https://neo4j.com/download-center/#community 下载最新的Neo4j Server安装文件 可以看到 neo4J 软件 exe 或 zip 格式的所有版本
(2).下载 Neo4j 3.5.17 (zip)
(3).解压
(4).修改配置文件 dbms.connectors.default_listen_address=0.0.0.0
(5) . 通过 neo4j.bat install-service 安装neo4j服务
注意的问题 如果是4.0 以及以上版本需要jdk11
修改文件 bin/neo4j.ps1 Import-Module “neo4j的主目录\bin\Neo4j-Management.psd1”
或者相对路径”.\Neo4j-Management.psd1”
(6).neo4j.bat启动 neo4j.bat start
(7).使用浏览器 访问服务器上的 neo4j http://127.0.0.1:7474
默认的账号是 neo4j 密码 neo4j 这里第一次登录的话会要求修改密码
3 Neo4j CQL
CQL代表Cypher查询语言。 像关系型数据库具有查询语言SQL,Neo4j使用CQL作为查询语言。
命令:
命令 | 作用 |
---|---|
create | 创建节点,关系和属性 |
match | 检索有关节点,关系和属性数据 |
return | 返回查询结果 |
where | 提供条件过滤检索MATCH数据 |
delete | 删除节点和关系 |
remove | 删除节点和关系的属性 删除节点和关系的标签 |
set | 添加或更新标签 |
order by | 对结果排序 |
skip limit | 分页 |
distinct | 去重 |
基于这个图创建一个图谱
3.1 Create
CREATE (
<node-name>:<label-name>
[{
<property1-name>:<property1-Value>
........
<propertyn-name>:<propertyn-Value>
}]
)
[…]表示可选项
< node-name> 它是我们将要创建的节点名称。节点名称没什么含义,主要是标签名,标签名可以有多个
< label-name> 它是一个节点标签名称
< property1-name>…< propertynname> 属性是键值对。 定义将分配给创建节点的属性的名称
< property1-value>…< propertynvalue> 属性是键值对。 定义将分配给创建节点的属性的值
例:
CREATE (person:Person)
CREATE (person:Person {cid:1,name:"范闲",age:24,gender:0,character:"A",money:1000});
CREATE (person:Person {cid:2,name:"林婉儿",age:20,gender:1,character:"B",money:800});
CREATE (person:Person {cid:3,name:"庆帝",age:49,gender:0,character:"A",money:8900});
3.2 match return
MATCH
(
<node-name>:<label-name>
{}
)
RETURN
<node-name>.<property1-name>,
...
<node-name>.<propertyn-name>
< node-name> 它是我们将要创建的节点名称。
< label-name> 它是一个节点标签名称
{} 可以放匹配条件
< property1-name>…< propertynname> 属性是键值对。 定义将分配给创建节点的属性的名 称
MATCH (person:Person) return person
MATCH (person:Person) return person.name,person.age
match(p:Person {name:"庆帝"}) return p
3.3 关系创建
两个节点之间可以创建多个关系,可以有属性,也可以没有属性。就像节点一样。属性是为了描述节点,节点关系的信息。
关系是有方向的。创建的时候不能同时存在两个方向的关系。比如
create (p1)<-[rr:friend]->(p2)
但是 可以
create (p1)-[rr:friend]->(p2)
或者
create (p1)<-[rr:friend]-(p2)
使用现有节点创建没有属性的关系
MATCH (<node1-name>:<node1-label-name>),(<node2-name>:<node2-label-name>)
CREATE (<node1-name>)
-[<relationship-name>:<relationship-label-name>]->
(<node2-name>)
< noode1-name> 它用于创建关系的“From Node”的名称。
< node1-label-name> 它用于创建关系的“From Node”的标签名称。
< node2-name> 它用于创建关系的“To Node”的名称。
< node2-label-name> 它用于创建关系的“To Node”的标签名称。
< relationship-name> 这是一个关系的名称。
< relationship-label-name> 它是一个关系的标签名称。
match(p1:Person{name:"北齐圣女海棠朵朵"}),(p2:Person{name:"北齐小皇帝战豆豆"})
create(p1)<-[:Friend]->(p2)
创建关系
match(person:Person {name:"范闲"}) ,(person2:Person {name:"林婉儿"})
create(person)-[r:Couple]->(person2);
查询关系
match p = (person:Person {name:"范闲"})-[r:Couple]->(person2:Person) return p
match (p1:Person {name:"范闲"})-[r:Couple]-(p2:Person) return p1,p2
match (p1:Person {name:"范闲"})-[r:Couple]-(p2:Person) return r
使用现有节点创建有属性的关系
MATCH (<node1-label-name>:<node1-name>),(<node2-label-name>:<node2-name>)
CREATE(<node1-label-name>)
-[<relationship-label-name>:<relationship-name> {<define-properties-list>}]->
(<node2-label-name>)
RETURN <relationship-label-name>
其中<define-properties-list> 是分配给新创建关系的属性(名称 - 值对)的列表。
{
<property1-name>:<property1-value>,
<property2-name>:<property2-value>,
...
<propertyn-name>:<propertyn-value>
}
match(person:Person {name:"范闲"}),(person2:Person {name:"林婉儿"})
create(person)-[r:Couple{mary_date:"1802-12-12",price:55000}]->(person2)
return r;
也可以不返回。
使用新节点创建没有属性的关系
CREATE
(<node1-label-name>:<node1-name>)
-[<relationship-label-name>:<relationship-name>]->
(<node1-label-name>:<node1-name>)
create(person1:Person {cid:4,name:"长公主",age:49,gender:1,character:"A",money:5000})
-[r:Friend]->
(person2:Person {cid:7,name:"九品射手燕小乙",age:48,gender:0,character:"B",money:1000})
create(person1:Person{cid:8,name:"二皇子",age:21,gender:0,character:"B",money:900})
-[r:Friend]->
create(person2:Person{cid:9,name:"靖王世子",age:20,gender:0,character:"C",money:890})
使用新节点创建有属性的关系
CREATE
(<node1-label-name>:<node1-name>{<define-properties-list>})
-[<relationship-label-name>:<relationship-name>{<define-properties-list>}]->
(<node1-label-name>:<node1-name>{<define-properties-list>})
create (person1:Person {cid:9,name:"靖王世子",age:23,gender:0,character:"A",money:3000})
-[r:Friend {date:"1800-10-19"}]->
(person2:Person {cid:8,name:"二皇子",age:24,gender:0,character:"B",money:6000})
3.4 关系和节点的属性可以使用的类型
boolean 它用于表示布尔文字:true, false。
byte 它用来表示8位整数。
short 它用于表示16位整数。
int 它用于表示32位整数。
long 它用于表示64位整数。
float 浮点数用于表示32位浮点数。
double Double用于表示64位浮点数。
char Char用于表示16位字符。
String 字符串用于表示字符串。
3.5 CREATE创建多个标签
一个节点可以有多个标签。可以在创建的时候指定多个标签,也可以在后面添加或删除。
CREATE (<node-name>:<label-name1>:<label-name2>.....:<label-namen>)
如:
CREATE (person:Person:Beauty:Picture {cid:20,name:"小美女"})
3.6 Where
where 子句中可以像mysql中使用 and,or,not等条件连接符
还有 =,!=,<>,>,<等比较符
MATCH (person:Person)
WHERE person.name = '范闲' OR person.name = '靖王世子'
RETURN person
可以只查询部分字段,并且给别名 使用as 就像mysql那样。必须要有as
MATCH (person:Person)
WHERE person.name = '范闲' OR person.name = '靖王世子'
RETURN person.name as fullName,person.age
3.7 delete和remove
delete可以删除节点,以及节点之间的关系。如果节点之间存在关系,删除之前必须先删除节点之间的关系。
首先创建
create(p1:Person {name:"r1"}) <-[rr:friend]- (p2:Person {name:"r2"})
然后删除关系:
match(p1:Person)-[rr:friend]->(p2:Person) delete rr
然后删除节点
match(p1:Person) where p1.name='r1' or p1.name='r2' delete p1
remove是删除节点或关系的标签,节点或关系的属性。
首先创建
create(p:Person:beauty:small {cid:60,name:"小美女",age:18})
然后删除age属性
match(p:Person)where p.name="小美女" remove p.age
删除small标签
match(b:beauty {name:"小美女"})remove b:small
match(b:beauty {name:"小美女"})remove b:beauty
3.8 set
向现有节点或关系添加新属性
更新属性值
MATCH (p:Person {cid:1})
SET p.money = 3456,p.age=25
如果有这个属性就更新属性值。如果没有就添加属性。相当于upInsert
3.9 order by ,skip,limit,distinct
order by根据属性进行排序必须放在return后面
默认是asc升序
match(p:Person) return p.cid,p.name,p.age order by p.money desc
分页
match(p:Person) return p.cid,p.name,p.age order by p.money desc skip 2 limit 2
去重
match(p:Person) return distinct(p.age)
#这种是无效的
match(p:Person) return distinct(p.age),p.name
3.10 in
neo4j也可以使用in操作符
match (p:Person) where p.name in ["范闲","林婉儿"] return p
3.11 关系查询
MATCH p=()-[r:Friend]->() RETURN p LIMIT 25
查询多个关系:
MATCH p=()-[r:Friend|Couple]->() RETURN p LIMIT 25
查询范闲的朋友
MATCH p=({name:"范闲"})-[r:Friend]->() return p
4 CQL高级
4.1 字符串函数
我使用的是4.1.3版本的neo4j。之前的版本是upper,lower。
函数名不区分大小写
toUpper它用于将所有字母更改为大写字母。
toLower它用于将所有字母改为小写字母。
substring它用于获取给定String的子字符串。
replace它用于替换一个字符串的子字符串。
MATCH (p:Person) RETURN ID(p),toLower(p.character)
match(p:Person) return p.character,lower(p.character),p.name,substring(p.name,2),replace(p.name,"子","z
i")
4.2 聚合函数
count,max,min,sum,avg
count不计算为null的列。如果使用p.property的时候
MATCH (p:Person)
RETURN MAX(p.money),SUM(p.money),min(p.money),count(*),count(p),count(p.money)
MATCH (p:Person)
where p.money > 890 RETURN MAX(p.money),SUM(p.money),min(p.money)
4.3 关系函数
不区分大小写。
STARTNODE 它用于知道关系的开始节点。
ENDNODE 它用于知道关系的结束节点。
ID 它用于知道关系的ID。
TYPE 它用于知道字符串表示中的一个关系的TYPE。比如Couple,Friend
查询和林婉儿是夫妻关系的头节点。
match p = (:Person {name:"林婉儿"})-[r:Couple]-(:Person) RETURN STARTNODE(r)
查出来是name=范闲节点
因为关系表达式使用的是 -[r:Couple]-所以是不区分方向的。若要是以林婉儿为开始节点,则可以-[r:Couple]->。
查询和林婉儿是夫妻关系的头节点,并且林婉儿是开始节点
match p = (:Person {name:"林婉儿"})-[r:Couple]->(:Person)RETURN STARTNODE(r)
这样是没结果的。
4.4 shorthestPath 函数返回最短的path
语法:
MATCH p=shortestPath( (node1)-[*]-(node2) )
RETURN length(p), nodes(p)
不要这个nodes函数也可以
一个节点到另一个节点的最短路径。
如图,范闲到长公主的最短路径为1。
范闲到二皇子的最短路径为2。
那么九品射手到靖王世子的最短路径是多少呢?
我们可以看一下从 九品射手到晋王世子可以通过那些途径认识靖王世子。 这个*号必须指定
match p=(:Person {name:"九品射手燕小乙"})-[*]-(:Person {name:"靖王世子"})return p
然后使用shortestPath
match p=shortestpath((:Person {name:"九品射手燕小乙"})-[*]-(:Person {name:"靖王世子"}))return p
4.5 CQL多深度关系节点
with
查询三层级关系节点如下:with可以将前面查询结果作为后面查询条件
比如我们查询两层关系节点:
关系里面可以指定是什么关系进行过滤,也可以不指定
关系可以是无向的也可以是有向的。
match(p:Person {name:"范闲"})-[re]->(p2:Person) return p,p2
#或者这样写
match m=(p:Person {name:"范闲"})-[re]->(p2:Person) return m
match m=(:Person {name:"范闲"})-[re]->(:Person) return m
match(p:Person {name:"范闲"})-[re:Couple]->(p2:Person) return p,p2
with可以将前面查询结果作为后面查询条件。
比如可以通过第二层的关系认识第三层的那些人。
p2就是第二层所认识的人,然后拿p2作为查询条件。相当于mysql中的一个子查询吧。
match(p1:Person {name:"范闲"})-[re]->(p2:Person) with p1,re,p2 match(p2)-[re2]-(p3) return p1,p2,p3
如果只通过儿子关系去认识第二层的朋友
match(p1:Person {name:"范闲"})-[re:Son]->(p2:Person) with p1,re,p2 match(p2:Person)-[re2:Friend]-(p3:Person) return p1,p2,p3
林婉儿认识第三层的人
match(p1:Person {name:"林婉儿"})-[re]->(p2:Person) with p1,re,p2 match(p2:Person)-[re2]-(p3:Person) return p1p2,p3
只认识第二层的朋友
match(p1:Person {name:"林婉儿"})-[re]->(p2:Person) with p1,re,p2 match(p2:Person)-[re2:Friend]-(p3:Person) return p1,p2,p3
直接拼接
前面是使用with。这里不适用with直接拼接。其实效果是一样的。
match(p1:Person {name:"林婉儿"})-[re]->(p2:Person)-[re2:Friend]-(p3:Person) return p1,p2,p3
match(p1:Person {name:"林婉儿"})-[re]-(p2:Person)-[re2:Friend]-(p3:Person) return p1,p2,p3
深度运算符
前面两种使用with和直接拼接太繁琐了。
可以使用深度运算符,但就是无法控制准确的精度。
关系标签Type可以省略。
可变数量的关系->节点可以使用-[:TYPE*minHops..maxHops]-。
match data=(na:Person{name:"范闲"})-[*1..2]-(nb:Person) return data
match data=(na:Person{name:"范闲"})-[*1..3]-(nb:Person) return data
match data=(na:Person{name:"范闲"})-[:Son*1..3]-(nb:Person) return data
5 事务
Neo4j支持ACID特性。
(1)所有对Neo4j数据库的数据修改操作都必须封装在事务里。
(2)默认的isolation level是READ_COMMITTED。
(3)死锁保护已经内置到核心事务管理 。 (Neo4j会在死锁发生之前检测死锁并抛出异常。在异常抛出之前,事务会被标志为回滚。当事务结束时,事务会释放它所持有的锁,则该事务的锁所引起的死锁也就是解 除,其他事务就可以继续执行。当用户需要时,抛出异常的事务可以尝试重新执行)
(4)除特别说明,Neo4j的API的操作都是线程安全的,Neo4j数据库的操作也就没有必要使用外部的同步方 法。
6 索引
Neo4j CQL支持节点或关系属性上的索引,以提高应用程序的性能。
可以为具有相同标签名称的属性上创建索引。
可以在MATCH或WHERE等运算符上使用这些索引列来改进CQL 的执行。
Neo4j的索引也是使用btree来实现的。
单一索引:
CREATE INDEX [indexName] ON :Label(property)
CREATE INDEX idx_p_name ON :Person(name)
#包含就像mysql like会使用索引
match(p:Person) where p.name contains "皇" return p
#左匹配 会使用索引
match(p:Person) where p.name starts with "北" return p
#右匹配会使用索引
match(p:Person) where p.name ends with "闲" return p
复合索引:
CREATE INDEX ON :Label(property1,property2)
CREATE INDEX ON :Person(age, gender)
全文索引:
neo4j的全文搜索使用的是lucene.
call db.index.fulltext.listAvailableAnalyzers命令可以查看有那些解析器,没有中文的
之前的常规模式索引只能对字符串进行精确匹配或者前后缀索引(starts with,ends with,contains),全文 索引将标记化索引字符串值,因此它可以匹配字符串中任何位置的术语。索引字符串如何被标记化并分 解为术语,取决于配置全文模式索引的分析器。索引是通过属性来创建,便于快速查找节点或者关系。
创建和配置全文模式索引
使用db.index.fulltext.createNodeIndex和db.index.fulltext.createRelationshipIndex创建全文模式索 引。在创建索引时,每个索引必须为每个索引指定一个唯一的名称,用于在查询或删除索引时引用相关 的特定索引。然后,全文模式索引分别应用于标签列表或关系类型列表,分别用于节点和关系索引,然 后应用于属性名称列表。
call db.index.fulltext.createNodeIndex("索引名",[Label,Label],[属性,属性])
call db.index.fulltext.createNodeIndex("nameAndDescription",["Person"],["name",
"description"])
call db.index.fulltext.queryNodes("nameAndDescription", "范闲") YIELD node, score
RETURN node.name, node.description, score
call db.index.fulltext.queryNodes("索引名", "query") YIELD node, score
RETURN node.name, node.description, score
query 也会进行分词 类似于es的 string查询。score是打分情况
查看和删除索引:
call db.indexes 或者 :schema 或者call db.indexes()
DROP INDEX ON :Person(name)
DROP INDEX ON :Person(age, gender)
call db.index.fulltext.drop("nameAndDescription")
唯一约束:
避免重复记录。 强制执行数据完整性规则。
创建唯一约束的时候也会像mysql那样创建索引。所以如果一个属性创建了索引,再创建唯一约束的时候就会报异常。
CREATE CONSTRAINT ON (变量:<label_name>) ASSERT 变量.<property_name> IS UNIQUE
CREATE CONSTRAINT ON (person:Person) ASSERT person.name IS UNIQUE
删除唯一约束
DROP CONSTRAINT ON (cc:Person) ASSERT cc.name IS UNIQUE
属性存在约束(企业版可用)
CREATE CONSTRAINT ON (p:Person) ASSERT exists(p.name)
查看约束
call db.constraints
或者 :schema
7 备份与恢复
我们使用的是社区版,备份和恢复的时候需要先关闭neo4j服务。企业版可以服务运行的时候备份和恢复。
备份:
- 停止服务
./bin/neo4j stop
- 备份
./bin/neo4j-admin dump --database=graph.db --to=/root/qyn.dump
我用的是4.1.3版本的datbase=neo4j
- 恢复 如果服务在运行,也需要先停止
我用的是4.1.3版本的datbase=neo4j
./bin/neo4j-admin load --database=graph.db --from=/root/qyn.dump --force
- 启动
./bin/neo4j start
linux系统备份出现警告 dump的时候
WARNING: Max 1024 open files allowed, minimum of 40000 recommended. See the Neo4j manual
修改 /etc/security/limits.conf配置文件
在最后面加上
* soft nofile 65535
* hard nofile 65535
然后重启linux服务就可以了。
8 优化
8.1 增加服务器内存
# java heap 初始值
dbms.memory.heap.initial_size=1g
# java heap 最大值,一般不要超过可用物理内存的80%
dbms.memory.heap.max_size=8g
# pagecache大小,官方建议设为:(总内存dbms.memory.heap.max_size)/2,
dbms.memory.pagecache.size=4g
8.2 数据预热
neo4j刚启动数据是冷的。可以执行以下cql语句进行数据预热。
MATCH (n)
OPTIONAL MATCH (n)-[r]->()
RETURN count(n.name) + count(r);
8.3 查看执行计划
我们在查询cql前面加上explain或者profile看着执行计划。profile更详细一些,建议使用profile。
profile match (p:Person) where p.name in ["范闲","林婉儿"] return p
主要看三个指标:
- 是否使用索引
- estimated rows: 需要被扫描行数的预估值
- 数的预估值 dbhits: 实际运行结果的命中绩效
后面两项数值越小越好。
9 Neo4j程序访问
9.1 数据访问
有两种方式:
- 嵌入式数据库,不需要单独将neo4j单独部署,直接嵌入程序使用
- 服务器模式(通过REST的访问)
它是由应用程序的性质(neo4j是独立服务器 还是和程序在一起),性能,监控和数据安全性来决定架构 选择。
嵌入式数据库:
- 使用Java作为我们项目的编程语言时
- 应用程序是独立的
- 程序追求很高的性能
服务器模式:
Neo4j Server是相互操作性,安全性和监控的最佳选择。 实际上,REST接口允许所有现代平台和编程 语言与它进行互操作。 此外,作为独立应用程序,它比嵌入式配置更安全(客户端中的潜在故障不会影响服务器),并且更易于监控。 如果我们选择使用这种模式,我们的应用程序将充当Neo4j服务器的客 户端。要连接到Neo4j服务器,可以使用任何编程语言的REST 访问数据库。
9.2 嵌入方式访问
引入依赖:
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>4.1.0</version>
</dependency>
我这里使用的是4版本以上的,索引使用api方式有点不同
官方文档:【https://neo4j.com/docs/java-reference/current/java-embedded/hello-world/】
public class EmbeddedNeo4jAdd {
private final static File dbDir = new File("embedded-neo4j/src/neo4j");
private final static String DEFAULT_DB_NAME = "neo4j";
public static void main(String[] args) {
DatabaseManagementService databaseManagementService = new DatabaseManagementServiceBuilder(dbDir).build();
GraphDatabaseService graphDatabaseService = databaseManagementService.database(DEFAULT_DB_NAME);
try(Transaction tx = graphDatabaseService.beginTx()){
Node node1 = tx.createNode();
node1.addLabel(new MyLabel("Person"));
node1.setProperty("cid",1);
node1.setProperty("name","张三");
node1.setProperty("age",23);
node1.setProperty("money",8900);
String cql = "create(:Person {cid:2,name:'李四',age:25,money:11000})";
tx.execute(cql);
tx.commit();
}
}
}
public class EmbeddedNeo4jQuery {
private final static File dbDir = new File("embedded-neo4j/src/neo4j");
private final static String DEFAULT_DB_NAME = "neo4j";
public static void main(String[] args) {
DatabaseManagementService databaseManagementService = new DatabaseManagementServiceBuilder(dbDir).build();
GraphDatabaseService graphDatabaseService = databaseManagementService.database(DEFAULT_DB_NAME);
try(Transaction tx = graphDatabaseService.beginTx()){
String cql = "match(pp:Person) where pp.age < $age return pp";
Map<String,Object> arg = new HashMap<>(2);
arg.put("age",30);
Result result = tx.execute(cql,arg);
while (result.hasNext()) {
Map<String, Object> next = result.next();
for (Map.Entry<String, Object> e : next.entrySet()) {
Node nd = (Node) e.getValue();
System.out.println(e.getKey()+"_"+nd.getProperty("name")+":"+nd.getProperty("money"));
}
}
tx.commit();
}
}
}
4以下的版本:
public class EmbeddedNeo4jAdd {
private static final File databaseDirectory = new File( "target/graph.db" );
public static void main(String[] args) {
GraphDatabaseService graphDb = new
GraphDatabaseFactory().newEmbeddedDatabase(databaseDirectory);
System.out.println("Database Load!");
Transaction tx = graphDb.beginTx();
Node n1 = graphDb.createNode();
n1.setProperty("name", "张三");
n1.setProperty("character", "A");
n1.setProperty("gender",1);
n1.setProperty("money", 1101);
n1.addLabel(new Label() {
@Override
public String name() {
return "Person";
}
});
String cql = "CREATE (p:Person{name:'李四',character:'B',gender:1,money:21000})";
graphDb.execute(cql);
tx.success();
tx.close();
System.out.println("Database Shutdown!");
graphDb.shutdown();
}
}
public class EmbeddedNeo4jQueryAll {
private static final File databaseDirectory = new File( "target/graph.db" );
public static void main(String[] args) {
GraphDatabaseService graphDb = new
GraphDatabaseFactory().newEmbeddedDatabase(databaseDirectory);
System.out.println("Database Load!");
String cql = "MATCH (a:Person) where a.money < $money return a";
Map<String, Object> paramerters = new HashMap<String, Object>();
paramerters.put("money", 25000);
Transaction tx = graphDb.beginTx();
Result result = graphDb.execute(cql,paramerters);
while (result.hasNext()) {
Map<String, Object> row = result.next();
for (String key : result.columns()) {
Node nd = (Node) row.get(key);
System.out.printf("%s = %s:%s%n", key,
nd.getProperty("name"),nd.getProperty("money"));
}
}
tx.success();
tx.close();
System.out.println("Database Shutdown!");
graphDb.shutdown();
}
}
9.3 服务器模式
引入依赖:
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-bolt-driver</artifactId>
<version>3.2.17</version>
</dependency>
demo:
import org.neo4j.driver.*;
import java.util.Map;
import static org.neo4j.driver.Values.parameters;
public class UseNeo4jDriver {
public static void main(String[] args) {
try(Driver driver = GraphDatabase.driver("bolt://127.0.0.1:7687",AuthTokens.basic("neo4j","123456"));
Session session = driver.session();){
query1(session);
query2(session);
}
}
private static void query1(Session session){
String cql = "match(p:Person) return p.name as name,p.money as money,p.age as age";
Result result = session.run(cql);
while (result.hasNext()) {
Record next = result.next();
Map<String, Object> v = next.asMap();
for (Map.Entry<String, Object> e : v.entrySet()) {
System.out.print(e.getKey()+":"+e.getValue());
}
System.out.println();
//System.out.println("name:"+next.get("name").asString()+",money:"+next.get("money").asInt()+",age:"+next.get("age").asInt());
}
}
private static void query2(Session session){
System.out.println("--------------分割线-------------");
String cql = "match p=(p1:Person {name:$startName})-[*1..4]-(p2:Person {name:$endName}) return p";
Result result = session.run(cql,parameters("startName","靖王世子","endName","九品射手燕小乙"));
while(result.hasNext()){
Record next = result.next();
System.out.println(next);
}
}
}
运行结果:
name:范闲money:1000age:24
name:林婉儿money:800age:20
name:庆帝money:8900age:49
name:二皇子money:900age:21
name:靖王世子money:890age:20
name:小美女money:nullage:null
name:长公主money:1000age:43
name:九品射手燕小乙money:700age:40
name:北齐圣女海棠朵朵money:1100age:20
--------------分割线-------------
Record<{p: path[(4)<-[2:Friend]-(3), (3)-[3:Son]->(2), (2)-[5:Brother]->(6), (6)-[7:Friend]->(7)]}>
9.4 SpringBoot整合Neo4j
参考官方文档:【https://docs.spring.io/spring-data/neo4j/docs/5.3.4.RELEASE/reference/html/#reference】
配置文件:
spring:
data:
neo4j:
password: 123456
username: neo4j
uri: bolt://127.0.0.1:7687
#uri: http:/127.0.0.1:7474
#file: springboot-neo4j/src/neo4j #嵌入式
repository:
我这里的neo4j的服务版本为4.1.3所以只支持param方式
public interface PersonRepository extends Neo4jRepository<Person,Long> {
/**
* 根据姓名删除 spring自动生成
* @param name
*/
void deleteByName(String name);
/**
* 根据姓名查询 spring自动生成
* @param name
* @return
*/
Person queryByName(String name);
/**
* 根据姓名列表查询
* @param names
* @return
*/
@Query("match(p:Person) where p.name in $names return p")
List<Person> queryByNames(@Param("names") List<String> names);
/**
* 根据姓名查询 spring自动生成
* @param names
* @return
*/
List<Person> queryByNameIn(List<String> names);
/**
* 查询最短路径
* @param startName
* @param endName
* @return
*/
@Query("match p=shortestPath((:Person {name:$startName})-[*1..4]-(:Person{name:$endName})) return p")
List<Person> shortestPath(@Param("startName") String startName,@Param("endName") String endName);
/**
* 创建节点关系的时候 关系的label使用动态参数$relationLabel 会报错(官方给的文档也不能,必须指定),写死就没事,关系的属性值可以使用map传参
* 实际使用的时候可以根据参数拼接cql
*
* @param startName
* @param endName
* @param relationLabel
* @param relationProperties
*/
@Query("match(p1:Person {name:$startName}),(p2:Person {name:$endName}) create (p1)-[:Friend $relationProperties]->(p2)")
void buildRelationshipWithProperties(@Param("startName") String startName, @Param("endName") String endName, @Param("relationLabel") String relationLabel, @Param("relationProperties") Map<String,Object> relationProperties);
}
entity:
@NodeEntity
public class Person {
@Id
@GeneratedValue
private Long id;
private int cid;
@Property
private String name;
private String character;
private double money;
private int gender;
private int age;
private String description;
/**
* type为关系类型 direction为关系方向
*/
@Relationship(type = "Friend",direction = Relationship.OUTGOING)
private Set<Person> relationships;
}
启动测试类:
@SpringBootApplication
public class SpringBootNeo4jApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootNeo4jApp.class, args);
PersonRepository personRepository = context.getBean(PersonRepository.class);
/*queryAll(personRepository);
System.out.println("----------分割线------------");
queryByName(personRepository);
System.out.println("----------分割线------------");
queryByNames(personRepository);
System.out.println("----------分割线------------");
queryByNames2(personRepository);
System.out.println("----------分割线------------");
shortestPath(personRepository);
System.out.println("----------分割线------------");*/
buildRelationship(personRepository);
}
private static void queryAll(PersonRepository personRepository){
//查询所有节点
Iterable<Person> all = personRepository.findAll();
all.forEach(System.out::println);
}
private static void queryByName(PersonRepository personRepository){
Person person = personRepository.queryByName("范闲");
System.out.println(person);
}
private static void queryByNames(PersonRepository personRepository){
List<String> names = Arrays.asList("王启年","范闲","林婉儿");
List<Person> people = personRepository.queryByNames(names);
System.out.println(people);
}
private static void queryByNames2(PersonRepository personRepository){
List<String> names = Arrays.asList("庆帝","范闲","林婉儿");
List<Person> people = personRepository.queryByNameIn(names);
System.out.println(people);
}
private static void shortestPath(PersonRepository personRepository){
List<Person> people = personRepository.shortestPath("九品射手燕小乙", "靖王世子");
System.out.println(people);
}
private static void save(PersonRepository personRepository){
//新增节点
Person person = new Person();
person.setName("王启年");
person.setAge(30);
person.setCharacter("C");
person.setGender(0);
person.setMoney(1300);
person.setCid(10);
personRepository.save(person);
}
private static void buildRelationship(PersonRepository personRepository){
Map<String,Object> properties = new HashMap<>();
properties.put("date", "1800-12-09");
personRepository.buildRelationshipWithProperties("范闲","王启年","Friend",properties);
}
private static void createNode(PersonRepository personRepository){
//新增 张三,李四节点 并建立朋友关系
Person person = new Person();
person.setName("张三");
person.setAge(30);
person.setCharacter("C");
person.setGender(0);
person.setMoney(1300);
person.setCid(10);
//新增节点
Person person2 = new Person();
person2.setName("李四");
person2.setAge(43);
person2.setCharacter("B");
person2.setGender(0);
person2.setMoney(2300);
person2.setCid(10);
Set<Person> relation = new HashSet<>(2);
relation.add(person2);
person.setRelationships(relation);
personRepository.save(person);
}
}