目录
- 依赖
- 配置 Neo4j
- 创建实体类节点
- 节点间的关系
- 使用 Neo4jTemplate 对图数据进行 CRUD
- 创建节点和关系
- 查询节点
- 更新节点信息
- 删除节点
- 完整
- 使用 repository 对图数据进行 CRUD
- 创建 Repository
- 创建节点和关系
- 查询
- 更新
- 删除
- 完整
根据这个 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>:7687authentication: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:"更新信息失败!");
}
