当前位置:   article > 正文

你知道图数据库-Neo4j是咋回事吗?来看看_图数据库和neo4j

图数据库和neo4j

一. 背景介绍

最近有粉丝问我,百度百科中的明星关系效果是如何实现的呢?比如下图这种效果:

图片

这种功能可以用关系型数据库来实现吗?答案当然是肯定的!其实简单的关系维护,本就是关系型数据库擅长的事情,但如果关系维度过多且关联足够复杂,还适合用关系型数据库吗?

比如实际生活中,人与人之间可能存在多重关系与交集,且分别还有自己的社交圈,圈子与圈子之间又存在各种交集。另外除了人与人之间的关系,还牵连到人物本身的作品、成绩、历史事件的关系等等...... 想想是不是就挺复杂的了?且不说构建这么庞大且复杂的关系之后,查询性能是否可以保证,仅仅只是这些人物、作品、地点、成绩、事件、关系等表结构的设计,就够开发者喝一壶的了。

类似的场景实际上还有很多,比如天眼查里的企业、法人、股东、投资者、事件等之间的关系;再比如商城项目中,商铺、买家、商品、订单、渠道之间的关系等。对于类似这种功能,如果仅仅只是依靠关系型数据库,一旦碰到深度关联的需求,那就等着系统重构吧。

那该怎么解决当然就是今天的主角 - 图数据库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

  1. neo4j:
  2. image: neo4j:5.5.0-community
  3. container_name: neo4j
  4. ports:
  5. - 7474:7474
  6. - 7687:7687
  7. environment:
  8. NEO4J_AUTH: neo4j/neo4j123456
  9. volumes:
  10. - ./neo4j/data:/data
  11. - ./neo4j/logs:/logs
  12. - ./neo4j/conf:/var/lib/neo4j/conf
  13. - ./neo4j/import:/var/lib/neo4j/import
  14. 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布尔值
byte8位整数
short16位整数
int32位整数
long64位整数
float32位浮点数
double64位浮点数
char16位无符号整数代表的字符u0000 to uffff
stringUnicode字符序列
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语句一起使用。

查找所有节点

  1. match (n)
  2. return n

查询带有某个标签的所有节点​​​​​​​

  1. //返回所有<电影>节点
  2. match (n:Movie)
  3. return n
  4. //返回所有<电影>节点的<标题>属性值以及节点的所有<标签>
  5. match (n:Movie)
  6. return n.title, labels(n)

查询关联节点

  1. //返回Tom Hanks有关系的所有节点
  2. match (n {name: 'Tom Hanks'}) -- (m)
  3. return m
  4. //返回Tom Hanks有参与的所有电影节点
  5. match (n {name: 'Tom Hanks'}) -- (m:Movie)
  6. return m
  7. //返回Tom Hanks导演(:DIRECTED)的所有电影节点
  8. match (n {name: 'Tom Hanks'}) -[:DIRECTED]- (m:Movie)
  9. return n,m //同时返回两种节点
  10. //返回所有和电影《A Few Good Men》有关的人物节点
  11. match (m:Movie {title:"A Few Good Men"}) <-[r]- (n:Person)
  12. return n,m
  13. //同上,关系方向相反
  14. match (n:Person) -[r]-> (m:Movie {title:"A Few Good Men"})
  15. return n,m
  16. //返回所有和电影《A Few Good Men》有关的人物的关系类型
  17. match (m:Movie {title:"A Few Good Men"}) <-[r]- (n:Person)
  18. return type[r]
  19. //返回电影《Something's Gotta Give》参与(:ACTED_IN)或者编写(:WROTE)人物节点
  20. match (m {title:"Something's Gotta Give"}) <-[:WROTE|:ACTED_IN]- (n:Person)
  21. return n,m
  22. //查询电影和人物之间的关系 包含属性roles,并且值为["Jane"]的相关数据
  23. match p = (m:Movie) -[* {roles:["Jane"]}]- (n:Person)
  24. return p

查询多个关系​​​​​​​

  1. //查询人物Susan Sarandon参与的电影(:movie),以及这些电影的导演节点(:DIRECTED)
  2. match (n:Person {name:"Susan Sarandon"}) -[:ACTED_IN]-> (m:Movie) <-[:DIRECTED]-(x:Person)
  3. return m,x

