一. 背景介绍
最近有粉丝问我,百度百科中的明星关系效果是如何实现的呢?比如下图这种效果:
这种功能可以用关系型数据库来实现吗?答案当然是肯定的!其实简单的关系维护,本就是关系型数据库擅长的事情,但如果关系维度过多且关联足够复杂,还适合用关系型数据库吗?
比如实际生活中,人与人之间可能存在多重关系与交集,且分别还有自己的社交圈,圈子与圈子之间又存在各种交集。另外除了人与人之间的关系,还牵连到人物本身的作品、成绩、历史事件的关系等等...... 想想是不是就挺复杂的了?且不说构建这么庞大且复杂的关系之后,查询性能是否可以保证,仅仅只是这些人物、作品、地点、成绩、事件、关系等表结构的设计,就够开发者喝一壶的了。
类似的场景实际上还有很多,比如天眼查里的企业、法人、股东、投资者、事件等之间的关系;再比如商城项目中,商铺、买家、商品、订单、渠道之间的关系等。对于类似这种功能,如果仅仅只是依靠关系型数据库,一旦碰到深度关联的需求,那就等着系统重构吧。
那该怎么解决呢?当然就是今天的主角 - 图数据库Neo4j来搞定咯!
这是一张Neo4j官方提供的电影、导演、演员、主角的关系数据,怎么样?看着是不是就很高端大气上档次?既然如此,那我就带大家认识一下Neo4j吧!
二. 图数据库概述
2.1 什么是图数据库?
图数据库(Graph Database)是基于图论实现的一种NoSQL数据库。在图论中,图的基本元素为节点和边,在图数据库中对应的就是节点和关系。
在图数据库中,数据与数据之间的关系通过节点和关系构成一个图结构,并在此结构上实现数据库的所有特性(CRUD),还有事务处理等能力。
2.2 图数据库 vs 关系型数据库
关系型数据库的弊端
关系型数据库将高度结构化的数据存储在二维表格中,并且通过外键约束来实现表与表之间的关联关系。关系数据库通过外键去主表中寻找匹配的主键记录进行搜索匹配,这种操作是计算密集型和内存密集型的,并且操作次数是表中记录的指数级别,当数据量巨大,或者关系特别复杂(关联表特别多)时,查询成本将会变的巨大。
图数据库的优势
图数据库中,关系是最重要的元素。每个节点都直接包含一个关系列表,关系列表存放此节点与其他节点的关系记录。关系记录类型和方向组织起来,并且保存附加属性,当进行连接Join时,图数据库都将使用关系列表直接放行连接的节点,无须进行记录搜索匹配的操作。
这种预先保存关系列表的方式,使得图数据库能够提供比关系型数据库高几个数量级的性能,特别对于复杂连接的查询,Neo4j可以实现毫秒级的响应。
2.3 图数据库的运用场景
学习一个技术,我觉得最重要的就是这个技术能解决什么问题?Neo4j的运用场景很多,以下就是给大家列举的几个场景的运用场景:
朋友圈关系维护,好友推荐,可能认识的人
金融圈行为管理,防电话欺诈
电商项目中商品多维度智能推荐
短视频搜索联想
.......
三. Neo4j基础
3.1 概述
官网:https://neo4j.com/
Neo4j是由Java实现的开源NoSQL图数据库。2003年研发,2007年正式发布第一版。Neo4j实现了专业数据库级别的图数据库模型的存储,与普通的图处理不同,Neo4j提供了完整的数据库特性,包括ACID事务、集群支持、备份、故障转移等,使其适用于企业级生产环境下的各种应用。
废话不多说,咱们直接开整吧!!!!!!
3.2 Neo4j安装
这里我们选择比较简单的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/
3.3 Neo4j的基本元素
节点
关系
3.3.1 节点
节点(Node)是图数据库中的一个基本元素,用以表示一个实体记录,就像关系型数据库中的一条记录一样。每个实体可以有0到多个属性(Property),这些属性以key-value形式存在。同时每个节点还具有相应的标签(Label),用来区分不同类型的节点。
3.3.2 关系
关系(Relationship)也是图数据库中的基本元素。关系用来连接两个节点,有起始节点和终止节点,另外,与节点一样,关系可以包含多个属性,但是只能有一个类型(Type)。
注意:
在图遍历时,可以指定关系遍历的方向或者指定为无方向,故在创建关系时,不用给两个节点创建双向关系;
一个节点可以存在指向自己的关系。
3.3.3 属性
节点和关系都可以拥有多个属性,属性由键值对组成,属性值可以是基本的数据类型,也可以由基本数据类型组成的数组。
注意:
属性没有null的概念;
属性不需要时,可以直接将整个键值对都移除,可以使用is null判断属性是否存在。
类型 | 说明 | 备注 |
boolean | 布尔值 | |
byte | 8位整数 | |
short | 16位整数 | |
int | 32位整数 | |
long | 64位整数 | |
float | 32位浮点数 | |
double | 64位浮点数 | |
char | 16位无符号整数代表的字符 | u0000 to uffff |
string | Unicode字符序列 |
3.3.4 路径
使用节点和关系创建一个图后,图中任意两个节点间,都是可能存在路径的。路径有长度的概念,也就是路径中关系的条数。简单来说,如果两个节点之间只有一个关系,那路径的长度就是1
四. Cypher语句(CQL语句)
4.1 Cypher概述
Cypher是一种声明书图数据库查询语言,能高效的查询和更新图数据。有点类似关系型数据库中的SQL语言。
4.1.1 节点语法
Cypher采用一对括号表示节点,比如()、(person)
():表示一个匿名节点;(n):表示一个(一批)的节点,并且用一个变量n代表这个(这批)节点;(:Label):表示一个(一批)标签为Label的节点;(n:Label):表示一个(一批)标签为Label的节点,并且用一个变量n代表这个(这批)节点;(n:Label {key:"value"}):表示一个(一批)标签为Label,属性key的值为value的节点,并且用一个变量n代表这个(这批)节点。
4.1.2 关系语法
Cypher使用 -- 表示一个关系,有方向的关系加上一个箭头即可。反括号[...]用于添加详情,里面可以包括变量、属性和类型信息。
--:无方向的关系,只表示关系的存在性;-->:向右方向的关系;<--:向左方向的关系;-[r]->:表示一个(一批)关系,并且用一个变量r表示这个(这批)关系;-[:Type]->:表示一个(一批)类型为Type的关系;-[r:Type]->:表示一个(一批)类型为Type的关系,并且用一个变量r表示这个(这批)关系;-[r:Type {key: ["value"]}]->:表示一个(一批)类型为Type、属性key的值为数组["Neo"]的关系,并且用一个变量r表示这个(这批)关系。
4.2 基本语法
CQL命令 | 说明 |
create | 创建节点、关系和属性 |
match | 查询节点、关系和属性 |
return | 返回查询结果 |
where | 过滤查询数据 |
delete | 删除节点和关系 |
remove | 删除节点/关系的属性 |
order by | 排序查询数据 |
set | 添加或更新标签 |
4.2.1 准备测试数据
查看Neo4j数据库
创建测试数据
4.2.2 match语句
使用指定的模式检索数据库,通常与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不一样
4.2.4 create语句
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
五. SpringBoot操作Neo4j
这里大家注意一点哦,带大家用的是最新的Neo4j5.5,最低只支持JDK17,还在用JDK8的小伙伴,给了你们一个升级JDK的理由,嘿嘿。
5.1 基本步骤
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;
5.2 相关操作
保存完整关系
/**
* 添加节点
*/
@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);
}
查询数据文章来源:https://www.toymoban.com/news/detail-659903.html
/**
* 根据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更高级的用法,或者私信我获取更多学习资料。关注千锋官方博客,干货天天都不断哦!文章来源地址https://www.toymoban.com/news/detail-659903.html
到了这里,关于你知道图数据库-Neo4j是咋回事吗?来看看的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!