mcpField
S
spring-data-neo4j

spring-data-neo4j

github.com/giuseppe-trisciuoglio数据与数据库3653.7k 次安装7 周前更新
代码仓库

关于 spring-data-neo4j

为 Spring Boot 应用集成 Spring Data Neo4j 提供实践指南,涵盖图实体映射、命令式与响应式仓库、自定义 Cypher 查询和嵌入式数据库测试。

主要功能

  1. 01节点与关系实体映射
  2. 02命令式与响应式仓库
  3. 03使用 @Query 编写自定义 Cypher
  4. 04使用 Neo4j Harness 进行集成测试

适用场景

  1. 01为 Spring Boot 应用接入 Neo4j 图存储
  2. 02通过自定义 Cypher 查询关联节点
  3. 03使用嵌入式数据库测试仓库

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-data-neo4j

安装后在 Claude Code 中输入/spring-data-neo4j确认已加载。

附带文件references/examples.mdreferences/reference.md

详细介绍

Spring Data Neo4j 集成模式

概述

为 Spring Boot 应用提供 Spring Data Neo4j 集成模式。涵盖使用 @Node 和 @Relationship 映射节点实体、配置仓库(命令式和响应式)、使用 @Query 编写自定义 Cypher 查询,以及使用嵌入式 Neo4j 数据库进行集成测试。

适用场景

在以下情况下使用此技能:

  • 在 Spring Boot 中使用图数据库和 Neo4j 集成
  • 使用节点实体、关系和 Cypher 查询
  • 使用 Spring Data Neo4j 仓库(命令式或响应式)
  • 使用嵌入式数据库测试 Neo4j

说明

1. 设置 Spring Data Neo4j

添加依赖:

Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'

在 application.properties 中配置连接:

spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret

配置 Cypher-DSL 方言(推荐):

@Configuration
public class Neo4jConfig {
    @Bean
    Configuration cypherDslConfiguration() {
        return Configuration.newConfig()
            .withDialect(Dialect.NEO4J_5).build();
    }
}

验证检查点:继续之前,使用 cypher-shell 运行 MATCH (n) RETURN count(n),验证连接是否正常。

2. 定义节点实体
  1. 使用 @Node 注解标记实体类
  2. 选择 ID 策略:
    • 将业务键用作 @Id(不可变的自然标识符)
    • 使用生成的 @Id @GeneratedValue(Neo4j 内部 ID)
  3. 使用 @Relationship 注解定义关系
  4. 使用 final 字段保持实体不可变
  5. 使用 @Property 指定自定义属性名称

验证检查点:如果实体保存失败,请检查是否违反约束——重复 ID 会违反唯一性约束。

3. 创建仓库
  1. 扩展仓库接口:
    • 命令式操作使用 Neo4jRepository<Entity, ID>
    • 响应式操作使用 ReactiveNeo4jRepository<Entity, ID>
  2. 简单查询使用查询派生
  3. 复杂 Cypher 查询使用 @Query 注解
  4. 参数使用 $paramName 语法

验证检查点:先使用 findAll() 测试仓库——如果结果为空,请确认 Neo4j 实例正在运行且凭据正确。

4. 测试实现
  1. 使用 @DataNeo4jTest通过测试切片测试仓库
  2. 使用嵌入式数据库和测试夹具设置 Neo4j Harness
  3. 通过 withFixture() Cypher 查询提供测试数据
  4. 在各项测试之间清理测试数据

验证检查点:如果测试因 "Connection refused" 失败,请确保嵌入式 Neo4j 已在 @BeforeAll 中成功启动。

基本实体映射

使用业务键的节点实体
@Node("Movie")
public class MovieEntity {

    @Id
    private final String title;  // Business key as ID

    @Property("tagline")
    private final String description;

    private final Integer year;

    @Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
    private List<Roles> actorsAndRoles = new ArrayList<>();

    @Relationship(type = "DIRECTED", direction = Direction.INCOMING)
    private List<PersonEntity> directors = new ArrayList<>();

    public MovieEntity(String title, String description, Integer year) {
        this.title = title;
        this.description = description;
        this.year = year;
    }
}
使用生成 ID 的节点实体
@Node("Movie")
public class MovieEntity {

