赞
踩
最近有粉丝问我,百度百科中的明星关系效果是如何实现的呢?比如下图这种效果:
这种功能可以用关系型数据库来实现吗?答案当然是肯定的!其实简单的关系维护,本就是关系型数据库擅长的事情,但如果关系维度过多且关联足够复杂,还适合用关系型数据库吗?
比如实际生活中,人与人之间可能存在多重关系与交集,且分别还有自己的社交圈,圈子与圈子之间又存在各种交集。另外除了人与人之间的关系,还牵连到人物本身的作品、成绩、历史事件的关系等等...... 想想是不是就挺复杂的了?且不说构建这么庞大且复杂的关系之后,查询性能是否可以保证,仅仅只是这些人物、作品、地点、成绩、事件、关系等表结构的设计,就够开发者喝一壶的了。
类似的场景实际上还有很多,比如天眼查里的企业、法人、股东、投资者、事件等之间的关系;再比如商城项目中,商铺、买家、商品、订单、渠道之间的关系等。对于类似这种功能,如果仅仅只是依靠关系型数据库,一旦碰到深度关联的需求,那就等着系统重构吧。
那该怎么解决呢?当然就是今天的主角 - 图数据库Neo4j来搞定咯!
这是一张Neo4j官方提供的电影、导演、演员、主角的关系数据,怎么样?看着是不是就很高端大气上档次?既然如此,那我就带大家认识一下Neo4j吧!
图数据库(Graph Database)是基于图论实现的一种NoSQL数据库。在图论中,图的基本元素为节点和边,在图数据库中对应的就是节点和关系。
在图数据库中,数据与数据之间的关系通过节点和关系构成一个图结构,并在此结构上实现数据库的所有特性(CRUD),还有事务处理等能力。
关系型数据库的弊端
关系型数据库将高度结构化的数据存储在二维表格中,并且通过外键约束来实现表与表之间的关联关系。关系数据库通过外键去主表中寻找匹配的主键记录进行搜索匹配,这种操作是计算密集型和内存密集型的,并且操作次数是表中记录的指数级别,当数据量巨大,或者关系特别复杂(关联表特别多)时,查询成本将会变的巨大。
图数据库的优势
图数据库中,关系是最重要的元素。每个节点都直接包含一个关系列表,关系列表存放此节点与其他节点的关系记录。关系记录类型和方向组织起来,并且保存附加属性,当进行连接Join时,图数据库都将使用关系列表直接放行连接的节点,无须进行记录搜索匹配的操作。
这种预先保存关系列表的方式,使得图数据库能够提供比关系型数据库高几个数量级的性能,特别对于复杂连接的查询,Neo4j可以实现毫秒级的响应。
学习一个技术,我觉得最重要的就是这个技术能解决什么问题?Neo4j的运用场景很多,以下就是给大家列举的几个场景的运用场景:
朋友圈关系维护,好友推荐,可能认识的人
金融圈行为管理,防电话欺诈
电商项目中商品多维度智能推荐
短视频搜索联想
.......
官网:https://neo4j.com/
Neo4j是由Java实现的开源NoSQL图数据库。2003年研发,2007年正式发布第一版。Neo4j实现了专业数据库级别的图数据库模型的存储,与普通的图处理不同,Neo4j提供了完整的数据库特性,包括ACID事务、集群支持、备份、故障转移等,使其适用于企业级生产环境下的各种应用。
废话不多说,咱们直接开整吧!!!!!!
这里我们选择比较简单的docker-compose的安装方法,如果没有学过docker-compose的小伙伴,你要先学习下这块内容哦。
1). 编写docker-compose.yml
- neo4j:
- image: neo4j:5.5.0-community
- container_name: neo4j
- ports:
- - 7474:7474
- - 7687:7687
- environment:
- NEO4J_AUTH: neo4j/neo4j123456
- volumes:
- - ./neo4j/data:/data
- - ./neo4j/logs:/logs
- - ./neo4j/conf:/var/lib/neo4j/conf
- - ./neo4j/import:/var/lib/neo4j/import
- restart: always
2). 运行容器
docker-compose up -d neo4j
3). 访问Neo4j管理平台
http://ip:7474/
节点
关系
节点(Node)是图数据库中的一个基本元素,用以表示一个实体记录,就像关系型数据库中的一条记录一样。每个实体可以有0到多个属性(Property),这些属性以key-value形式存在。同时每个节点还具有相应的标签(Label),用来区分不同类型的节点。
关系(Relationship)也是图数据库中的基本元素。关系用来连接两个节点,有起始节点和终止节点,另外,与节点一样,关系可以包含多个属性,但是只能有一个类型(Type)。
注意:
在图遍历时,可以指定关系遍历的方向或者指定为无方向,故在创建关系时,不用给两个节点创建双向关系;
一个节点可以存在指向自己的关系。
节点和关系都可以拥有多个属性,属性由键值对组成,属性值可以是基本的数据类型,也可以由基本数据类型组成的数组。
注意:
属性没有null的概念;
属性不需要时,可以直接将整个键值对都移除,可以使用is null判断属性是否存在。
类型 | 说明 | 备注 |
boolean | 布尔值 | |
byte | 8位整数 | |
short | 16位整数 | |
int | 32位整数 | |
long | 64位整数 | |
float | 32位浮点数 | |
double | 64位浮点数 | |
char | 16位无符号整数代表的字符 | u0000 to uffff |
string | Unicode字符序列 |
使用节点和关系创建一个图后,图中任意两个节点间,都是可能存在路径的。路径有长度的概念,也就是路径中关系的条数。简单来说,如果两个节点之间只有一个关系,那路径的长度就是1
Cypher是一种声明书图数据库查询语言,能高效的查询和更新图数据。有点类似关系型数据库中的SQL语言。
Cypher采用一对括号表示节点,比如()、(person)
():表示一个匿名节点;(n):表示一个(一批)的节点,并且用一个变量n代表这个(这批)节点;(:Label):表示一个(一批)标签为Label的节点;(n:Label):表示一个(一批)标签为Label的节点,并且用一个变量n代表这个(这批)节点;(n:Label {key:"value"}):表示一个(一批)标签为Label,属性key的值为value的节点,并且用一个变量n代表这个(这批)节点。
Cypher使用 -- 表示一个关系,有方向的关系加上一个箭头即可。反括号[...]用于添加详情,里面可以包括变量、属性和类型信息。
--:无方向的关系,只表示关系的存在性;-->:向右方向的关系;<--:向左方向的关系;-[r]->:表示一个(一批)关系,并且用一个变量r表示这个(这批)关系;-[:Type]->:表示一个(一批)类型为Type的关系;-[r:Type]->:表示一个(一批)类型为Type的关系,并且用一个变量r表示这个(这批)关系;-[r:Type {key: ["value"]}]->:表示一个(一批)类型为Type、属性key的值为数组["Neo"]的关系,并且用一个变量r表示这个(这批)关系。
CQL命令 | 说明 |
create | 创建节点、关系和属性 |
match | 查询节点、关系和属性 |
return | 返回查询结果 |
where | 过滤查询数据 |
delete | 删除节点和关系 |
remove | 删除节点/关系的属性 |
order by | 排序查询数据 |
set | 添加或更新标签 |
查看Neo4j数据库
创建测试数据
使用指定的模式检索数据库,通常与WHERE语句一起使用。
查找所有节点
- match (n)
- return n
查询带有某个标签的所有节点
- //返回所有<电影>节点
- match (n:Movie)
- return n
-
- //返回所有<电影>节点的<标题>属性值以及节点的所有<标签>
- match (n:Movie)
- return n.title, labels(n)
查询关联节点
- //返回Tom Hanks有关系的所有节点
- match (n {name: 'Tom Hanks'}) -- (m)
- return m
-
- //返回Tom Hanks有参与的所有电影节点
- match (n {name: 'Tom Hanks'}) -- (m:Movie)
- return m
-
- //返回Tom Hanks导演(:DIRECTED)的所有电影节点
- match (n {name: 'Tom Hanks'}) -[:DIRECTED]- (m:Movie)
- return n,m //同时返回两种节点
-
- //返回所有和电影《A Few Good Men》有关的人物节点
- match (m:Movie {title:"A Few Good Men"}) <-[r]- (n:Person)
- return n,m
-
- //同上,关系方向相反
- match (n:Person) -[r]-> (m:Movie {title:"A Few Good Men"})
- return n,m
-
- //返回所有和电影《A Few Good Men》有关的人物的关系类型
- match (m:Movie {title:"A Few Good Men"}) <-[r]- (n:Person)
- return type[r]
-
- //返回电影《Something's Gotta Give》参与(:ACTED_IN)或者编写(:WROTE)人物节点
- match (m {title:"Something's Gotta Give"}) <-[:WROTE|:ACTED_IN]- (n:Person)
- return n,m
- //查询电影和人物之间的关系 包含属性roles,并且值为["Jane"]的相关数据
- match p = (m:Movie) -[* {roles:["Jane"]}]- (n:Person)
- return p

