文章目录
前言
Neo4j 是一个高性能的 NoSQL 图形数据库系统,它使用图形模型来存储和处理数据。在 Neo4j 中,数据被表示为节点(Nodes)和关系(Relationships),这使得它非常适合用于存储高度互联的数据集。
1.安装
图数据库驱动版本:3.5.31(社区版)
官方下载地址为:https://neo4j.com/download-center/#releases
2.访问neo4j图形化界面
地址:localhost:7474,初次访问账号密码都是neo4j
3.使用
图数据中只有两种结构。Node节点和Relationship关系
在neo4j中使用的数据库语言是cypher图形查询语言,类比sql的cql
4.简单的cql
4.1 创建一个包含属性的节点
Cql:create (n:Building{buildingName:"测试建筑1"})
4.2 创建一个关系
Cql:create (b1:Building{buildingName:"测试建筑1"})-[:direct]->(b2:Building{buildingName:"测试建筑2"})
4.3 查看所有节点和关系
Cql:match (n) return n
4.4 简单说明
()代表一个节点[]代表一个关系,这两种括号内第一个 : 确定节点类和关系类
例如:(:Building)代表一个Building类的节点,(:Room)代表一个Room类节点,[:direct] 代表一个direct类关系,冒号之前的可以作为当前节点标签或者关系的变量简称
例如:(n:Building) n就可以作为所有Building标签的的简称 [r:direct]是所有direct关系的简称
4.5 查询节点
4.5.1通过标签查询
查询所有的Building标签的节点 : match (n:Building) return n
4.5.2 通过id查询
查询指定id的节点:match (n) where id(n)=219302 return n
4.5.3 查询没有关联关系的节点
查询没有关联关系的节点:MATCH (n) WHERE NOT (n)-–() return n
4.6 更新节点的属性
4.6.1 通过节点id更新节点属性
如果要修改单个节点的属性,推荐使用id或者使用唯一属性作为标识修改属性 使用id:match (n) where id(n)=219301 with n set n.buildingName="修改建筑1"
4.6.2 更新所有该标签的属性
修改所有该标签且该属性的节点的属性:match (n:Building{buildingName:"测试建筑2"}) with n set n.buildingName = "修改建筑2"
4.7 移除节点
思路是先查出来再删除,但是删除前需要先移除当前节点所有的关联关系
4.7.1 移除关系
移除所有节点之间的direct关系:match ()-[r:direct]->() with r delete r
4.7.2 移除没有任何关系的节点(通过唯一标识移除)
可以通过id或唯一标签移除指定节点,或者同一标签的所有节点:match (n) where id(n) = 219302 with n delete n
4.7.3 移除所有的节点和关系(清空)
删除所有节点和关系:MATCH (r) DETACH DELETE r
4.8 查询路线
先创建一套节点和关系 路线查询
4.8.1 查询建筑1到所有的Floor标签的路线
结果集合
4.8.2 查询建筑1到所有的Room标签所有路线
4.8.2.1 逐层查询路线
查询建筑1到房间标签的所有路径: match p=(b:Building{buildingName:"建筑1"})-[]->(f:Floor)-[]->(r:Room) return p
结果集合
如果建筑1到房间标签没有出了楼层Floor以外的其他节点,可以直接使用()代替(f:Floor)
4.8.2.2 省略写法
match p=(b:Building{buildingName:"建筑1"})-[*2]->(r:Room) return p
查询语句翻译:查询路线p = 第一个节点b是Building标签,属性建筑名为“建筑1”,中间经过任意关系两次[*2](也可以认为深度为2),到达最后一个节点r是Room标签,返回所有路线。
4.8.3 两个节点之间关系
任意两个关系:()-[*2]->()
任意两个以内:()-[*..2]->()
任意两个以上:()-[*2..]->()
任意一个以上两个以内:()-[*1..2]->()
5.进阶的cql
5.1 使用match查询的结果作为条件,进行下一次操作
进行下一次操作包含增删改查,都可以。使用with关键字
match p=(b:Building{buildingName:"建筑1"})-[]->(f:Floor{floorName:"楼层1"}) with f match p=(f)-[]->(r:Room) return p,r
With之后查询不需要使用逗号隔开再次查询,返回可以是所有出现过的简称
结果集合
5.2 使用match查询的结果作为条件创建关联关系
match (b1:Building{buildingName:"建筑1"})-[*2]-(r1:Room{roomName:"房间101"})
match (b2:Building{buildingName:"建筑2"})-[*2]-(r2:Room{roomName:"房间101"})
with r1,r2
create (r1)-[:Room2Room]->(r2)
create (r2)-[:Room2Room]->(r1)
5.3 查询无方向路线
之前查询路线的时候使用的是()-[]->(),这个是带有方向关系的路线。查询没有方向关系使用的是()-[]-(),但是创建关联关系的时候不能创建无方向的关系。如图5.2(2)中建筑1到建筑2之间的关系是
(b1:Building{buildingName:”建筑1”})-[]->()-[]->(r1:Room)->(r2:Room)<-[]-()<-[]-(b2:Building{buildName:”建筑2”})
查询建筑1到建筑2之间所有的路线
match p=(b1:Building{buildingName:“建筑1”})-[*]-(b2:Building{buildingName:“建筑2”}) return p
结果集合
5.4 定向查询
查询有房间关联关系的两栋建筑节点
match (b1)-[]->(f1)-[]->(r1:Room)-[:Room2Room]->(r2:Room)<-[]-(f2)<-[]-(b2:Building) return b1,b2
6.cql可以返回的结果
6.1 cql返回节点
Match (n:Building) return n
6.2 cql返回关系
Match (n:Building)-[r:Building2Floor]->() return r
6.3 cql返回属性
Match (n:Building{buildingId:100000000000}) return n.buildingName
6.4 cql返回路线
Match p=(n:Building{buildingId:100000000000})-[]->() return p
6.5 cql返回路线上所有的节点
Match p=(n:Building{buildingId:100000000000})-[]->() return NODES(p)
一条路线上所有的节点为一个集合体
6.6 cql返回路线长度
Match p=(n:Building{buildingId:100000000000})-[*]->() return Length(p)
6.7 返回路线上所有节点(去重)
正常执行cql查询所有节点:Match p=(n:Building{buildingId:100000000000})-[*2]->(r1:Room)-[]-(r2:Room) return NODES(p)
有两个条结果,因为再room到room之间有两种关系,一个正向的Room2Room一个是反向的Room2Room,
但是这两两条路线上经过的节点都是一样的可以使用distinct去重实现,返回节点集合唯一
Match p=(n:Building{buildingId:100000000000})-[*2]->(r1:Room)-[]-(r2:Room) return distinct NODES§
7.将Neo4j集成到Springboot中
集成Neo4j采用的是Spring-data-neo4j。Springboot版本是2.4.0对应Neo4j的driver驱动版本是6.0.1
7.1 导入依赖
Pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
这里说明一下,Springboot2.4之前的Spring-data-neo4j的类和2.4之后的类有很大的不同,做了比较大的改动
7.2 配置参数
Application.yml
spring:
neo4j:
uri: bolt://localhost:7687
authentication:
username: neo4j
password: 123456
使用bolt协议,也可以使用http协议,端口是7474,但是需要单独引用http驱动包。暂时不做介绍。
7.3 设置节点对象
7.4 设置关系对象
7.5 创建持久层
Spring-data-neo4j和Spring-data-jpa类似
7.5.1 节点持久层
继承Spring-data-neo4j内置的Neo4jRepository可以有很多基础的方法
也可以通过节点属性来查讯,例如findByBuildName可以直接通过建筑名称查询。
7.5.2 关系持久层
8.Sprinboot中neo4j的增删改查
8.1添加节点
8.1.1添加单个节点
public void addBuildingNode() {
Building building = new Building();
building.setBuildingId(10000L)
.setBuildingName("Boot添加的一个建筑节点");
buildingRepository.save(building);
}
执行之后
8.1.2添加树形结构节点
也可以直接添加树形结构。会自动添加补全关系和节点
例如:
public void addBuildingNode() {
Building building = new Building();
building.setBuildingId(20000L).setBuildingName("测试添加树形节点");
Floor floor1 = new Floor();
floor1.setFloorId(21000L).setFloorName("一层");
Floor floor2 = new Floor();
floor2.setFloorId(22000L).setFloorName("二层");
List<Floor> floorList = new ArrayList<>();
floorList.add(floor1);
floorList.add(floor2);
building.setFloorList(floorList);
buildingRepository.save(building);
}
注:这种结果是建立于创建的对象是有关联关系的。就是有@Relationship注解的,且注解使用正确
8.2移除节点
移除节点和再cql中移除一样,需要保证当前节点没有其他关系之后,才可以直接移除选中的节点。
使用neo4jRepository中自带的delete方法移除节点时,需要保证当前对象有图数据库中的节点id。
public void addBuildingNode() {
Building building = new Building();
building.setId(219319L);
buildingRepository.delete(building);
}
之前创建的 "Boot添加的一个建筑节点"建筑节点已经被移除
8.3 更新节点
和jpa类似,使用save方法,传入对象的时候带入对象在图数据库中的节点id,就会修改属性。
更新节点也可以修改其 关系下的树形结构。
public void addBuildingNode() {
Building building = new Building();
building.setId(219339L);
building.setBuildingName("更新建筑名称");
Floor floor = new Floor();
floor.setFloorId(23000L)
.setFloorName("三层");
List<Floor> floorList = new ArrayList<>();
floorList.add(floor);
building.setFloorList(floorList);
buildingRepository.save(building);
}
因为之前添加的楼层Floor节点没有被移除,所以还会存在,但是关联关系已经修改。如果没有传入floorList的集合,就会直接移除所有的floor关系。但是floor节点不会被移除。
8.4 查询节点
8.4.1查询全部
public List<Building> addBuildingNode() {
List<Building> list = buildingRepository.findAll();
return list;
}
在springboot2.4之后,查询全部findAll()方法,不能在指定深度,默认只能再多查到下一层级的接就是Floor节点的数据
8.4.2使用属性查询
public List<Floor> addBuildingNode() {
List<Floor> list = floorRepository.findByFloorId(23000L);
return list;
}
注:能直接查询到指定楼层,但是需要用list集合来接收。
且查询到的结果层级深度只有当前,不会延伸到下一层
8.5自定义查询
如果需要比较复杂的cql,有两种方法可以使用。
一种是使用@Query注解
一种是使用Neo4jClient
8.5.1使用@Query注解查询
将注解添加到持久层接口的方法上。
该种方法适用于cql比较固定,不会改变的情况。
在使用@Query注解之前要先了解查询结果会对应的类是那些
○返回节点 - 直接使用节点对象集合接收 例如 List<Building>
○返回关系 - 直接使用关系对象接收
○返回属性 - 使用属性对应的数据类型接收
○返回路线 - 使用PathValue接收 例如 List<PathValue> PathValue是Spring-data-neo4j中带的类,用于接收路线对象
○返回路线上的节点集合 - 使用NodeValue接收 例如List<NodeValue> 也是自带的类,用于接收节点对象
○如果是多条路线的节点集合 - 可以使用ListValue接收 例如 List<ListValue> 自带类,可用于接收节点集合
○如果是增删改操作没有返回结果即void
例子:
返回节点
返回属性
返回路线
返回路线上的节点结合
增删改操作
8.5.2使用Neo4jClient查询
注入Neo4jClient
直接编写cypher语言,手动执行编写好的cypher语言
该种方法对于可能会频繁变动的cql语句,动态的cql语句支持较好。
@Autowired
private Neo4jClient neo4jClient;
public Collection<Map<String, Object>> addBuildingNode() {
String cypher = "match (:Building)-[]->(f:Floor) return f";
Neo4jClient.RunnableSpec query = neo4jClient.query(cypher);
Collection<Map<String, Object>> all = query.fetch().all();
all.forEach(map -> {
map.forEach((k,v) -> {
Node node = (Node) v;
System.out.println("节点标签:" + node.labels());
System.out.println("节点id:" + node.id());
System.out.println("节点属性:" + node.asMap());
});
});
return all;
}
使用该种方式查询出来的结果需要自己一步步解析,当前是查询的是节点,使用的是Node
将Object对象转换,对应路线对象也有接收的是PathValue,key值对应的是return返回的内容,例如return f ,map的key值就是 f
9.Neo4j的多数据源事务
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories;
import org.springframework.data.transaction.ChainedTransactionManager;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableReactiveNeo4jRepositories
@EnableTransactionManagement
class MyConfiguration {
public static final String NEO4J_URL = "bolt://localhost/7687";
public static final String USERNAME = "neo4j";
public static final String PASSWORD = "123456";
/**
* 默认数据库事务
* @param dataSource
* @return
*/
@Bean("transactionManager")
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 2. Neo4J的事务管理
@Bean("neo4jTransactionManager")
public Neo4jTransactionManager neo4jTransactionManager(Driver driver) {
return new Neo4jTransactionManager(driver);
}
@Bean
public Driver neo4jDriver() {
return GraphDatabase.driver(NEO4J_URL, AuthTokens.basic(USERNAME, PASSWORD));
}
// 需要使用多种事务时,需要加。
@Autowired
@Primary
@Bean(name = "multiTransactionManager")
public PlatformTransactionManager multiTransactionManager(
Neo4jTransactionManager neo4jTransactionManager,
DataSourceTransactionManager mysqlTransactionManager) {
return new ChainedTransactionManager(
neo4jTransactionManager, mysqlTransactionManager);
}
}
在有图数据和关系型数据库需要一起操作的时候使用multiTransactionManager
使用方式
如果只涉及到单个数据源就可以使用neo4jTransactionManager或者transactionManager
10.参考网站
Neo4j官网https://neo4j.com/
Spring-data-neo4j-6.0.1Boot官方文档:
https://docs.spring.io/spring-data/neo4j/docs/6.0.1/reference/html/#preface