spring-data-neo4j

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Spring Data Neo4j Integration Patterns

Spring Data Neo4j集成模式

When to Use This Skill

何时使用本技能

To use this skill when you need to:
  • Set up Spring Data Neo4j in a Spring Boot application
  • Create and map graph node entities and relationships
  • Implement Neo4j repositories with custom queries
  • Write Cypher queries using @Query annotations
  • Configure Neo4j connections and dialects
  • Test Neo4j repositories with embedded databases
  • Work with both imperative and reactive Neo4j operations
  • Map complex graph relationships with bidirectional or unidirectional directions
  • Use Neo4j's internal ID generation or custom business keys
在以下场景中可使用本技能:
  • 在Spring Boot应用中搭建Spring Data Neo4j
  • 创建并映射图节点实体与关系
  • 实现带有自定义查询的Neo4j仓库
  • 使用@Query注解编写Cypher查询
  • 配置Neo4j连接与方言
  • 使用嵌入式数据库测试Neo4j仓库
  • 处理命令式与响应式Neo4j操作
  • 映射双向或单向的复杂图关系
  • 使用Neo4j的内部ID生成机制或自定义业务键

Overview

概述

Spring Data Neo4j provides three levels of abstraction for Neo4j integration:
  • Neo4j Client: Low-level abstraction for direct database access
  • Neo4j Template: Medium-level template-based operations
  • Neo4j Repositories: High-level repository pattern with query derivation
Key features include reactive and imperative operation modes, immutable entity mapping, custom query support via @Query annotation, Spring's Conversion Service integration, and full support for graph relationships and traversals.
Spring Data Neo4j为Neo4j集成提供了三个层级的抽象:
  • Neo4j Client:用于直接数据库访问的底层抽象
  • Neo4j Template:基于模板的中层操作抽象
  • Neo4j Repositories:带有查询推导功能的高层仓库模式
核心特性包括响应式与命令式操作模式、不可变实体映射、通过@Query注解支持自定义查询、与Spring转换服务集成,以及对图关系和遍历的全面支持。

Quick Setup

快速搭建

Dependencies

依赖

Maven:
xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
Gradle:
groovy
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
}
Maven:
xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
Gradle:
groovy
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
}

Configuration

配置

application.properties:
properties
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret
Configure Neo4j Cypher-DSL Dialect:
java
@Configuration
public class Neo4jConfig {

    @Bean
    Configuration cypherDslConfiguration() {
        return Configuration.newConfig()
            .withDialect(Dialect.NEO4J_5).build();
    }
}
application.properties:
properties
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret
配置Neo4j Cypher-DSL方言:
java
@Configuration
public class Neo4jConfig {

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

Basic Entity Mapping

基础实体映射

Node Entity with Business Key

带有业务键的节点实体

java
@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;
    }
}
java
@Node("Movie")
public class MovieEntity {

    @Id
    private final String title;  // 作为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;
    }
}

Node Entity with Generated ID

带有生成ID的节点实体

java
@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;
        }
    }
}
java
@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;  // 切勿手动设置
        this.title = title;
        this.description = description;
    }

    // 用于带生成ID的不可变对象的Wither方法
    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 Patterns

仓库模式

Basic Repository Interface

基础仓库接口

java
@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);
}
java
@Repository
public interface MovieRepository extends Neo4jRepository<MovieEntity, String> {

    // 从方法名推导查询
    MovieEntity findOneByTitle(String title);

    List<MovieEntity> findAllByYear(Integer year);

    List<MovieEntity> findByYearBetween(Integer startYear, Integer endYear);
}

Reactive Repository

响应式仓库

java
@Repository
public interface MovieRepository extends ReactiveNeo4jRepository<MovieEntity, String> {

    Mono<MovieEntity> findOneByTitle(String title);

    Flux<MovieEntity> findAllByYear(Integer year);
}
Imperative vs Reactive:
  • Use
    Neo4jRepository
    for blocking, imperative operations
  • Use
    ReactiveNeo4jRepository
    for non-blocking, reactive operations
  • Do not mix imperative and reactive in the same application
  • Reactive requires Neo4j 4+ on the database side
java
@Repository
public interface MovieRepository extends ReactiveNeo4jRepository<MovieEntity, String> {

    Mono<MovieEntity> findOneByTitle(String title);

    Flux<MovieEntity> findAllByYear(Integer year);
}
命令式与响应式对比:
  • 对于阻塞式命令式操作,使用
    Neo4jRepository
  • 对于非阻塞式响应式操作,使用
    ReactiveNeo4jRepository
  • 请勿在同一应用中混合使用命令式与响应式操作
  • 响应式操作要求数据库端使用Neo4j 4及以上版本