查询多个关系
- //查询人物Susan Sarandon参与的电影(:movie),以及这些电影的导演节点(:DIRECTED)
- match (n:Person {name:"Susan Sarandon"}) -[:ACTED_IN]-> (m:Movie) <-[:DIRECTED]-(x:Person)
- return m,x
查询多级关系
- 孤驻一郑[电影]
- https://mr.baidu.com/14mMKrYDLQ4
查询最短路径
- //查找电影《Ninja Assassin》和电影《Speed Racer》之间最短的关系路径
- //shortestPath()函数 - 找到两个节点之间的最短路径
- match
- (m1:Movie {title:"Ninja Assassin"}),
- (m2:Movie {title:"Speed Racer"}),
- p = shortestPath((m1)-[*0..5]-(m2))
- return p
-
- //查找所有最短路径
- //allshortestPaths()函数 - 找到两个节点之间所有的最短路径
- match
- (m1:Movie {title:"Ninja Assassin"}),
- (m2:Movie {title:"Speed Racer"}),
- p = allshortestPaths((m1)-[*0..5]-(m2))
- return p
-
- //查找电影《Ninja Assassin》和电影《Speed Racer》之间最短的所有关系路径,但是要排除掉id为174的关系路径
- //relationships(p)函数 - 获取变量p中代表的所有关系
- match
- (m1:Movie {title:"Ninja Assassin"}),
- (m2:Movie {title:"Speed Racer"}),
- p = allshortestPaths((m1)-[r*0..5]-(m2))
- where none(r in relationships(p) where id(r) = 174)
- return p

