Spring Data Neo4j 学习
目录
根据这个 https://github.com/WuYiheng-Og/neo4j_springboot 来实践
注:本文所使用SpringBoot版本为3.5.4,Java17,neo4j-5.25.1
本文将会结合官网的一个 【导演-电影-演员】关系图来进行实现。以新海诚导演的《你的名字》为例。
![[Pasted image 20250803165044.png]](https://doucment-img.oss-cn-hangzhou.aliyuncs.com/neo4j/Pasted%20image%2020250803165044.png)
{
"identity": 22,
"labels": [
"Movie"
],
"properties": {
"tagline": "影片讲述了男女高中生在梦中相遇,并寻找彼此的故事。",
"title": "你的名字"
},
"elementId": "4:539d38bc-240d-4be5-8ab6-d144d21a7c26:22"
}
{
"identity": 26,
"labels": [
"Person"
],
"properties": {
"born": 1997,
"name": "上白石萌音"
},
"elementId": "4:539d38bc-240d-4be5-8ab6-d144d21a7c26:26"
}
{
"identity": 27,
"labels": [
"Person"
],
"properties": {
"born": 1993,
"name": "神木隆之介"
},
"elementId": "4:539d38bc-240d-4be5-8ab6-d144d21a7c26:27"
}
{
"identity": 28,
"labels": [
"Person"
],
"properties": {
"born": 1973,
"name": "新海诚"
},
"elementId": "4:539d38bc-240d-4be5-8ab6-d144d21a7c26:28"
}
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tmesh</groupId>
<artifactId>neo4jdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>neo4jdemo</name>
<description>neo4jdemo</description>
<properties> <java.version>17</java.version>
</properties>
<dependencies> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
<!-- neo4j 驱动 这个需要自己手动添加一下,主意,这里不指定版本,会自动选择 4x 版本 -->
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>5.28.5</version>
</dependency>
<dependency> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build> <plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置 Neo4j
spring:
neo4j:
uri: bolt://<YourNeo4jIpAddress>:7687
authentication:
username: <yourUserName>
password: <yourPassword>
# 指定数据库
data:
neo4j:
database: <yourDatabase>
创建 utils 包,并在该包下创建 ExampleCommandLineRunner 来装配 Driver 和 Session
package cn.tmesh.neo4jdemo.utils;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Session;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class ExampleCommandLineRunner implements CommandLineRunner {
private final Driver driver;
private final ConfigurableApplicationContext applicationContext;
public final Session session;
@Bean
Session session(){
return session;
}
// Autowire the Driver bean by constructor injection
public ExampleCommandLineRunner(Driver driver, ConfigurableApplicationContext applicationContext) {
this.driver = driver;
this.applicationContext = applicationContext;
this.session = driver.session();
}
@Override
public void run(String... args) throws Exception {
}
}
创建实体类节点
创建 entity 包,添加实体类:PersonEntity 和 MovieEntity
package cn.tmesh.neo4jdemo.entity;
import lombok.Data;
import org.springframework.data.neo4j.core.schema.*;
import java.util.ArrayList;
import java.util.List;
@Node(labels = "Movie")
@Data
public class MovieEntity {
@Id
@GeneratedValue // Id自增
private Long id;
private final String title;
@Property("tagline") // 映射到neo4j的属性名
private final String description;
public MovieEntity(String title, String description) {
this.id = null;// 生成 node 时自动生成
this.title = title;
this.description = description;
}
// 用户指定特定的Id
public MovieEntity withId(Long id) {
if (this.id!= null && this.id.equals(id)) {
return this;
} else {
MovieEntity newObject = new MovieEntity(this.title, this.description);
newObject.id = id;
return newObject;
}
}
// 定义一个关系(参演)
@Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING)
private List<Roles> actorsAndRoles = new ArrayList<>();
// 定义一个关系(导演)
@Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING)
private List<PersonEntity> directors = new ArrayList<>();
// 注意这些关系最终的箭头指向是当前实体,即TargetNode(PersonEntity)->当前定义Relationship的实体(MovieEntity)
}
package cn.tmesh.neo4jdemo.entity;
import lombok.Data;
import org.springframework.data.neo4j.core.schema.*;
/**
* 创作一个对应 Person 实体对象 -> 对应我们 Neo4j 数据库中的 Node 对象
*/
@Node("Person")
@Data
public class PersonEntity {
@Id
@GeneratedValue private Long id;
private String name;
private Integer born;
public PersonEntity(Integer born, String name) {
this.name = name;
this.born = born;
}
}
节点间的关系
package cn.tmesh.neo4jdemo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.data.neo4j.core.schema.RelationshipId;
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
import org.springframework.data.neo4j.core.schema.TargetNode;
import java.util.List;
/**
* 定义一个关系属性
*/
@RelationshipProperties
public class Roles {
@RelationshipId
private Long id;
private final List<String> roles;
@TargetNode // 相当于@StartNode
private final PersonEntity person;
// 参数1是目标关系实体节点 参数2是关系属性
// Roles 参数1:Person 实体,演员的出生年和姓名;参数2:演员名字列表(考虑到一个演员可能参演多个角色)
public Roles(PersonEntity person, List<String> roles) {
this.person = person;
this.roles = roles;
}
public List<String> getRoles() {
return roles;
}
}
注意这些关系 @TargetNode 修饰的是关系箭头的尾部, 最终的箭头指向是当前实体,即TargetNode(PersonEntity)->当前定义 Relationship 的实体(MovieEntity)
即 MovieEntity 里面的这一段
// 定义一个关系(参演)
@Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING)
private List<Roles> actorsAndRoles = new ArrayList<>();
// 定义一个关系(导演)
@Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING)
private List<PersonEntity> directors = new ArrayList<>();
// 注意这些关系最终的箭头指向是当前实体,即TargetNode(PersonEntity)->当前定义Relationship的实体(MovieEntity)
}
使用 Neo4jTemplate 对图数据进行 CRUD
创建节点和关系
@Test
void creteNodeAndRelationship() {
// 创建节点实体
MovieEntity movie = new MovieEntity("你的名字","影片讲述了男女高中生在梦中相遇,并寻找彼此的故事。");// 电影实体节点
// 定义(参演)关系
// new Roles 参数1:Person实体,演员的出生年和姓名;参数2:演员名字列表(考虑到一个演员可能参演多个角色)
// 参数1是目标关系实体节点 参数2是关系属性
Roles roles1 = new Roles(new PersonEntity(1998,"上白石萌音"), Collections.singletonList("宫水三叶"));
Roles roles2 = new Roles(new PersonEntity(1993,"神木隆之介"), Collections.singletonList("立花泷"));
PersonEntity director = new PersonEntity(1973,"新海诚");
// 添加movie的演员实体,加入(参演)关系
movie.getActorsAndRoles().add(roles1);
movie.getActorsAndRoles().add(roles2);
movie.getDirectors().add(director);
// 存入图数据库持久化
neo4jTemplate.save(movie);
}
查询节点
@Test
void selectNode() {
// 查询(不太推荐用Neo4jTemplate进行过滤查询,因为需要手动写cypherQuery,需要开发人员了解一下cypherQuery的写法)
Optional<PersonEntity> person;
// 1. 通过id查询
person = neo4jTemplate.findById(12, PersonEntity.class);
System.out.println("id为12号的Person节点:\n"+person);
// 2. 通过属性查询节点,如name 需要手写cypherQuery语句
Map<String,Object> map = new HashMap<>();
map.put("name","新海诚");
// 两种写法都对,看个人喜好 n是一个变量随意取,{}或者where填写query的filter过滤条件
person = neo4jTemplate.findOne("MATCH (n:Person {name: $name}) RETURN n",map, PersonEntity.class);
// person = neo4jTemplate.findOne("MATCH (n:Person) WHERE n.name = $name RETURN n",map, PersonEntity.class);
System.out.println("\n查询名字为新海诚的Person节点:\n"+person);
// 3. 通过属性关系查询节点
map = new HashMap<>();
map.put("roles",Collections.singletonList("宫水三叶"));
// 方法1.使用toExecutableQuery查询
QueryFragmentsAndParameters parameters = new QueryFragmentsAndParameters(
"MATCH (person:Person) -[ relation:ACTED_IN]-> (movie:Movie) \n" +
"WHERE relation.roles = $roles\n" +
"RETURN person",map);
List<PersonEntity> roles = neo4jTemplate.toExecutableQuery(PersonEntity.class, parameters).getResults();
// 方法2.使用 findOne 查询
// Optional<PersonEntity> roles = neo4jTemplate.findOne(
// "MATCH (person:Person) -[ relation:ACTED_IN]-> (movie:Movie) \n" + // "WHERE relation.roles = $roles\n" + // "RETURN person",map,PersonEntity.class);
System.out.println("\n查询角色为“宫水三叶”的演员:\n"+roles);
}
更新节点信息
@Test
void updateNode() {
Optional<PersonEntity> person;
Map<String,Object> map = new HashMap<>();
map.put("roles",Collections.singletonList("宫水三叶"));
// 1. 通过id查询
person = neo4jTemplate.findById(12, PersonEntity.class);
Long userId = person.get().getId();// 记录当前查询的"新海诚"的节点id
// 更新①---------更新“新海诚”的name为曾用名“新津诚”(这是他的曾用名)
map.put("name","新海诚");
map.put("usedName","新津诚");
QueryFragmentsAndParameters queryFragmentsAndParameters =
new QueryFragmentsAndParameters(
"MATCH (n:Person{name: $name}) SET n.name = $usedName",
map);
neo4jTemplate.toExecutableQuery(
PersonEntity.class,
queryFragmentsAndParameters).getResults();
Optional<PersonEntity> person1 = neo4jTemplate.findById(userId, PersonEntity.class);
System.out.println("\n更新“新海诚”的name为曾用名“新津诚”(这是他的曾用名):\n"+person1);
// 更新②---------更新“新津诚”的name为“新海诚”
person.get().setName("新海诚");
neo4jTemplate.save(person.get());
Optional<PersonEntity> person2 = neo4jTemplate.findById(userId, PersonEntity.class);
System.out.println("\n更新“新津诚”的name为“新海诚”:\n"+person2);
}
删除节点
@Test
void deleteNodeAll() {
// 删除所有节点和关系(删除节点会响应删除关联关系)[也可以用cypherQuery执行,不再赘述]
neo4jTemplate.deleteAll(MovieEntity.class);
neo4jTemplate.deleteAll(PersonEntity.class);
}
完整
/**
* 没有Repository情况下使用Neo4jTemplate操作数据
* @param neo4jTemplate
*/
@Test
void TestNoRepository(@Autowired Neo4jTemplate neo4jTemplate){
// 删除所有节点和关系(删除节点会响应删除关联关系),避免后续创建节点重复影响
neo4jTemplate.deleteAll(MovieEntity.class);
neo4jTemplate.deleteAll(PersonEntity.class);
// 创建节点实体
MovieEntity movie = new MovieEntity("你的名字","影片讲述了男女高中生在梦中相遇,并寻找彼此的故事。");
// new Roles 参数1:Person实体,演员的出生年和姓名;参数2:演员名字列表(考虑到一个演员可能参演多个角色)
// 参数1是目标关系实体节点 参数2是关系属性
Roles roles1 = new Roles(new PersonEntity(1998,"上白石萌音"), Collections.singletonList("宫水三叶"));
Roles roles2 = new Roles(new PersonEntity(1993,"神木隆之介"), Collections.singletonList("立花泷"));
PersonEntity director = new PersonEntity(1973,"新海诚");
// 添加movie的演员实体,加入(参演)关系
movie.getActorsAndRoles().add(roles1);
movie.getActorsAndRoles().add(roles2);
movie.getDirectors().add(director);
// 存入图数据库持久化
neo4jTemplate.save(movie);
// 查询(不太推荐用Neo4jTemplate进行过滤查询,因为需要手动写cypherQuery,需要开发人员了解一下cypherQuery的写法)
Optional<PersonEntity> person;
// 1. 通过id查询
person = neo4jTemplate.findById(12, PersonEntity.class);
System.out.println("id为12号的Person节点:\n"+person);
// 2. 通过属性查询节点,如name 需要手写cypherQuery语句
Map<String,Object> map = new HashMap<>();
map.put("name","新海诚");
// 两种写法都对,看个人喜好 n是一个变量随意取,{}或者where填写query的filter过滤条件
person = neo4jTemplate.findOne("MATCH (n:Person {name: $name}) RETURN n",map, PersonEntity.class);
// person = neo4jTemplate.findOne("MATCH (n:Person) WHERE n.name = $name RETURN n",map, PersonEntity.class);
System.out.println("\n查询名字为新海诚的Person节点:\n"+person);
// 3. 通过属性关系查询节点
map = new HashMap<>();
map.put("roles",Collections.singletonList("宫水三叶"));
// 方法1.使用toExecutableQuery查询
QueryFragmentsAndParameters parameters = new QueryFragmentsAndParameters(
"MATCH (person:Person) -[ relation:ACTED_IN]-> (movie:Movie) \n" +
"WHERE relation.roles = $roles\n" +
"RETURN person",map);
List<PersonEntity> roles = neo4jTemplate.toExecutableQuery(PersonEntity.class, parameters).getResults();
// 方法2.使用findOne查询
// Optional<PersonEntity> roles = neo4jTemplate.findOne(
// "MATCH (person:Person) -[ relation:ACTED_IN]-> (movie:Movie) \n" +
// "WHERE relation.roles = $roles\n" +
// "RETURN person",map,PersonEntity.class);
System.out.println("\n查询角色为“宫水三叶”的演员:\n"+roles);
Long userId = person.get().getId();// 记录当前查询的"新海诚"的节点id
// 更新①---------更新“新海诚”的name为曾用名“新津诚”(这是他的曾用名)
map.put("name","新海诚");
map.put("usedName","新津诚");
QueryFragmentsAndParameters queryFragmentsAndParameters =
new QueryFragmentsAndParameters(
"MATCH (n:Person{name: $name}) SET n.name = $usedName",
map);
neo4jTemplate.toExecutableQuery(
PersonEntity.class,
queryFragmentsAndParameters).getResults();
Optional<PersonEntity> person1 = neo4jTemplate.findById(userId, PersonEntity.class);
System.out.println("\n更新“新海诚”的name为曾用名“新津诚”(这是他的曾用名):\n"+person1);
// 更新②---------更新“新津诚”的name为“新海诚”
person.get().setName("新海诚");
neo4jTemplate.save(person.get());
Optional<PersonEntity> person2 = neo4jTemplate.findById(userId, PersonEntity.class);
System.out.println("\n更新“新津诚”的name为“新海诚”:\n"+person2);
}
使用 repository 对图数据进行 CRUD
创建 Repository
新建 repository 包,创建 PersonRepository 和 MovieRepository
package cn.tmesh.neo4jdemo.repository;
import cn.tmesh.neo4jdemo.entity.MovieEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MovieRepository extends Neo4jRepository<MovieEntity, Long> {
// @Query("MATCH (n:Movie) WHERE id(n) = $0 RETURN n") 这种方法是自己写Query语句进行查询
List<MovieEntity> findMovieEntitiesById(Long id);
MovieEntity findMovieEntityByTitle(String title);
}
package cn.tmesh.neo4jdemo.repository;
import cn.tmesh.neo4jdemo.entity.PersonEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PersonRepository extends Neo4jRepository<PersonEntity, Long> {
PersonEntity findPersonEntityByName(String name);
}
创建节点和关系
@Test
void createNode() {
// 创建节点
MovieEntity movie = new MovieEntity("你的名字","影片讲述了男女高中生在梦中相遇,并寻找彼此的故事。");
Roles roles1 = new Roles(new PersonEntity(1998,"上白石萌音"), Collections.singletonList("宫水三叶"));
Roles roles2 = new Roles(new PersonEntity(1993,"神木隆之介"), Collections.singletonList("立花泷"));
PersonEntity director = new PersonEntity(1973,"新海诚");
// 添加关系
movie.getActorsAndRoles().add(roles1);
movie.getActorsAndRoles().add(roles2);
movie.getDirectors().add(director);
// 存入图数据库持久化
movieRepository.save(movie);
}
查询
根据 Person 的名字查询对应节点
@Test
void selectNode() {
// 查询
PersonEntity person = personRepository.findPersonEntityByName("上白石萌音");
System.out.println("查询名字为“上白石萌音”的PersonEntity:"+person);
MovieEntity movieQueried = movieRepository.findMovieEntityByTitle("你的名字");
System.out.println("查询名字为“你的名字”的MovieEntity:"+movieQueried);
}
更新
@Test
void updateNode() {
PersonEntity person = personRepository.findPersonEntityByName("上白石萌音");
System.out.println("查询名字为“上白石萌音”的PersonEntity:"+person);
// 更新(更新主要是三步:1.获取实体id;2.修改实体属性;3.更新实体)
// 注意:repository的save方法【对应的实体若id一致】则为修改,否则为新建。
Long personId = person.getId();
person.setBorn(1997);
personRepository.save(person);
person = personRepository.findPersonEntityByName("上白石萌音");
System.out.println(personId == person.getId()?"\n更新“上白石萌音”出生日期为1997信息成功!:\n"+person:"更新信息失败!");
}
删除
@Test
void deleteNodeAll() {
// 删除所有节点和关系
movieRepository.deleteAll();
personRepository.deleteAll();
}
完整
/**
* 使用repository操作图数据
*/
@Test
void testByRepository(@Autowired MovieRepository movieRepository, @Autowired PersonRepository personRepository){
// 删除所有节点和关系(删除节点会响应删除关联关系),避免后续创建节点重复影响
movieRepository.deleteAll();
personRepository.deleteAll();
// 创建节点
MovieEntity movie = new MovieEntity("你的名字","影片讲述了男女高中生在梦中相遇,并寻找彼此的故事。");
Roles roles1 = new Roles(new PersonEntity(1998,"上白石萌音"), Collections.singletonList("宫水三叶"));
Roles roles2 = new Roles(new PersonEntity(1993,"神木隆之介"), Collections.singletonList("立花泷"));
PersonEntity director = new PersonEntity(1973,"新海诚");
// 添加关系
movie.getActorsAndRoles().add(roles1);
movie.getActorsAndRoles().add(roles2);
movie.getDirectors().add(director);
// 存入图数据库持久化
movieRepository.save(movie);
// 查询
PersonEntity person = personRepository.findPersonEntityByName("上白石萌音");
System.out.println("\n查询名字为“上白石萌音”的PersonEntity:\n"+person);
MovieEntity movieQueried = movieRepository.findMovieEntityByTitle("你的名字");
System.out.println("\n查询名字为“你的名字”的MovieEntity:\n"+movieQueried);
// 更新(更新主要是三步:1.获取实体id;2.修改实体属性;3.更新实体)
// 注意:repository的save方法【对应的实体若id一致】则为修改,否则为新建。
Long personId = person.getId();
person.setBorn(1997);
personRepository.save(person);
person = personRepository.findPersonEntityByName("上白石萌音");
System.out.println(personId == person.getId()?"\n更新“上白石萌音”出生日期为1997信息成功!:\n"+person:"更新信息失败!");
}

浙公网安备 33010602011771号