Neo4j介绍
个人学习笔记,不足之处望海涵和指出。
官网:https://neo4j.com/
官网文档:https://neo4j.com/docs/cypher-manual/current/introduction/
为什么要使用Neo4j
图数据库主要用于存储更多的连接数据(因为图结构相比其他数据结构而言,能保存更多的数据间的关系)。
如果我们使用 RDBMS 数据库来存储更多连接的数据,那么它们不能提供用于遍历大量数据的适当性能。 在这些情况下,Graph Database 提高了应用程序性能。
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查询语言命令是人性化的可读格式,非常容易学习
- 使用简单而强大的数据模型
- 它不需要复杂的连接来检索连接的相关数据,因为它很容易检索它的相邻节点或关系细节没有连接或索引
Neo4j - 数据模型
Neo4j属性图数据模型:Neo4j图数据库遵循属性图模型来存储和管理其数据。
属性图模型规则:
- 表示节点,关系和属性中的数据
- 节点和关系都包含属性
- 关系连接节点
- 属性是键值对
- 节点用圆圈表示,关系用方向键表示。
- 关系具有方向:单向和双向。
- 每个关系包含“开始节点”或“从节点”和“到节点”或“结束节点”
在Neo4j中,关系是有方向性的。如果我们尝试创建没有方向的关系,那么Neo4j会抛出一个错误消息。
Neo4j图数据库将其所有数据存储在节点和关系中。我们不需要任何额外的RDBMS数据库或无SQL数据库来存储Neo4j数据库数据。它以图形的形式存储其数据的本机格式。
Neo4j使用本机GPE(图形处理引擎)引擎来使用它的本机图存储格式。
图形数据库数据模型的主要构建块是:
- 节点
- 关系
- 属性
Neo4j图数据库主要有以下构建块
- 节点
- 属性
- 关系
- 标签
- 数据浏览器
节点
节点用来表示一个实体记录,在Neo4j中节点可以包含多个属性和多个标签
- 节点是主要元素
- 节点通过关系连接到其他节点
- 节点可以具有一个或多个属性(存储为键值对的属性)
- 节点有一个或多个标签,用于描述其在图的作用
属性
属性是用于描述图节点和关系的键值对,其中key是一个字符串,值可以通过使用任何Neo4j数据类型表示
- 属性是命名值,其中键是字符串
- 属性可以被索引和约束
- 可以从多个属性创建复合索引
关系
用来连接两个节点,关系也称为图论的边,其始端和末端必须是节点。
标签
Label将一个公共名称与一组节点或关系相关联。
节点可以包含零或多个标签
关系必须包含一个标签
数据浏览器
一旦安装Neo4j,就可以访问Neo4jj数据浏览器,url:host/browser
初始username:neo4j
初始password:neo4j
文件介绍
- bin目录:包括各个启动脚本其中:neo4j.bat是window下的启动脚本cypher-shell.bat 是CQL的命令窗口
- certificates目录:身份认证
- conf目录:配置文件
- data目录:数据文件
- import目录:导入文件(csv后缀的文件)
- lib目录:jar包
- logs:日志
启动Neo4j
neo4j.bat console
1.将Neo4j作为控制台应用程序运行:<NEO4J_HOME>\bin\neo4j console
console:直接启动Neo4j服务
2.将Neo4j作为服务安装:<NEO4J_HOME>\bin\neo4j install-service
install-service | uninstall-service | update-service :安装/卸载/更新Neo4j服务
start/stop/restar/status:启动/停止/重启/状态
默认的host是bolt://localhost:7687,默认的用户是neo4j,默认的密码是:neo4j
Neo4j - CQL简介
CQL代表Cypher查询语言。 像Oracle数据库具有查询语言SQL,Neo4j具有CQL作为查询语言。
Neo4j CQL特点
- 它是Neo4j图形数据库的查询语言。
- 它是一种声明性模式匹配语言
- 它遵循SQL语法。
- 它的语法是非常简单且人性化、可读的格式。
CQL使用
CQL命令 | 用法 |
---|---|
CREATE | 创建节点、关系和属性 |
MATCH | 检索节点、关系和属性数据 |
RETURN | 返回查询数据 |
WHERE | 提供条件过滤检索数据 |
DELETE | 删除节点和关系 |
REMOVE | 删除节点和关系的属性 |
ORDER BY | 排序检索数据 |
SET | 添加或更新标签 |
创建数据
创建的所有节点和关系都自带id
语法结构
CREATE ([result-name]:< label-name > [{key1:value1,key2:value2…}])
动态Cypher
private Boolean insertDicBatch(String dictTypeKey,Long dictTypeId,List<JSONObject> specialDictBodys){
if (StringUtils.isNullOrEmpty(dictTypeKey) || null == dictTypeId || null == specialDictBodys || specialDictBodys.isEmpty()){
return false;
}
String cypher = "match(dictType:DictType) where id(dictType) = $dictTypeId " +
"unwind $specialDictBodys as item " +
"merge(dictType) -[:DictTypeRelationship]-> (dict:" + dictTypeKey + "{code:item.code}) " +
"set dict = item";
Map<String, Object> params = new HashMap<>();
params.put("dictTypeId", dictTypeId);
params.put("specialDictBodys", specialDictBodys);
session.query(cypher , params);
return true;
}
创建节点,标签是Stud
ent,属性是name和age
CREATE (:Student {name: '张三', age: 18})
Cypher 会返回更改的数量,在本例中添加 1 个节点、1 个标签和 2 个属性。
Created Nodes: 1
Added Labels: 1
Set Properties: 2
Rows: 0
创建多个节点,返回某个新增节点的数据
create(n:Student{name:"王五",age:15}),(m:School{name:"幼儿园"})return n,m
第二种写法
create(n:Student{name:"王五",age:15})
create(m:School{name:"幼儿园"})
return n , m
创建多个标签的节点
create (n:Car:Plane{name:"飞机车",power:100,hight:100,rate:1000})
创建两个节点和它们之间的关系
create (a:Person {name:'张三',age:30})-[b:老婆{roles:['admin']}]->(c:Person {name:'王五',age:33}) return a,c
创建三个节点和它们之间的关系
create (a:Person {name:'张三',age:30})-[b:老婆{roles:['admin']}]->(c:Person {name:'王五',age:33})
create(m:School{name:"奇葩幼儿园"})-[:幼儿园]->(a) return a,c,m
将查询的数据和现有的数据做关联
MATCH (n:Person{name:'张三'})
create(n)-[:儿子]->(m:Person{name:'老九'})
return n,m
第二种写法
MATCH (n:Person{name:'张三'})
create(m:Person{name:'老九'})
create(n)-[:儿子]->(m)
return n,m
查询是否存在该数据,不存在则创建新的节点和关系
merge (n:Person{name:'张三'})-[:爷爷]->(m:Person{name:'老六'}) return n,m
第二种写法
match (n:Person{name:'老头'}),(m:Person{name:'老婆'})
merge (n)-[r:wife]->(m)
return n,r,m
一个merge只能对应一条查询语句
例如:
match (n) where id(n) = 19
match (m) where id(m) = 18
match (k) where id(k) = 2
match (c) where id(c) = 3
merge (n)-[:`朋友`]->(m)
merge (n)-[:朋友]->(k)
merge (k)-[:老豆]->(c)
return n,m,k,c
错误写法:
match (n) where id(n) = 19
match (m) where id(m) = 18
match (k) where id(k) = 2
match (c) where id(c) = 3
merge (n)-[:`朋友`]->(m),(n)-[:朋友]->(k),(k)-[:老豆]->(c)
return n,m,k,c
查询所有的人和其对应的城市,城市不存在则创建
MATCH (person:Person)
MERGE (city:City { name: person.bornIn })
RETURN person.name, person.bornIn, city
on create,是当merge不创建时才执行,查询张三是否存在,存在则设置其年龄为20
merge (n:Person{name:'张三'}) on create set n.age = 20 return n
查询数据
查询关系深度为2的节点
MATCH p = ()-[*2]->() RETURN nodes(p)
查询关系深度3~5的节点
MATCH p = ()-[*3..5]->() RETURN nodes(p)
查询深度为2以上的节点的节点
MATCH p = ()-[*2..]->() RETURN nodes(p)
查询前25个节点
MATCH (n) RETURN n LIMIT 25
分页
查询结果集跳过前25个数据,取后面25条数据
MATCH (n) RETURN n SKIP 25 LIMIT 25
去重
查询是朋友的节点,并且去重
MATCH ()-[:朋友]->(m) return distinct m
排序
match (p:Person) return p order by p.age asc,p.sex desc
查询标签是Student的前25个节点
MATCH (n:Student) RETURN n LIMIT 25
查询属性name是张三的节点
MATCH (n{name:'张三'}) RETURN n
查询标签是Student并且name是王五的节点
MATCH (n:Student{name:'王五'}) RETURN n
查询学校和人有关系的所有节点,以下添加属性即可筛选需要的数据
MATCH (n:Person)<-[]-(m:School) RETURN n,m
查询人与人有关系的节点,关系的方向可以不写
MATCH (n:Person)-[]-(m:Person) return n,m
添加条件查询
布尔表达式:AND,OR,XOR(异或)和NOT
比较运算符:1.= 2.<> 3.< 4.> 5.<= 6.>= 7.IS NULL 8.IS NOT NULL
算数运算:+, -, *, /, %, ^(次方)
查询结果集name是张三的数据
MATCH (n) where n.name = '张三' return n
in运算符
查询年龄是20或61的结果集
match (n) where n.age in [20,61] return n
当in放在前面时,相当于=,不能用list集合
match (n) where 20 in n.age return n
正则表达式=~,后面写规则
match (n) where n.name =~ '张.*' return n
使用CQL自动生成的Id进行查询
match (n) where id(n) in [1,2,3] return n
CQL分析
1.n结果集为:有朋友的节点
2.m的结果集:是朋友的节点
加上not (m)-[:老豆]->()条件后(没有老豆的结果集)
那么m的结果集为:是朋友并且没有老豆的节点
n的结果集为:有朋友并且该朋友没有老豆的节点
match (n:Person)-[r:朋友]->(m)
where not (m)-[:老豆]->()
return m
collect函数使用,将结果集变成[]的形式,count用来计数会跳过空值数据
match (:Person{name:'赵六'})-[:`朋友`]->(m)
match (:Person{name:'王五'})-[:`朋友`]->(r)
return collect(m.name) as zhaoliuFriends,count(*),r.name as wangwuFriends
结果集
| zhaoliuFriends |count(*)| wangwuFriends |
| ["张三", "王五"]| 2 | "张三" |
不使用collect函数
| zhaoliuFriends |count(*)|wangwuFriends |
| "张三" | 1 | "张三" |
| "王五" | 1 | "张三" |
union并集,返回的列必须以相同的方式命名。
match (:Person{name:'赵六'})-[:`朋友`]->(n)
return n.name as name
union
match (:Person{name:'王五'})-[:`朋友`]->(m)
return m.name as name
在关系上添加多个标签的方式
查询是朋友或者是老豆的节点
match (n)-[:`朋友`|`老豆`]->(m)return m
contains 模糊查询,包含市的机构
match(n:Org)
where n.name contains '市'
return n
索引的使用
- 在图数据库中使用索引的主要原因是为了找到图遍历的起点。一旦找到该起点,遍历就依赖于图内结构来实现高性能。
- 可以随时添加索引。
- 如果数据库中已有数据,则创建索引需要一些时间。
- 可以使用索引提示指定在特定查询中使用哪个索引, 在大多数情况下,查询数据时不需要指定索引,因为将自动使用适当的索引。
创建索引
为Person标签的属性name创建索引,这时Person标签具有name属性的所有节点将添加到该索引中
写法一:
create index index_Person_name for (person:Person) on (person.name)
写法二:
create index on :Person(name)
复合索引
Person标签下具有name和age属性的节点添加到索引中
写法一,可以指定索引名称
create index index_Person_name_age for (person:Person) on (person.name,person.age)
写法二,系统生成索引名称
create index on :Person(name,age)
查询索引
SHOW INDEXES YIELD name, labelsOrTypes, properties, type
唯一性约束
添加唯一约束将隐式添加该属性的索引。如果删除了约束,但仍然需要索引,则必须显式创建索引。
单字段约束,会创建索引
CREATE CONSTRAINT constraint_Plane_name FOR (p:Plane) REQUIRE p.name IS UNIQUE
CREATE CONSTRAINT ON (p:Plane) ASSERT p.name IS UNIQUE
复合约束,会创建索引
CREATE CONSTRAINT ON (p:Plane) ASSERT (p.name,p.age) IS UNIQUE
约束查询
SHOW CONSTRAINTS YIELD id, name, type, entityType, labelsOrTypes, properties, ownedIndexId
删除约束,会自动删除对应的索引
DROP CONSTRAINT ON (p:Plane) ASSERT p.name IS UNIQUE
修改/添加属性/标签
修改属性
match (n) where id(n) = 38 set n.name = 'new1' set n.age = 1 set n:Person set n:Student return n
属性替换
CREATE (a:Person {name: 'Jane', age: 20})
WITH a
MATCH (p:Person {name: 'Jane'})
SET p = {name: 'Ellen', livesIn: 'London'}
RETURN p.name, p.age, p.livesIn
结果
Ellen null London
说明:{name: 'Jane', age: 20}整个被替换成{name: 'Ellen', livesIn: 'London'}所以age为null
删除节点数据
全部删除,先删除所有关系,再删除所有节点
match (n)-[r]->(m) delete r
match (n) delete n
删除属性/标签
删除属性语法规则
MATCH (N) WHERE ID(N)=17 remove N.age,N.name,N.sex return N
删除标签语法规则
MATCH (n:Student{id:100}) remove n:Person,n:Student RETURN n
导入csv文件
Neo4j配置文件指定了导入文件的文件夹,需要导入的csv文件需要放在该目录中才能被导入。
默认配置文件夹是import文件夹,可自定义文件夹。
dbms.directories.import=import
csv文件格式要求:utf8编码,可被导入数据大概一千万条
参数
参数 | 名称 | 用途 |
---|---|---|
using periodic commit [n] | 批量提交 | 可以设置每满 n 条提交一次,防止内存溢出。默认值是1000 |
with headers | 读取首行 | 读取文件的第一行作为参数名不使用此参数,要用 line[0]、line[1] 这样的方式表示使用此参数,可以使用 line.name 这样的表示方式 |
as line | 重命名 | 为每行数据重命名 |
fieldterminator ‘,’ | 自定义字段定界符 | csv中的分隔符基本都是逗号或分号。最常用的是逗号, |
自定义csv文件,放在import目录下
name,age
张三,18
李四,21
王五,16
不使用 with headers 的写法:
load csv
from 'file:///export.csv' as person
fieldterminator ','
create (
p:Person{
name: person[0],
age: person[1]
}
)
使用 with headers 的写法:
load csv with headers
from 'file:///export.csv' as person
fieldterminator ','
create (
p:Person{
name: person.name,
age: person.age
}
)
WITH
WITH 用于向后面的语句传递指定结果,并可以改变结果集中实体的形式和数量。注意,WITH 会影响查询结果集里的变量,WITH 语句外的变量不会传递到后续查询中,主要作用:
1、通过使用oder by 和limit,with可以限制传入下一个match子查询语句的实体数目。
2、对聚合值过滤。
如何改变查询结果集的变量?
示例如下:
根据b找出有关系的节点
match ({name:'b'})--(b)
return b
此时得到a,c两个节点,将其向下个语句流通数据时:
match ({name:'b'})--(b) 此处得到a,c节点
with b
match ({name:'c'})--(c) 该语句单独查询得到b。当上面的a,c流下来时相当于做遍历处理,a数据和该语句数据匹配得到b,c也是得到b数据
with b.name as bname,c.name as cname
return *
最终结果
MATCH ()-[:朋友]->(m)
WITH m.name as name,count(*) as cnt(后续语句只能读取到name和cnt)
return name,cnt
UNWIND的使用
UNWIND将一个列表展开为一个行的序列(行转列)。
unwind[1,2,3] as x return x
CALL {} (subquery)
- 以RETURN语句结尾的子查询称为返回子查询,而没有这种返回语句的子查询则称为单元子查询。
- 为每个传入的输入行计算子查询。返回子查询的每个输出行都与输入行组合,以生成子查询的结果。这意味着返回的子查询将影响行数。如果子查询未返回任何行,则子查询之后将没有可用的行。
- 另一方面,调用单元子查询是因为它们的副作用,而不是它们的结果,因此不会影响单元查询的结果。
子查询与单元查询交互的方式存在限制:
- 如果显式导入了单元查询中的变量,则子查询只能引用这些变量。
- 子查询不能返回与单元查询中的变量同名的变量。
- 从子查询返回的所有变量之后都可以在单元查询中使用。
子查询
为每个传入的输入行计算子查询。返回子查询的每个输出行都与输入行组合,子查询没有return值则with相关数据就没有该数据,call统计时能统计0
UNWIND [0, 1, 2] AS x
CALL {
RETURN 'hello' AS innerReturn
}
RETURN innerReturn
单元子查询
此示例查询为每个现有人员创建五个克隆。由于子查询是单位子查询,因此它不会更改封闭查询的行数。
MATCH (p:Person)
CALL {
WITH p
UNWIND range (1, 5) AS i
CREATE (:Person {name: p.name})
}
RETURN count(*)
OPTIONAL
查找数据不存在时返回null
match (n:Person{name:'Maria'})-->(m) return m
optional match (n:Person{name:'Maria'})-->(m) return m
CASE
对查询的结果集添加一个字段
计算表达式,并按顺序与WHEN子句进行比较,直到找到匹配项。如果未找到匹配项,则返回ELSE子句中的表达式。如果没有ELSE事例并且没有找到匹配项,则返回null。
MATCH (n)//查询所有节点
RETURN n.name,//返回节点name属性
CASE n.age
WHEN n.age IS NULL THEN -1//age为null则age_10_years_ago为-1
ELSE n.age - 10//age_10_years_ago的值为age-10
END AS age_10_years_ago//结果集添加字段age_10_years_ago
上述查询结果
Daniel的age_10_years_ago为null说明:n.age IS NULL其表示值为null,而Daniel没有age属性但是不能说明其age属性是null,因此其走的是ELSE分支,在ELSE分支中n.age - 10表示为空属性 - 10无法成立则返回null。
标签表达式:
查询不包含AB标签或者包含C标签的节点
MATCH (n:(!A&!B)|C)
RETURN n.name AS name
查询包含A或B标签的节点
MATCH (n:A|B)
RETURN n.name AS name
查询有标签的节点
MATCH (n:%)
RETURN n.name AS name
查询没有标签的节点
MATCH (n:!%)
RETURN n.name AS name
返回结果为Boolean值,拥有AB标签的节点返回true否则返回false
MATCH (n)
RETURN n:A&B
关系表达式
查询所有的关系
MATCH ()-[r]->()
RETURN r.name as name
查询R1类型的关系
MATCH ()-[r:R1]->()
RETURN r.name AS name
查询R1或R2类型的关系
1.写法一
MATCH ()-[r:R1|R2]->()
RETURN r.name AS name
2.写法二
MATCH (n)-[r]->(m)
WHERE r:R1|R2
RETURN r.name AS name
查询非R1的关系
MATCH ()-[r:!R1]->()
RETURN r.name AS name
查询非R1和R2或者R3的关系
MATCH ()-[r:(!R1&!R2)|R3]->()
RETURN r.name as name
返回Boolean值,包含R1或R2的关系返回true
MATCH (n)-[r]->(m)
RETURN r:R1|R2 AS result
EXISTS
EXISTS子查询可用于确定指定的模式是否在数据中至少存在一次。
MATCH (person:Person)//查询所有标签是Person的节点
WHERE EXISTS {//获取以下结果集为true的交集
MATCH (person)-[:HAS_DOG]->(dog:Dog)//拥有狗的人返回true
WHERE EXISTS {//获取以下结果集为true的交集
MATCH (dog)-[:HAS_TOY]->(toy:Toy)//拥有玩具的狗返回true
WHERE toy.name = 'Banana'//玩具名称为Banana
}
}
RETURN person.name AS name
上述结果为:"Peter"
以下是返回Boolean值,在return时使用,存在则返回true否则返回false
MATCH (person:Person)
RETURN person.name AS name, EXISTS {
MATCH (person)-[:HAS_DOG]->(:Dog)
} AS hasDog
上述结果:
来自外部范围的变量对于整个子查询都是可见的,即使在使用WITH子句时也是如此。这意味着不允许隐藏这些变量。当用同一变量定义内部范围内新引入的变量时,外部范围变量将被隐藏。在下面的示例中,WITH子句引入了一个新变量。注意,主查询中引用的外部范围变量person在WITH子句之后仍然可用。
MATCH (person:Person)
WHERE EXISTS {
WITH "Ozzy" AS dogName
MATCH (person)-[:HAS_DOG]->(d:Dog)
WHERE d.name = dogName
}
RETURN person.name AS name
上述结果集:"Peter"
COUNT
COUNT子查询表达式可用于计算子查询返回的行数。
允许任何非写入查询。COUNT子查询与常规查询的不同之处在于,可以省略最后的RETURN子句,因为子查询中定义的任何变量在表达式之外都不可用,即使使用了最后的RET子句。一个例外是,对于DISTINCT UNION子句,RETURN子句仍然是强制的。
值得注意的是,在COUNT仅由模式和可选where子句组成的情况下,可以在子查询中省略MATCH关键字。
MATCH (person:Person)//查询所有Person标签节点
RETURN
person.name AS name,//返回name属性
COUNT {//统计养猫狗的数量
MATCH (person)-[:HAS_DOG]->(dog:Dog)
RETURN dog.name AS petName
UNION
MATCH (person)-[:HAS_CAT]->(cat:Cat)
RETURN cat.name AS petName
} AS numPets
上述结果:
来自外部范围的变量对于整个子查询都是可见的,即使在使用WITH子句时也是如此。这意味着不允许隐藏这些变量。当用同一变量定义内部范围内新引入的变量时,外部范围变量将被隐藏。在下面的示例中,WITH子句引入了一个新变量。注意,主查询中引用的外部范围变量person在WITH子句之后仍然可用。
ALL()
all()表示所有的元素都满足条件,返回值Boolean
MATCH p = (a)-[*1..3]->(b)
WHERE
a.name = 'Alice'
AND b.name = 'Daniel'
AND all(x IN nodes(p) WHERE x.age > 30) x用来标识结果集,年龄大于30做筛选
RETURN p
时间函数
以下图片地址:https://neo4j.com/docs/cypher-manual/current/syntax/temporal/
各个时间函数支持的数据类型
各个时间函数支持的属性
使用示例
示例1
示例2
Durations
指定持续时间
持续时间表示时间量,捕捉两个瞬间之间的时间差,可以是负值。
持续时间的规范前缀为P,可以使用基于单位的形式或基于日期和时间的形式:
基于单位的形式:P[nY][nM][nW][nD][T[nH][nM][nS]]
方括号([])表示可选组件(零值的组件可以省略)。
n表示64位整数范围内的数值,可以包含小数。
每个组件必须以表示该单元的组件标识符作为后缀。
基于单位的表格使用M作为月和分钟的后缀。因此,时间部分必须始终以T开头,即使没有给出日期部分的组成部分。
持续时间的最大总长度由64位整数中可保持的秒数限制。
基于日期和时间的形式:P<Date>T<time>
。
与基于单元的表单不同,此表单要求每个组件都在有效LocalDateTime的范围内。
下表列出了基于单元的表单的组件标识符:
示例解析:
以下表示经过14天16小时12分
以下表示经过5个月又1.5天,结果为5个月1天又12小时
以下表示0.75分钟,结果为45秒
以下表示2.5周,结果为17天又12小时
持续时间”可以有几个组件,每个组件分为“月”、“天”和“秒
请注意:
Cypher在处理闰秒时使用UTC-SLS。
一天中不一定有24小时;当切换到夏令时时,一天可以有23或25个小时。
一个月的天数并不总是相同的。
由于闰年,一年中的天数并不总是相同的。
也可以访问由组中最大(最重要)组分界定的组分组中较小(不太重要)的组分:
示例结果解析(根据上方两个图表进行分析):
示例为:经过1年5个月111天42分钟
结果:
d.years = 1,经过了一年所以years为1
d.quarters = 5,经过一年5个月则为17个月,根据上方duration.quarters的解释一年有4个季度,3个月为1个季度则17%3=5所以结果为5表示经过了5个季度
d.quartersOfYear = 1,组中不占全年的季度数为5个月/3个月=1余2个季度。
d.months = 17,表示经过了17个月,定义时间为1年5个月已经表示17个月,111天无法表示经过多少个月(每个月份天数不同)所以未参与计算
d.monthsOfYear = 5,组中不构成一整年的月份数。
示例2:经过5个月25天1小时
结果:
d.weeks = 3,从上表得知7天为一周,25%7 = 3
d.days = 25,直接获取days
d.daysOfWeek,组中不占一周的天数25/7=3余4,,不占一周的天数为4。
between
获取两个时间中间的差值
inDays
获取两个时间中间的差值的天数
truncate
获取指定年、月、周第一天的日期
以下表示获取今年第一天的日期
以下表示获取指定时间‘2019-10-01’当前周周四的日期
以下表示获取当前时间加上两个月后的第一天减去一天
distance计算两点之间距离
地球坐标的距离
WITH
point({latitude:toFloat('13.43'), longitude:toFloat('56.21')}) AS p1,
point({latitude:toFloat('13.10'), longitude:toFloat('56.41')}) AS p2
RETURN toInteger(distance(p1, p2)/1000) AS km
二维坐标
WITH
point({x: 3, y: 0}) AS p2d,
point({x: 0, y: 4, z: 1}) AS p3d
RETURN
distance(p2d, p3d) AS bad,
distance(p2d, point({x: p3d.x, y: p3d.y})) AS good
列表规则
自定义列表
RETURN [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] AS list
RETURN range(0, 10)//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
按照下标取值
RETURN range(0, 10)[3]//3,下标从0开始,第三数对应三
RETURN range(0, 10)[-3]//8,负数表示从列表后面往前数三个数
按照范围下标获取值
RETURN range(0, 10)[0..3]//[0,1,2],该范围包前不包后,0的下标对应值是0,3对应下标对应值为3,取值为0~3不包括3
RETURN range(0, 10)[0..-5]//[0,1,2,3,4,5],0下标值为0,-5下标值为6,取值为[0,6)
RETURN range(0, 10)[-5..]//[6,7,8,9,10]
项目配置
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
实体类
import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
@NodeEntity(label = "FundAccount")
public class FundAccount {
@Id
@GeneratedValue
private Long id;
}
repository
public interface FundAccountRepository extends Neo4jRepository<FundAccount> {
//两张传参占位符形式
@Query("match(a:User)" +
"where a.account =$viewPwd.account " +
"set a.password = $viewPwd.newPwd")
void changePasswordByViewPwd(@Param("viewPwd") ViewPwd viewPwd);
@Query("match(a:User)" +
"where a.account =$0.account " +
"set a.password = $0.newPwd ")
void changePasswordByViewPwd(ViewPwd viewPwd);
}
多数据源配置
目录结构参考
数据源指向到Repository
配置文件
spring:
profiles: test
data:
#第一数据源--主数据库
neo4j:
uri: bolt://192.168.200.11:8888
username: neo4j
password: 123
#第二数据源--审计库
secondary:
uri: bolt://192.168.200.11:9999
username: neo4j
password: 123
第一个数据源
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import static com.Lyy.cloudGateway.config.dataSouce.PrimaryDataSourceConfig.*;
/**
* @author chenkepeng
*
*/
@Configuration
//开启事务
@EnableTransactionManagement
//开启实体类扫描
//@EntityScan(basePackages = {"com.Lyy.phoneDataAnalysis.basicService.persistence.entity","com.Lyy.phoneDataAnalysis.entity"})
//开启Neo4jRepositories的支持
@EnableNeo4jRepositories(
sessionFactoryRef = PRIMARY_SESSION_FACTORY,
sessionBeanName = PRIMARY_SESSION_BEAN_NAME,
// basePackages = {"com.Lyy.phoneDataAnalysis.basicService.persistence.repository","com.Lyy.phoneDataAnalysis.repo","com.Lyy.phoneDataAnalysis.basicService.config.audit.persistence"},
basePackages = {"com.Lyy.cloudGateway.persistence.primary"},
transactionManagerRef = PRIMARY_TRANSACTION_MANAGER
)
//@EnableConfigurationProperties(Neo4jProperties.class)
public class PrimaryDataSourceConfig {
public static final String PRIMARY_SESSION_FACTORY = "sessionFactoryForPrimaryDataSource";
public static final String PRIMARY_TRANSACTION_MANAGER = "transactionManagerForPrimaryDataSource";
public static final String PRIMARY_SESSION_BEAN_NAME = "sessionBeanNameForPrimaryDataSource";
@Primary
@Bean
@ConfigurationProperties("spring.data.neo4j")
public Neo4jProperties neo4jPrimaryDataSourceProperties() {
return new Neo4jProperties();
}
@Primary
@Bean
public org.neo4j.ogm.config.Configuration primaryDataSourceOgmConfiguration() {
return neo4jPrimaryDataSourceProperties().createConfiguration();
}
@Primary
@Bean(name = PRIMARY_SESSION_FACTORY)
public SessionFactory sessionFactory() {
// return new SessionFactory(primaryDataSourceOgmConfiguration(), "com.Lyy.phoneDataAnalysis.basicService.persistence.repository","com.Lyy.phoneDataAnalysis.repo","com.Lyy.phoneDataAnalysis.basicService.config.audit.persistence");
return new SessionFactory(primaryDataSourceOgmConfiguration(),
"com.Lyy.cloudGateway.persistence.primary" );
}
@Primary
@Bean(name = PRIMARY_TRANSACTION_MANAGER)
public Neo4jTransactionManager neo4jTransactionManager() {
return new Neo4jTransactionManager(sessionFactory());
}
}
第二个数据源
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories(
sessionFactoryRef = SecondaryNeo4jDataSourceConfig.SECONDARY_SESSION_FACTORY,
basePackages = SecondaryNeo4jDataSourceConfig.SECONDARY_BASE_PACKAGE,
transactionManagerRef = SecondaryNeo4jDataSourceConfig.SECONDARY_TRANSACTION_MANAGER,
sessionBeanName = SecondaryNeo4jDataSourceConfig.SECONDARY_SESSION_BEAN_NAME
)
public class SecondaryNeo4jDataSourceConfig {
public static final String SECONDARY_SESSION_FACTORY = "sessionFactoryForSecondaryDataSource";
public static final String SECONDARY_SESSION_BEAN_NAME = "sessionBeanNameForSecondaryDataSource";
public static final String SECONDARY_TRANSACTION_MANAGER = "transactionManagerForSecondaryDataSource";
static final String SECONDARY_BASE_PACKAGE = "com.Lyy.cloudGateway.persistence.secondary";
@Bean
@ConfigurationProperties("spring.data.secondary")
public Neo4jProperties neo4jSecondaryDataSourceProperties() {
return new Neo4jProperties();
}
@Bean
public org.neo4j.ogm.config.Configuration secondaryDataSourceOgmConfiguration() {
return neo4jSecondaryDataSourceProperties().createConfiguration();
}
@Bean(name = SECONDARY_SESSION_FACTORY)
public SessionFactory sessionFactory() {
return new SessionFactory(secondaryDataSourceOgmConfiguration(), SECONDARY_BASE_PACKAGE);
}
@Bean(name = SECONDARY_TRANSACTION_MANAGER)
public Neo4jTransactionManager neo4jTransactionManager() {
return new Neo4jTransactionManager(sessionFactory());
}
}
使用参考动态cypher
动态cypher
@Autowired
@Qualifier(SecondaryNeo4jDataSourceConfig.SECONDARY_SESSION_FACTORY)
Session session
@Override
public Page<ViewRequestInfo> getAuditInfoList(ViewRequestInfoSearch viewRequestInfoSearch) {
if (null == viewRequestInfoSearch){
//查询条件为空则查全部
viewRequestInfoSearch = new ViewRequestInfoSearch();
}
String matchSql = " match(n:RequestInfo) ";
List<String> whereSqlList = new ArrayList<>();
String account = viewRequestInfoSearch.getAccount();
//账号
if (null != account){
this.getLikeWhereSql("account",account,whereSqlList);
}
//用户名
String username = viewRequestInfoSearch.getUsername();
if (null != username){
this.getLikeWhereSql("username",username,whereSqlList);
}
//操作
String operation = viewRequestInfoSearch.getOperation();
if (null != operation){
this.getLikeWhereSql("operation",operation,whereSqlList);
}
//方法
String method = viewRequestInfoSearch.getMethod();
if (null != method){
this.getLikeWhereSql("method",method,whereSqlList);
}
//路径
String path = viewRequestInfoSearch.getPath();
if (null != path){
this.getLikeWhereSql("path",path,whereSqlList);
}
//参数
String requestBody = viewRequestInfoSearch.getRequestBody();
if (null != requestBody){
this.getLikeWhereSql("requestBody",requestBody,whereSqlList);
}
//请求状态
Integer responseStatus = viewRequestInfoSearch.getResponseStatus();
if (null != requestBody){
String whereSql = " n."+ responseStatus +" = " + responseStatus + " ";
whereSqlList.add(whereSql);
}
String startTime = viewRequestInfoSearch.getStartTime();
//操作时间
if (null != startTime){
String whereSql = " n.time >= " + startTime + " ";
whereSqlList.add(whereSql);
}
String endTime = viewRequestInfoSearch.getEndTime();
if (null != endTime){
String whereSql = " n.time <= " + endTime + " ";
whereSqlList.add(whereSql);
}
//域名
String host = viewRequestInfoSearch.getHost();
if (null != host){
this.getLikeWhereSql("host",host,whereSqlList);
}
//模块
String model = viewRequestInfoSearch.getModel();
if (null != model){
this.getLikeWhereSql("model",model,whereSqlList);
}
String whereSql = "";
if (!whereSqlList.isEmpty()){
whereSql = whereSqlList.stream().collect(Collectors.joining(" and "));
whereSql = " where " + whereSql;
}
//构造查询
matchSql = matchSql + whereSql;
//构造分页
Integer pageNum = viewRequestInfoSearch.getPageNum();
Integer pageSize = viewRequestInfoSearch.getPageSize();
if (null == pageNum){
pageNum = 0;
}
if (null == pageSize){
pageSize = 10;
}
Integer skip = pageNum * pageSize;
//查询数据
String matchListSql = matchSql + " return n skip " + skip + " limit " + pageSize + " ";
Iterable<RequestInfo> requestInfos = session.query(RequestInfo.class, matchListSql, Maps.newHashMap());
//查询总数
String matchCountSql = matchSql + " return count(n)";
Long total = session.queryForObject(Long.class, matchCountSql, Maps.newHashMap());
//构造返回值
PageRequest pageRequest = PageRequest.of(pageNum, pageSize);
List<ViewRequestInfo> viewRequestInfoList = new ArrayList<>();
if (null != requestInfos){
for (RequestInfo requestInfo : requestInfos){
ViewRequestInfo viewRequestInfo = new ViewRequestInfo();
BeanUtils.copyProperties(requestInfo,viewRequestInfo);
viewRequestInfoList.add(viewRequestInfo);
}
}
PageImpl<ViewRequestInfo> pageConfigFlags = new PageImpl<ViewRequestInfo>(viewRequestInfoList, pageRequest, total);
return pageConfigFlags;
}
private String getLikeWhereSql(String paramName, String paramValue,List<String> whereSqlList) {
if (null == paramName && null == paramValue){
return null;
}
String whereSql = " n."+ paramName +" =~'.*" + paramValue + ".*' ";
whereSqlList.add(whereSql);
return whereSql;
}
查询结果集到自定义的类
Long total = this.session.queryForObject(Long.class, matchCount, Maps.newHashMap());
String matchList = matchCypher + " return {" +
"id: id(n), " +
"caseID: n.caseID, " +
"caseName: n.caseName" +
"} as res " +
"skip " + pageNum * pageSize + " limit " + pageSize + "";
Result result = this.session.query(matchList , Maps.newHashMap());
List<ViewFreezeCase> viewFreezeCases = new ArrayList<>();
if (null != result){
for(Map<String, Object> map : result){
JSONObject jsonObject = new JSONObject(map);
JSONObject resData = jsonObject.getJSONObject("res");
if (null == resData || resData.isEmpty()) {
continue;
}
ViewFreezeCase viewFreezeCase = JSON.toJavaObject(resData, ViewFreezeCase.class);
//构造过滤器类型返回值
viewFreezeCases.add(viewFreezeCase);
}
}
PageRequest pageRequest = PageRequest.of(pageNum, pageSize);
PageImpl<ViewFreezeCase> page = new PageImpl<ViewFreezeCase>(viewFreezeCases, pageRequest, total);
//apoc.map.setLists函数解析
apoc.map.setLists(n1,[n2],n3)
查询一个列表信息,每个元素数据都是map对象
n1可以是任意节点,只查询节点的properties属性
n2是要查询的字段
n3是n2对应的每行的数据
当n1和n2互相包含相同字段时,n2会覆盖n1的字段值
$动态传参
String cypher = "match(dictType:DictType) where id(dictType) = $dictTypeId " +
"unwind $specialDictBodys as item " +
"merge(dictType) -[:DictTypeRelationship]-> (dict:" + dictTypeKey + "{code:item.code}) " +
"set dict = item";
Map<String, Object> params = new HashMap<>();
params.put("dictTypeId", dictTypeId);
params.put("specialDictBodys", specialDictBodys);
列表推导式收集集合中的属性
Neo4j 4.x或更高版本
match(user:User) -[:资金字段映射|:billMappingRelation]-> (n)
where n:资金字段映射 or n:billMapping
optional match(case:资金分析) -[:关联文件] -> (bill:Bill:资金数据文件)
where n.hash = bill.hash
with distinct n, user, case ,bill
with distinct n, user, collect(distinct case) as cases ,collect(distinct bill) as bills
RETURN {
id: id(n),
creater: user.name,
createTime: n.createTime,
isEnable: n.isEnable,
propertiesMap: n.data,
originPropsData: n.originPropsData,
mappingPropsData: n.mappingPropsData,
viewBillInfos: [bill IN bills | {id: id(bill), name: bill.原始数据文件名}],
viewCaseInfos: [case IN cases | {id: id(case), name: case.caseName}]
} AS result
查询结果
{
"mappingPropsData": null,
"createTime": "2024-01-25 12:59:46",
"creater": "ss",
"viewCaseInfos": [
{
"name": "测试y",
"id": 3132730
},
{
"name": "测试时间",
"id": 3213273
}
],
"propertiesMap": "{"对方开户行":"交易对方账号开户行","交易余额":"交易余额","对方账号":"交易对方账卡号"}",
"id": 77465,
"viewBillInfos": [
{
"name": "-2级-黄-62163-平果国民村镇银行.xls",
"id": 77512
},
{
"name": "-1级-鄂建设银行.xls",
"id": 3205608
}
],
"originPropsData": null,
"isEnable": null
}