根据id查询
- //查询id为127的节点
- match (n)
- where id(n) = 127
- return n
-
- //查询关系id为117的关系以及连接节点
- match p = ()-[r]-()
- where id(r) = 177
- return p
-
- //多id查询
- match p = ()-[r]-()
- where id(r) in [177,17]
- return p
4.2.3 where 语句
where不能单独使用,只能作为match、optinal match、start和with的一部分。和with和start配合使用时,where用于过滤结果。对于match、optinal match,where用于增加约束,这时不能看成匹配完成后过滤结果。
基本使用
- //返回所有电影节点 等同于match(n:Movie) return n
- match (n)
- where n:Movie return n
-
- //返回所有发行时间(released)小于1995年的电影
- match (n:Movie)
- where n.released < 1995 return n
-
- //返回所有影评(REVIEWED)中,评分(rating)小于50分的相关数据
- match p = () -[r:REVIEWED]- ()
- where r.rating < 50 return p
-
- //返回所有不存在released属性的节点
- match (n)
- where n.released is null return n
-
- //返回所有存在released属性的节点
- match (n)
- where n.released is not null return n

属性过滤中,n.属性名称 等价于 n["属性名称"]
布尔运算
可以在where中使用布尔运算符:
and
or
not
- //返回电影2012年之前发行(released),并且编剧(WROTE)的出生年月(born)不低于1960年的电影与编剧关系
- match p = (n:Movie) -[r:WROTE]- (m:Person)
- where n.released <= 2012 and not m.born <= 1960 return p
字符串匹配
- //返回所有以C开头的电影节点,类似于sql中的like 'C%'
- match (n)
- where n.title starts with 'C'
- return n
-
- //返回所有以c结尾的电影节点,类似于sql中的like '%c'
- match (n)
- where n.title ends with 'c'
- return n
-
- //返回所有包含c的电影节点,类似于sql中的like '%c%'
- match (n)
- where n.title contains 'c'
- return n
注意:
字符串匹配方式,是大小写敏感的;
如果要反向匹配,只需要在前面加上not即可,比如 not n.title contains 'c'。
路径匹配
- //返回Jim Cash编剧的所有电影节点
- match (n:Person),(m)
- where n.name = 'Jim Cash' and (n)-[:WROTE]->(m)
- return n,m
-
- //返回Jim Cash编剧的所有电影节点
- match (n:Person),(m)
- where n.name = 'Jim Cash' and (n)-[:WROTE]->(m)
- return n,m
-
- //返回Meg Ryan没有参演的所有电影节点
- match (n:Person),(m)
- where n.name = 'Meg Ryan' and m:Movie and not (n)-[:ACTED_IN]->(m)
- return n,m
-
- //返回参演过RescueDawn电影的所有人员节点
- match(n)
- where (n)-[:ACTED_IN]->(:Movie{title: "RescueDawn"})
- return n