Custom Queries with @Query

使用@Query实现自定义查询

java
@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);
}
Custom Query Best Practices:
  • Use
    $parameterName
    for parameter placeholders
  • Use
    @Param
    annotation when parameter name differs from method parameter
  • MATCH specifies node patterns and relationships
  • WHERE filters results
  • RETURN defines what to return
java
@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用于定义返回内容

Testing Strategies

测试策略

Neo4j Harness for Integration Testing

用于集成测试的Neo4j Harness

Test Configuration:
java
@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");
    }
}
测试配置:
java
@DataNeo4jTest
class BookRepositoryIntegrationTest {

    private static Neo4j embeddedServer;

    @BeforeAll
    static void initializeNeo4j() {
        embeddedServer = Neo4jBuilders.newInProcessBuilder()
            .withDisabledServer()  // 无需HTTP访问
            .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");
    }
}

Examples

示例

Progress from basic to advanced examples covering complete movie database, social network patterns, e-commerce product catalogs, custom queries, and reactive operations.
See examples for comprehensive code examples.
从基础到进阶的示例涵盖完整的电影数据库、社交网络模式、电商产品目录、自定义查询和响应式操作。
查看示例获取完整代码示例。

Best Practices

最佳实践

Entity Design

实体设计

  • Use immutable entities with final fields
  • Choose between business keys (@Id) or generated IDs (@Id @GeneratedValue)
  • Keep entities focused on graph structure, not business logic
  • Use proper relationship directions (INCOMING, OUTGOING, UNDIRECTED)
  • 使用带有final字段的不可变实体
  • 在业务键(@Id)与生成ID(@Id @GeneratedValue)之间做出选择
  • 实体应专注于图结构,而非业务逻辑
  • 使用正确的关系方向(INCOMING、OUTGOING、UNDIRECTED)

Repository Design

仓库设计

  • Extend
    Neo4jRepository
    for imperative or
    ReactiveNeo4jRepository
    for reactive
  • Use query derivation for simple queries
  • Write custom @Query for complex graph patterns
  • Don't mix imperative and reactive in same application
  • 对于命令式操作继承
    Neo4jRepository
    ,对于响应式操作继承
    ReactiveNeo4jRepository
  • 简单查询使用查询推导
  • 复杂图模式使用自定义@Query
  • 请勿在同一应用中混合命令式与响应式操作

Configuration

配置

  • Always configure Cypher-DSL dialect explicitly
  • Use environment-specific properties for credentials
  • Never hardcode credentials in source code
  • Configure connection pooling based on load
  • 始终显式配置Cypher-DSL方言
  • 使用环境特定的属性存储凭据
  • 切勿在源代码中硬编码凭据
  • 根据负载配置连接池

Testing

测试

  • Use Neo4j Harness for integration tests
  • Provide test data via
    withFixture()
    Cypher queries
  • Use
    @DataNeo4jTest
    for test slicing
  • Test both successful and edge-case scenarios
  • 使用Neo4j Harness进行集成测试
  • 通过
    withFixture()
    Cypher查询提供测试数据
  • 使用
    @DataNeo4jTest
    进行测试切片
  • 测试成功场景与边缘场景

Architecture

架构

  • Use constructor injection exclusively
  • Separate domain entities from DTOs
  • Follow feature-based package structure
  • Keep domain layer framework-agnostic
  • 仅使用构造函数注入
  • 分离领域实体与DTO
  • 遵循基于功能的包结构
  • 保持领域层与框架无关

Security

安全

  • Use Spring Boot property overrides for credentials
  • Configure proper authentication and authorization
  • Validate input parameters in service layer
  • Use parameterized queries to prevent Cypher injection
  • 使用Spring Boot属性覆盖来管理凭据
  • 配置适当的认证与授权
  • 在服务层验证输入参数
  • 使用参数化查询防止Cypher注入

References

参考资料

For detailed documentation including complete API reference, Cypher query patterns, and configuration options:
  • Annotations Reference
  • Cypher Query Language
  • Configuration Properties
  • Repository Methods
  • Projections and DTOs
  • Transaction Management
  • Performance Tuning
如需包含完整API参考、Cypher查询模式和配置选项的详细文档:
  • 注解参考
  • Cypher查询语言
  • 配置属性
  • 仓库方法
  • 投影与DTO
  • 事务管理
  • 性能调优

External Resources

外部资源