查询多级关系​​​​​​​

  1. 孤驻一郑[电影]
  2. https://mr.baidu.com/14mMKrYDLQ4

查询最短路径

  1. //查找电影《Ninja Assassin》和电影《Speed Racer》之间最短的关系路径
  2. //shortestPath()函数 - 找到两个节点之间的最短路径
  3. match
  4. (m1:Movie {title:"Ninja Assassin"}),
  5. (m2:Movie {title:"Speed Racer"}),
  6. p = shortestPath((m1)-[*0..5]-(m2))
  7. return p
  8. //查找所有最短路径
  9. //allshortestPaths()函数 - 找到两个节点之间所有的最短路径
  10. match
  11. (m1:Movie {title:"Ninja Assassin"}),
  12. (m2:Movie {title:"Speed Racer"}),
  13. p = allshortestPaths((m1)-[*0..5]-(m2))
  14. return p
  15. //查找电影《Ninja Assassin》和电影《Speed Racer》之间最短的所有关系路径,但是要排除掉id为174的关系路径
  16. //relationships(p)函数 - 获取变量p中代表的所有关系
  17. match
  18. (m1:Movie {title:"Ninja Assassin"}),
  19. (m2:Movie {title:"Speed Racer"}),
  20. p = allshortestPaths((m1)-[r*0..5]-(m2))
  21. where none(r in relationships(p) where id(r) = 174)
  22. return p

根据id查询

  1. //查询id为127的节点
  2. match (n)
  3. where id(n) = 127
  4. return n
  5. //查询关系id为117的关系以及连接节点
  6. match p = ()-[r]-()
  7. where id(r) = 177
  8. return p
  9. //多id查询
  10. match p = ()-[r]-()
  11. where id(r) in [177,17]
  12. return p

4.2.3 where 语句

where不能单独使用,只能作为match、optinal match、start和with的一部分。和with和start配合使用时,where用于过滤结果。对于match、optinal match,where用于增加约束,这时不能看成匹配完成后过滤结果。

基本使用

  1. //返回所有电影节点 等同于match(n:Movie) return n
  2. match (n)
  3. where n:Movie return n
  4. //返回所有发行时间(released)小于1995年的电影
  5. match (n:Movie)
  6. where n.released < 1995 return n
  7. //返回所有影评(REVIEWED)中,评分(rating)小于50分的相关数据
  8. match p = () -[r:REVIEWED]- ()
  9. where r.rating < 50 return p
  10. //返回所有不存在released属性的节点
  11. match (n)
  12. where n.released is null return n
  13. //返回所有存在released属性的节点
  14. match (n)
  15. where n.released is not null return n

属性过滤中,n.属性名称 等价于 n["属性名称"]

布尔运算

可以在where中使用布尔运算符:

  • and

  • or

  • not

  1. //返回电影2012年之前发行(released),并且编剧(WROTE)的出生年月(born)不低于1960年的电影与编剧关系
  2. match p = (n:Movie) -[r:WROTE]- (m:Person)
  3. where n.released <= 2012 and not m.born <= 1960 return p

字符串匹配

  1. //返回所有以C开头的电影节点,类似于sql中的like 'C%'
  2. match (n)
  3. where n.title starts with 'C'
  4. return n
  5. //返回所有以c结尾的电影节点,类似于sql中的like '%c'
  6. match (n)
  7. where n.title ends with 'c'
  8. return n
  9. //返回所有包含c的电影节点,类似于sql中的like '%c%'
  10. match (n)
  11. where n.title contains 'c'
  12. return n

注意:

  • 字符串匹配方式,是大小写敏感的

  • 如果要反向匹配,只需要在前面加上not即可,比如 not n.title contains 'c'