列表
- //返回姓名为Zach Grenier和Christian Bale的人员节点
- match(n)
- where n.name in ["Zach Grenier", "Christian Bale"]
- return n
注意:in后面是用的[],和sql不一样
create语句用于创建图元素:节点和关系
创建节点
- //创建单个节点
- create (n)
- //创建多个节点
- create (n),(m)
- //创建带标签的节点
- create (m:Movie)
- //创建带多个标签的节点
- create (n:Person:Docter)
- //创建带标签并且带属性的节点
- create(n:Person {name:"张三",age:18})
注意:
创建的节点标签可以完全自定义;
创建的节点属性可以完全自定义。
创建关系
- //创建<张三>和<王五>之间的<朋友>关系
- match(n1:Person {name:"张三"}),(n2:Person {name:"王五"})
- create (n1)-[r:Friend]->(n2)
- return r
注意:
创建节点关系时,必须携带关系的方向;
这种创建关系的方式,必须保证节点已经存在。
- //创建<张三>和<王五>之间的<朋友>关系,并且附带了相识的时间和关系的类型
- match(n1:Person {name:"张三"}),(n2:Person {name:"王五"})
- create (n1)-[r:Friend {date: "2012-12-01", type:"同窗"}]->(n2)
- return r
-
- //创建3个节点的并创建他们的关系
- create p=(n:People {name:"关羽"})-[r:Borther {type:"二弟"}]
- ->(m:People {name: "刘备"})
- <-[r2:Borther {type:"三弟"}]-(k:People {name:"张飞"})
- -[r3:Borther {type:"三弟"}]->(n)
- return p
这里大家注意一点哦,带大家用的是最新的Neo4j5.5,最低只支持JDK17,还在用JDK8的小伙伴,给了你们一个升级JDK的理由,嘿嘿。
1). 添加依赖
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-dependencies</artifactId>
- <version>2.5.5</version>
- <scope>import</scope>
- <type>pom</type>
- </dependency>
- </dependencies>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-neo4j</artifactId>
- </dependency>
注意:
Neo4j 5.x最低支持JDK17;
SpringBoot最低要求2.5.5的版本。
2). 配置文件
- spring:
- neo4j:
- uri: bolt://ip:7687
- authentication:
- username: neo4j
- password: neo4j
3). 准备节点实体
- package com.qf.neo4j.entity;
-
- import lombok.Data;
- import org.springframework.data.neo4j.core.schema.GeneratedValue;
- import org.springframework.data.neo4j.core.schema.Id;
- import org.springframework.data.neo4j.core.schema.Node;
- import org.springframework.data.neo4j.core.schema.Relationship;
-
- import java.util.ArrayList;
- import java.util.HashSet;
- import java.util.Set;
-
- @Data
- @Node(primaryLabel = "student", labels = {"person"})
- public class Student {
- @Id
- private Long id;
- private String name;
- private Integer age;
- private String sex;
-
- @Relationship(type = "select", direction = Relationship.Direction.OUTGOING)
- private Set<StuCourRelations> stuCourRelations = new HashSet<>();
-
- public void addStuCourRelations(StuCourRelations stuCourRelations) {
- this.stuCourRelations.add(stuCourRelations);
- }
- }
-
- package com.qf.neo4j.entity;
-
- import lombok.Data;
- import org.springframework.data.neo4j.core.schema.GeneratedValue;
- import org.springframework.data.neo4j.core.schema.Id;
- import org.springframework.data.neo4j.core.schema.Node;
-
- import java.math.BigDecimal;
-
- /**
- * 课程
- */
- @Data
- @Node(primaryLabel = "course")
- public class Course {
- @Id
- private Long id;
- private String courseName;
- private BigDecimal price;
- }
-
- package com.qf.neo4j.entity;
-
- import lombok.Data;
- import org.springframework.data.neo4j.core.schema.*;
- import java.util.Date;
-
- @Data
- @RelationshipProperties
- public class StuCourRelations {
- @Id
- @GeneratedValue
- private Long id;
-
- /**
- * 选课的时间
- */
- private Date time;
- @TargetNode
- private Course course;
- }

