Spring Data Neo4j 学习


根据这个 https://github.com/WuYiheng-Og/neo4j_springboot 来实践

注:本文所使用SpringBoot版本为3.5.4,Java17,neo4j-5.25.1
本文将会结合官网的一个 【导演-电影-演员】关系图来进行实现。以新海诚导演的《你的名字》为例。
[Pasted image 20250803165044.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 包,添加实体类:PersonEntityMovieEntity

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 包,创建 PersonRepositoryMovieRepository

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:"更新信息失败!");  
}
posted @ 2025-08-03 17:34  Thousand_Mesh  阅读(66)  评论(0)    收藏  举报