路径匹配​​​​​​​

  1. //返回Jim Cash编剧的所有电影节点
  2. match (n:Person),(m)
  3. where n.name = 'Jim Cash' and (n)-[:WROTE]->(m)
  4. return n,m
  5. //返回Jim Cash编剧的所有电影节点
  6. match (n:Person),(m)
  7. where n.name = 'Jim Cash' and (n)-[:WROTE]->(m)
  8. return n,m
  9. //返回Meg Ryan没有参演的所有电影节点
  10. match (n:Person),(m)
  11. where n.name = 'Meg Ryan' and m:Movie and not (n)-[:ACTED_IN]->(m)
  12. return n,m
  13. //返回参演过RescueDawn电影的所有人员节点
  14. match(n)
  15. where (n)-[:ACTED_IN]->(:Movie{title: "RescueDawn"})
  16. return n

列表​​​​​​​

  1. //返回姓名为Zach Grenier和Christian Bale的人员节点
  2. match(n)
  3. where n.name in ["Zach Grenier", "Christian Bale"]
  4. return n

注意:in后面是用的[],和sql不一样

4.2.4 create语句

create语句用于创建图元素:节点和关系

创建节点

  1. //创建单个节点
  2. create (n)
  3. //创建多个节点
  4. create (n),(m)
  5. //创建带标签的节点
  6. create (m:Movie)
  7. //创建带多个标签的节点
  8. create (n:Person:Docter)
  9. //创建带标签并且带属性的节点
  10. create(n:Person {name:"张三",age:18})

注意:

  • 创建的节点标签可以完全自定义

  • 创建的节点属性可以完全自定义

创建关系​​​​​​​

  1. //创建<张三><王五>之间的<朋友>关系
  2. match(n1:Person {name:"张三"}),(n2:Person {name:"王五"})
  3. create (n1)-[r:Friend]->(n2)
  4. return r

注意:

  • 创建节点关系时,必须携带关系的方向

  • 这种创建关系的方式,必须保证节点已经存在

  1. //创建<张三><王五>之间的<朋友>关系,并且附带了相识的时间和关系的类型
  2. match(n1:Person {name:"张三"}),(n2:Person {name:"王五"})
  3. create (n1)-[r:Friend {date: "2012-12-01", type:"同窗"}]->(n2)
  4. return r
  5. //创建3个节点的并创建他们的关系
  6. create p=(n:People {name:"关羽"})-[r:Borther {type:"二弟"}]
  7. ->(m:People {name: "刘备"})
  8. <-[r2:Borther {type:"三弟"}]-(k:People {name:"张飞"})
  9. -[r3:Borther {type:"三弟"}]->(n)
  10. return p

五. SpringBoot操作Neo4j

这里大家注意一点哦,带大家用的是最新的Neo4j5.5,最低只支持JDK17,还在用JDK8的小伙伴,给了你们一个升级JDK的理由,嘿嘿。

5.1 基本步骤

1). 添加依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-dependencies</artifactId>
  5. <version>2.5.5</version>
  6. <scope>import</scope>
  7. <type>pom</type>
  8. </dependency>
  9. </dependencies>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-data-neo4j</artifactId>
  13. </dependency>

注意:

  • Neo4j 5.x最低支持JDK17

  • SpringBoot最低要求2.5.5的版本

2). 配置文件​​​​​​​

  1. spring:
  2. neo4j:
  3. uri: bolt://ip:7687
  4. authentication:
  5. username: neo4j
  6. password: neo4j