4). 操作Neo4j
- /**
- * spring提供的操作对象
- */
- @Autowired
- private Neo4jTemplate neo4jTemplate;
-
- /**
- * 原生操作对象,获取Session进行原生操作
- */
- @Autowired
- private Driver driver;
保存完整关系
- /**
- * 添加节点
- */
- @Test
- public void insert(){
- //准备一个学生节点
- Student student = new Student();
- student.setId(5L);
- student.setName("小明");
- student.setAge(18);
- student.setSex("男");
-
- //准备两个课程节点
- Course course1 = new Course();
- course1.setId(100L);
- course1.setCourseName("Java高级开发");
- course1.setPrice(BigDecimal.valueOf(199.99));
-
- Course course2 = new Course();
- course2.setId(101L);
- course2.setCourseName("C语言基础");
- course2.setPrice(BigDecimal.valueOf(259.99));
-
- //创建学生和课程的关系
- StuCourRelations stuCourRelations = new StuCourRelations();
- stuCourRelations.setCourse(course1);
- stuCourRelations.setTime(new Date());
- student.addStuCourRelations(stuCourRelations);
-
- StuCourRelations stuCourRelations2 = new StuCourRelations();
- stuCourRelations2.setCourse(course2);
- stuCourRelations2.setTime(new Date());
- student.addStuCourRelations(stuCourRelations2);
-
- neo4jTemplate.save(student);
- }

追加关系
- /**
- * 追加节点
- */
- @Test
- public void append(){
- //将学生和课程关联起来
- Course course = neo4jTemplate.findById(156L, Course.class).get();
- Student student = neo4jTemplate.findById(5L, Student.class).get();
- System.out.println(student);
-
- //创建关联关系
- StuCourRelations courRelations = new StuCourRelations();
- courRelations.setTime(new Date());
- courRelations.setCourse(course);
-
- student.addStuCourRelations(courRelations);
- //保存关系
- neo4jTemplate.save(student);
- }

查询数据
- /**
- * 根据CQl查询
- */
- @Test
- public void query(){
- //查询id为5的学生1..2级相关的节点
- String cql = """
- match p = (n:student {id:5}) -[r*1..2]- (m)
- return p
- """;
-
- List<Course> result = neo4jTemplate.findAll(cql, Course.class);
- System.out.println("查询集合:" + result);
- }
感谢大家观看Neo4j 5.x的入门篇,因为篇幅所限,还有很多语法和使用技巧没办法写出来,感兴趣的小伙伴可以去官网学习下Neo4j更高级的用法,或者私信我获取更多学习资料。关注千锋官方博客,干货天天都不断哦!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。