    @Id @GeneratedValue
    private Long id;

    private final String title;

    @Property("tagline")
    private final String description;

    public MovieEntity(String title, String description) {
        this.id = null;  // Never set manually
        this.title = title;
        this.description = description;
    }

    // Wither method for immutability with generated IDs
    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;
        }
    }
}

仓库模式

基本仓库接口
@Repository
public interface MovieRepository extends Neo4jRepository<MovieEntity, String> {

    // Query derivation from method name
    MovieEntity findOneByTitle(String title);

    List<MovieEntity> findAllByYear(Integer year);

    List<MovieEntity> findByYearBetween(Integer startYear, Integer endYear);
}
响应式仓库
@Repository
public interface MovieRepository extends ReactiveNeo4jRepository<MovieEntity, String> {

    Mono<MovieEntity> findOneByTitle(String title);

    Flux<MovieEntity> findAllByYear(Integer year);
}

命令式与响应式:

  • 阻塞式命令式操作使用 Neo4jRepository
  • 非阻塞式响应式操作使用 ReactiveNeo4jRepository
  • 不要在同一个应用中混用命令式和响应式
  • 响应式要求数据库端使用 Neo4j 4+

使用 @Query 编写自定义查询

@Repository
public interface AuthorRepository extends Neo4jRepository<Author, Long> {

    @Query("MATCH (b:Book)-[:WRITTEN_BY]->(a:Author) " +
           "WHERE a.name = $name AND b.year > $year " +
           "RETURN b")
    List<Book> findBooksAfterYear(@Param("name") String name,
                                   @Param("year") Integer year);

    @Query("MATCH (b:Book)-[:WRITTEN_BY]->(a:Author) " +
           "WHERE a.name = $name " +
           "RETURN b ORDER BY b.year DESC")
    List<Book> findBooksByAuthorOrderByYearDesc(@Param("name") String name);
}

自定义查询最佳实践:

  • 使用 $parameterName 作为参数占位符
  • 当参数名称与方法参数不同时,使用 @Param 注解
  • MATCH 用于指定节点模式和关系
  • WHERE 用于筛选结果
  • RETURN 用于定义返回内容

测试策略

使用 Neo4j Harness 进行集成测试

测试配置:

@DataNeo4jTest
class BookRepositoryIntegrationTest {

    private static Neo4j embeddedServer;

    @BeforeAll
    static void initializeNeo4j() {
        embeddedServer = Neo4jBuilders.newInProcessBuilder()
            .withDisabledServer()  // No HTTP access needed
            .withFixture(
                "CREATE (b:Book {isbn: '978-0547928210', " +
                "name: 'The Fellowship of the Ring', year: 1954})" +
                "-[:WRITTEN_BY]->(a:Author {id: 1, name: 'J. R. R. Tolkien'}) " +
                "CREATE (b2:Book {isbn: '978-0547928203', " +
                "name: 'The Two Towers', year: 1956})" +
                "-[:WRITTEN_BY]->(a)"
            )
            .build();
    }

    @AfterAll
    static void stopNeo4j() {
        embeddedServer.close();
    }

    @DynamicPropertySource
    static void neo4jProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.neo4j.uri", embeddedServer::boltURI);
        registry.add("spring.neo4j.authentication.username", () -> "neo4j");
        registry.add("spring.neo4j.authentication.password", () -> "null");
    }

    @Autowired
    private BookRepository bookRepository;

    @Test
    void givenBookExists_whenFindOneByTitle_thenBookIsReturned() {
        Book book = bookRepository.findOneByTitle("The Fellowship of the Ring");
        assertThat(book.getIsbn()).isEqualTo("978-0547928210");
    }
}

示例

示例 1:保存和检索实体

输入:

MovieEntity movie = new MovieEntity("The Matrix", "Welcome to the Real World", 1999);
movieRepository.save(movie);

MovieEntity found = movieRepository.findOneByTitle("The Matrix");

输出:

MovieEntity{
    title="The Matrix",
    description="Welcome to the Real World",
    year=1999,
    actorsAndRoles=[],
    directors=[]
}
示例 2:自定义 Cypher 查询

输入:

List<Book> books = authorRepository.findBooksAfterYear("J.R.R. Tolkien", 1950);

输出:

[
    Book{isbn="978-0547928210", name="The Fellowship of the Ring", year=1954},
    Book{isbn="978-0547928203", name="The Two Towers", year=1956},
    Book{isbn="978-0547928227", name="The Return of the King", year=1957}
]
示例 3:关系遍历

输入:

@Query("MATCH (m:Movie)<-[:ACTED_IN]-(a:Person) " +
       "WHERE m.title = $title RETURN a.name as actorName")
List<String> findActorsByMovieTitle(@Param("title") String title);

List<String> actors = movieRepository.findActorsByMovieTitle("The Matrix");

输出:

["Keanu Reeves", "Laurence Fishburne", "Carrie-Anne Moss", "Hugo Weaving"]

从基础示例逐步深入高级示例,涵盖完整的电影数据库、社交网络模式、电商产品目录、自定义查询和响应式操作。

请参阅 示例,了解完整的代码示例。

最佳实践

实体设计
  • 使用带有 final 字段的不可变实体
  • 选择业务键(@Id)或生成的 ID(@Id @GeneratedValue)
  • 让实体专注于图结构,而非业务逻辑
  • 使用恰当的关系方向(INCOMING、OUTGOING、UNDIRECTED)
仓库设计
  • 命令式应用扩展 Neo4jRepository,响应式应用扩展 ReactiveNeo4jRepository
  • 简单查询使用查询派生
  • 复杂图模式编写自定义 @Query
  • 不要在同一个应用中混用命令式和响应式
配置
  • 始终显式配置 Cypher-DSL 方言
  • 使用特定于环境的属性设置凭据
  • 不要在源代码中硬编码凭据
  • 根据负载配置连接池
测试
  • 使用 Neo4j Harness 进行集成测试
  • 通过 withFixture() Cypher 查询提供测试数据
  • 使用 @DataNeo4jTest 进行测试切片
  • 测试成功场景和边缘场景
架构
  • 仅使用构造函数注入
  • 将领域实体与 DTO 分开
  • 遵循基于功能的包结构
  • 保持领域层与框架无关
安全性
  • 使用 Spring Boot 属性覆盖凭据
  • 配置适当的身份验证和授权
  • 在服务层验证输入参数
  • 使用参数化查询防止 Cypher 注入

限制和警告

  • 不要在同一个应用中混用命令式和响应式仓库。
  • Neo4j 写操作需要事务;确保正确配置 @Transactional。
  • 谨慎进行深层关系遍历,因为这可能导致性能问题。
  • 应对大型结果集进行分页,以避免内存问题。
  • Cypher 查询区分大小写;确保属性名称的大小写一致。
  • 不可变实体需要正确的 wither 方法来处理生成的 ID。
  • Spring Data Neo4j 中的关系默认不会延迟加载;对于大型图,建议使用投影。
  • Neo4j Java 驱动程序与响应式流不兼容;响应式操作请使用响应式驱动程序。

故障排除

问题原因解决方案
在 localhost:7687 上出现 Connection refusedNeo4j 服务未运行启动 Neo4j,或在测试中使用嵌入式 Neo4j
Authentication failed凭据错误检查 spring.neo4j.authentication.username/password
实体未保存 / MATCH 未返回任何结果事务未提交添加 @Transactional 或检查自动提交设置
保存时出现 ConstraintViolationException@Id 值重复确保 ID 唯一,或使用 @GeneratedValue
结果中缺少关系@Relationship 方向错误检查 Direction.INCOMING/OUTGOING/UNDIRECTED
@Query 返回错误数据Cypher 参数语法错误使用 $paramName,而非 $ {paramName}
@DataNeo4jTest 测试失败嵌入式 Neo4j 未启动确保 @BeforeAll 在测试前启动 Neo4j

参考资料

有关完整 API 参考、Cypher 查询模式和配置选项的详细文档:

外部资源

评论

支持 Markdown