3). 准备节点实体​​​​​​​

  1. package com.qf.neo4j.entity;
  2. import lombok.Data;
  3. import org.springframework.data.neo4j.core.schema.GeneratedValue;
  4. import org.springframework.data.neo4j.core.schema.Id;
  5. import org.springframework.data.neo4j.core.schema.Node;
  6. import org.springframework.data.neo4j.core.schema.Relationship;
  7. import java.util.ArrayList;
  8. import java.util.HashSet;
  9. import java.util.Set;
  10. @Data
  11. @Node(primaryLabel = "student", labels = {"person"})
  12. public class Student {
  13. @Id
  14. private Long id;
  15. private String name;
  16. private Integer age;
  17. private String sex;
  18. @Relationship(type = "select", direction = Relationship.Direction.OUTGOING)
  19. private Set<StuCourRelations> stuCourRelations = new HashSet<>();
  20. public void addStuCourRelations(StuCourRelations stuCourRelations) {
  21. this.stuCourRelations.add(stuCourRelations);
  22. }
  23. }
  24. package com.qf.neo4j.entity;
  25. import lombok.Data;
  26. import org.springframework.data.neo4j.core.schema.GeneratedValue;
  27. import org.springframework.data.neo4j.core.schema.Id;
  28. import org.springframework.data.neo4j.core.schema.Node;
  29. import java.math.BigDecimal;
  30. /**
  31. * 课程
  32. */
  33. @Data
  34. @Node(primaryLabel = "course")
  35. public class Course {
  36. @Id
  37. private Long id;
  38. private String courseName;
  39. private BigDecimal price;
  40. }
  41. package com.qf.neo4j.entity;
  42. import lombok.Data;
  43. import org.springframework.data.neo4j.core.schema.*;
  44. import java.util.Date;
  45. @Data
  46. @RelationshipProperties
  47. public class StuCourRelations {
  48. @Id
  49. @GeneratedValue
  50. private Long id;
  51. /**
  52. * 选课的时间
  53. */
  54. private Date time;
  55. @TargetNode
  56. private Course course;
  57. }

4). 操作Neo4j​​​​​​​

  1. /**
  2. * spring提供的操作对象
  3. */
  4. @Autowired
  5. private Neo4jTemplate neo4jTemplate;
  6. /**
  7. * 原生操作对象,获取Session进行原生操作
  8. */
  9. @Autowired
  10. private Driver driver;
5.2 相关操作

保存完整关系

  1. /**
  2. * 添加节点
  3. */
  4. @Test
  5. public void insert(){
  6. //准备一个学生节点
  7. Student student = new Student();
  8. student.setId(5L);
  9. student.setName("小明");
  10. student.setAge(18);
  11. student.setSex("男");
  12. //准备两个课程节点
  13. Course course1 = new Course();
  14. course1.setId(100L);
  15. course1.setCourseName("Java高级开发");
  16. course1.setPrice(BigDecimal.valueOf(199.99));
  17. Course course2 = new Course();
  18. course2.setId(101L);
  19. course2.setCourseName("C语言基础");
  20. course2.setPrice(BigDecimal.valueOf(259.99));
  21. //创建学生和课程的关系
  22. StuCourRelations stuCourRelations = new StuCourRelations();
  23. stuCourRelations.setCourse(course1);
  24. stuCourRelations.setTime(new Date());
  25. student.addStuCourRelations(stuCourRelations);
  26. StuCourRelations stuCourRelations2 = new StuCourRelations();
  27. stuCourRelations2.setCourse(course2);
  28. stuCourRelations2.setTime(new Date());
  29. student.addStuCourRelations(stuCourRelations2);
  30. neo4jTemplate.save(student);
  31. }

追加关系​​​​​​​

  1. /**
  2. * 追加节点
  3. */
  4. @Test
  5. public void append(){
  6. //将学生和课程关联起来
  7. Course course = neo4jTemplate.findById(156L, Course.class).get();
  8. Student student = neo4jTemplate.findById(5L, Student.class).get();
  9. System.out.println(student);
  10. //创建关联关系
  11. StuCourRelations courRelations = new StuCourRelations();
  12. courRelations.setTime(new Date());
  13. courRelations.setCourse(course);
  14. student.addStuCourRelations(courRelations);
  15. //保存关系
  16. neo4jTemplate.save(student);
  17. }

查询数据​​​​​​​

  1. /**
  2. * 根据CQl查询
  3. */
  4. @Test
  5. public void query(){
  6. //查询id为5的学生1..2级相关的节点
  7. String cql = """
  8. match p = (n:student {id:5}) -[r*1..2]- (m)
  9. return p
  10. """;
  11. List<Course> result = neo4jTemplate.findAll(cql, Course.class);
  12. System.out.println("查询集合:" + result);
  13. }

六. 结语

感谢大家观看Neo4j 5.x的入门篇,因为篇幅所限,还有很多语法和使用技巧没办法写出来,感兴趣的小伙伴可以去官网学习下Neo4j更高级的用法,或者私信我获取更多学习资料。关注千锋官方博客,干货天天都不断哦!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/748089
推荐阅读
相关标签
  

闽ICP备14008679号