neo4j-spring-data-skill

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Neo4j Spring Data Skill

Neo4j Spring Data 技能指南

When to Use

适用场景

  • Configuring Spring Boot with Neo4j (
    spring-boot-starter-data-neo4j
    )
  • Writing
    @Node
    entity classes and
    @Relationship
    /
    @RelationshipProperties
    mappings
  • Defining
    Neo4jRepository
    or
    ReactiveNeo4jRepository
    interfaces
  • Writing
    @Query
    annotations with Cypher on repository methods
  • Using Spring projections (interface-based, DTO, dynamic) with Neo4j
  • Configuring
    application.yml
    for Neo4j connection
  • Custom queries via
    Neo4jClient
    or
    Neo4jTemplate
  • Spring AI
    Neo4jVectorStore
    for vector search in Spring apps
  • Transaction management, auditing, optimistic locking
  • 配置集成Neo4j的Spring Boot应用(使用
    spring-boot-starter-data-neo4j
    依赖)
  • 编写
    @Node
    实体类及
    @Relationship
    /
    @RelationshipProperties
    映射
  • 定义
    Neo4jRepository
    ReactiveNeo4jRepository
    接口
  • 在仓库方法上使用带Cypher语句的
    @Query
    注解
  • 将Spring投影(基于接口、DTO、动态投影)与Neo4j结合使用
  • 配置
    application.yml
    中的Neo4j连接信息
  • 通过
    Neo4jClient
    Neo4jTemplate
    执行自定义查询
  • 在Spring应用中使用Spring AI
    Neo4jVectorStore
    实现向量搜索
  • 事务管理、审计、乐观锁机制

When NOT to Use

不适用场景

  • Raw Java driver without Spring
    neo4j-driver-java-skill
  • Cypher query authoring
    neo4j-cypher-skill
  • Driver version upgrades
    neo4j-migration-skill
  • GDS algorithms
    neo4j-gds-skill

  • 未集成Spring的原生Java驱动 → 使用
    neo4j-driver-java-skill
  • Cypher查询编写 → 使用
    neo4j-cypher-skill
  • 驱动版本升级 → 使用
    neo4j-migration-skill
  • GDS算法 → 使用
    neo4j-gds-skill

Version Matrix

版本对应矩阵

SDNSpring BootSpring FrameworkJavaNeo4j
8.0.x3.3.x / 3.4.x6.2.x17+5.15+
8.1.x3.4.x+7.0.x17+5.15+
7.5.x3.2.x6.1.x17+4.4+
Use
spring-boot-starter-data-neo4j
— it pulls SDN + driver. No explicit SDN version needed when using Spring Boot BOM.

SDNSpring BootSpring FrameworkJavaNeo4j
8.0.x3.3.x / 3.4.x6.2.x17+5.15+
8.1.x3.4.x+7.0.x17+5.15+
7.5.x3.2.x6.1.x17+4.4+
推荐使用
spring-boot-starter-data-neo4j
依赖——它会自动引入SDN及对应驱动。使用Spring Boot BOM时无需显式指定SDN版本。

Setup

环境搭建

Maven

Maven依赖

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

Gradle

Gradle依赖

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

Reactive stack (add alongside above)

响应式栈(需搭配上述依赖)

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Configuration

配置示例

application.yml — imperative (standard)

application.yml — 命令式(标准)

yaml
spring:
  neo4j:
    uri: ${NEO4J_URI:bolt://localhost:7687}
    authentication:
      username: ${NEO4J_USERNAME:neo4j}
      password: ${NEO4J_PASSWORD}
  data:
    neo4j:
      database: ${NEO4J_DATABASE:neo4j}
yaml
spring:
  neo4j:
    uri: ${NEO4J_URI:bolt://localhost:7687}
    authentication:
      username: ${NEO4J_USERNAME:neo4j}
      password: ${NEO4J_PASSWORD}
  data:
    neo4j:
      database: ${NEO4J_DATABASE:neo4j}

application.yml — Aura (TLS required)

application.yml — Aura环境(需TLS)

yaml
spring:
  neo4j:
    uri: ${NEO4J_URI}            # neo4j+s://xxxx.databases.neo4j.io
    authentication:
      username: ${NEO4J_USERNAME:neo4j}
      password: ${NEO4J_PASSWORD}
  data:
    neo4j:
      database: ${NEO4J_DATABASE:neo4j}
Credentials: store in
.env
; never hardcode. Verify
.env
is in
.gitignore
.

yaml
spring:
  neo4j:
    uri: ${NEO4J_URI}            # 格式为neo4j+s://xxxx.databases.neo4j.io
    authentication:
      username: ${NEO4J_USERNAME:neo4j}
      password: ${NEO4J_PASSWORD}
  data:
    neo4j:
      database: ${NEO4J_DATABASE:neo4j}
凭证管理:将凭证存储在
.env
文件中;切勿硬编码。确保
.env
已加入
.gitignore

Entity Mapping

实体映射示例

java
import org.springframework.data.neo4j.core.schema.*;

// Internal generated ID (default for most cases)
@Node("Person")
public class PersonEntity {
    @Id @GeneratedValue private Long id;         // element ID (Long)
    private String name;
    @Property("birth_year") private Integer birthYear;  // custom property name
    @Relationship(type = "KNOWS", direction = Relationship.Direction.OUTGOING)
    private List<PersonEntity> friends = new ArrayList<>();
}

// UUID business key
@Node("Product")
public class ProductEntity {
    @Id @GeneratedValue(generatorClass = GeneratedValue.UUIDStringGenerator.class)
    private String id;
    @Version private Long version;               // optimistic locking; required with business key
}

// User-assigned key (caller sets value; no @GeneratedValue)
@Node("Country")
public class CountryEntity {
    @Id private String isoCode;
    private String name;
}

// Multiple static labels
@Node(primaryLabel = "Vehicle", labels = {"Car", "Auditable"})
public class CarEntity { ... }

// Runtime labels
@Node("Content")
public class ContentEntity {
    @Id @GeneratedValue private Long id;
    @DynamicLabels private Set<String> tags = new HashSet<>();  // labels added at runtime
}

java
import org.springframework.data.neo4j.core.schema.*;

// 内部生成ID(大多数场景下的默认选项)
@Node("Person")
public class PersonEntity {
    @Id @GeneratedValue private Long id;         // 元素ID(Long类型)
    private String name;
    @Property("birth_year") private Integer birthYear;  // 自定义属性名称
    @Relationship(type = "KNOWS", direction = Relationship.Direction.OUTGOING)
    private List<PersonEntity> friends = new ArrayList<>();
}

// UUID业务主键
@Node("Product")
public class ProductEntity {
    @Id @GeneratedValue(generatorClass = GeneratedValue.UUIDStringGenerator.class)
    private String id;
    @Version private Long version;               // 乐观锁;使用业务主键时必填
}

// 用户指定主键(调用方设置值;无需@GeneratedValue)
@Node("Country")
public class CountryEntity {
    @Id private String isoCode;
    private String name;
}

// 多静态标签
@Node(primaryLabel = "Vehicle", labels = {"Car", "Auditable"})
public class CarEntity { ... }

// 运行时动态标签
@Node("Content")
public class ContentEntity {
    @Id @GeneratedValue private Long id;
    @DynamicLabels private Set<String> tags = new HashSet<>();  // 运行时添加的标签
}

Relationship Properties

关系属性示例

Use
@RelationshipProperties
when the relationship itself carries data.
java
@RelationshipProperties
public class RolesRelationship {

    @RelationshipId                     // internal relationship ID; required
    private Long id;

    private List<String> roles;

    @TargetNode                         // marks the other end of the relationship
    private PersonEntity person;
}
java
@Node("Movie")
public class MovieEntity {

    @Id @GeneratedValue
    private Long id;

    private String title;

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

当关系本身携带数据时,使用
@RelationshipProperties
java
@RelationshipProperties
public class RolesRelationship {

    @RelationshipId                     // 内部关系ID;必填
    private Long id;

    private List<String> roles;

    @TargetNode                         // 标记关系的另一端节点
    private PersonEntity person;
}
java
@Node("Movie")
public class MovieEntity {

    @Id @GeneratedValue
    private Long id;

    private String title;

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

Repository Interfaces

仓库接口示例

Basic CRUD

基础CRUD操作

java
import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface PersonRepository extends Neo4jRepository<PersonEntity, Long> {

    Optional<PersonEntity> findByName(String name);

    List<PersonEntity> findByBirthYearBetween(int from, int to);

    List<PersonEntity> findByNameContainingIgnoreCase(String fragment);

    long countByBirthYearGreaterThan(int year);

    void deleteByName(String name);
}
java
import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface PersonRepository extends Neo4jRepository<PersonEntity, Long> {

    Optional<PersonEntity> findByName(String name);

    List<PersonEntity> findByBirthYearBetween(int from, int to);

    List<PersonEntity> findByNameContainingIgnoreCase(String fragment);

    long countByBirthYearGreaterThan(int year);

    void deleteByName(String name);
}

@Query — custom Cypher

@Query — 自定义Cypher语句

java
// CORRECT: $param bound parameter
@Query("MATCH (p:Person {name: $name})-[:KNOWS]->(f:Person) RETURN f")
List<PersonEntity> findFriendsOf(String name);

// With pagination
@Query(value = "MATCH (p:Person) RETURN p ORDER BY p.name",
       countQuery = "MATCH (p:Person) RETURN count(p)")
Page<PersonEntity> findAllPaged(Pageable pageable);

// Return relationship-rich entity; map target via @Node return
@Query("MATCH (m:Movie)<-[r:ACTED_IN]-(p:Person {name: $name}) RETURN m, collect(r), collect(p)")
List<MovieEntity> findMoviesActedInBy(String name);
Security rule: NEVER string-concatenate user input into Cypher. Always use
$paramName
.
java
// 正确写法:使用$param绑定参数
@Query("MATCH (p:Person {name: $name})-[:KNOWS]->(f:Person) RETURN f")
List<PersonEntity> findFriendsOf(String name);

// 分页查询
@Query(value = "MATCH (p:Person) RETURN p ORDER BY p.name",
       countQuery = "MATCH (p:Person) RETURN count(p)")
Page<PersonEntity> findAllPaged(Pageable pageable);

// 返回包含关系的实体;通过@Node返回映射目标节点
@Query("MATCH (m:Movie)<-[r:ACTED_IN]-(p:Person {name: $name}) RETURN m, collect(r), collect(p)")
List<MovieEntity> findMoviesActedInBy(String name);
安全规则:切勿将用户输入字符串拼接进Cypher语句。始终使用
$paramName
参数绑定。

Pagination and sorting

分页与排序

java
Page<PersonEntity> findByBirthYearGreaterThan(int year, Pageable pageable);

List<PersonEntity> findTop10ByOrderByNameAsc();

List<PersonEntity> findByName(String name, Sort sort);
Usage:
java
Pageable page = PageRequest.of(0, 20, Sort.by("name").ascending());
Page<PersonEntity> result = repo.findByBirthYearGreaterThan(1980, page);

java
Page<PersonEntity> findByBirthYearGreaterThan(int year, Pageable pageable);

List<PersonEntity> findTop10ByOrderByNameAsc();

List<PersonEntity> findByName(String name, Sort sort);
使用示例:
java
Pageable page = PageRequest.of(0, 20, Sort.by("name").ascending());
Page<PersonEntity> result = repo.findByBirthYearGreaterThan(1980, page);

Projections

投影示例

Interface projection (closed — query-optimizable)

接口投影(封闭型——支持查询优化)

java
public interface PersonSummary {
    String getName();
    Integer getBirthYear();
}

List<PersonSummary> findByBirthYearLessThan(int year);
java
public interface PersonSummary {
    String getName();
    Integer getBirthYear();
}

List<PersonSummary> findByBirthYearLessThan(int year);

DTO projection (record — preferred in Java 17+)

DTO投影(记录类型——Java 17+推荐使用)

java
public record PersonDto(String name, Integer birthYear) {}

List<PersonDto> findByName(String name);
java
public record PersonDto(String name, Integer birthYear) {}

List<PersonDto> findByName(String name);

Dynamic projection

动态投影

java
<T> List<T> findByName(String name, Class<T> type);

// Usage
repo.findByName("Alice", PersonSummary.class);
repo.findByName("Alice", PersonEntity.class);
java
<T> List<T> findByName(String name, Class<T> type);

// 使用示例
repo.findByName("Alice", PersonSummary.class);
repo.findByName("Alice", PersonEntity.class);

Open projection — SpEL (disables query optimization)

开放投影——SpEL(会禁用查询优化)

java
public interface FullName {
    @Value("#{target.name + ' (' + target.birthYear + ')'}") String getDisplayName();
}

java
public interface FullName {
    @Value("#{target.name + ' (' + target.birthYear + ')'}") String getDisplayName();
}

Reactive Repository

响应式仓库

java
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface ReactivePersonRepository extends ReactiveNeo4jRepository<PersonEntity, Long> {

    Mono<PersonEntity> findByName(String name);

    @Query("MATCH (p:Person {name: $name})-[:KNOWS]->(f) RETURN f")
    Flux<PersonEntity> findFriendsOf(String name);
}
Do NOT mix imperative and reactive database access in the same application context.

java
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface ReactivePersonRepository extends ReactiveNeo4jRepository<PersonEntity, Long> {

    Mono<PersonEntity> findByName(String name);

    @Query("MATCH (p:Person {name: $name})-[:KNOWS]->(f) RETURN f")
    Flux<PersonEntity> findFriendsOf(String name);
}
请勿在同一应用上下文中混合使用命令式与响应式数据库访问方式。

Custom Repository Implementation

自定义仓库实现

Fragment pattern — use when
@Query
is not enough.
java
// 1. Fragment interface
public interface PersonRepositoryCustom {
    List<PersonEntity> findByComplexCriteria(String criteria);
}

// 2. Impl — must end with "Impl"
public class PersonRepositoryCustomImpl implements PersonRepositoryCustom {
    private final Neo4jClient neo4jClient;
    PersonRepositoryCustomImpl(Neo4jClient c) { this.neo4jClient = c; }

    @Override
    public List<PersonEntity> findByComplexCriteria(String c) {
        return new ArrayList<>(neo4jClient
            .query("MATCH (p:Person) WHERE p.name CONTAINS $c RETURN p").bind(c).to("c")
            .fetchAs(PersonEntity.class)
            .mappedBy((t, r) -> { var e = new PersonEntity(); e.setName(r.get("p").asNode().get("name").asString()); return e; })
            .all());
    }
}

// 3. Compose
public interface PersonRepository extends Neo4jRepository<PersonEntity, Long>, PersonRepositoryCustom {}

片段模式——当
@Query
无法满足需求时使用。
java
// 1. 片段接口
public interface PersonRepositoryCustom {
    List<PersonEntity> findByComplexCriteria(String criteria);
}

// 2. 实现类——名称必须以"Impl"结尾
public class PersonRepositoryCustomImpl implements PersonRepositoryCustom {
    private final Neo4jClient neo4jClient;
    PersonRepositoryCustomImpl(Neo4jClient c) { this.neo4jClient = c; }

    @Override
    public List<PersonEntity> findByComplexCriteria(String c) {
        return new ArrayList<>(neo4jClient
            .query("MATCH (p:Person) WHERE p.name CONTAINS $c RETURN p").bind(c).to("c")
            .fetchAs(PersonEntity.class)
            .mappedBy((t, r) -> { var e = new PersonEntity(); e.setName(r.get("p").asNode().get("name").asString()); return e; })
            .all());
    }
}

// 3. 组合仓库接口
public interface PersonRepository extends Neo4jRepository<PersonEntity, Long>, PersonRepositoryCustom {}

Neo4jClient — Low-Level Queries

Neo4jClient — 底层查询

Use when
@Query
is insufficient or you need full control over Cypher execution.
java
// Bind params + fetch single scalar
neo4jClient.query("MATCH (p:Person {name: $name}) RETURN count(*) AS cnt")
    .bind("Alice").to("name")
    .fetchAs(Long.class)
    .mappedBy((t, r) -> r.get("cnt").asLong())
    .one();

// Bind + run write (no result)
neo4jClient.query("MERGE (p:Person {name: $name})")
    .bind(personName).to("name")
    .run();

// Custom object mapping
neo4jClient.query("MATCH (p:Person)-[:DIRECTED]->(m:Movie) WHERE p.name=$n RETURN p, collect(m) AS movies")
    .bind("Lilly Wachowski").to("n")
    .fetchAs(Director.class)
    .mappedBy((typeSystem, record) -> new Director(
        record.get("p").asNode().get("name").asString(),
        record.get("movies").asList(v -> new Movie(v.get("title").asString()))
    )).one();
Full API: references/neo4j-client.md

@Query
不足以满足需求或需要完全控制Cypher执行逻辑时使用。
java
// 绑定参数并获取单个标量结果
neo4jClient.query("MATCH (p:Person {name: $name}) RETURN count(*) AS cnt")
    .bind("Alice").to("name")
    .fetchAs(Long.class)
    .mappedBy((t, r) -> r.get("cnt").asLong())
    .one();

// 绑定参数并执行写入操作(无返回结果)
neo4jClient.query("MERGE (p:Person {name: $name})")
    .bind(personName).to("name")
    .run();

// 自定义对象映射
neo4jClient.query("MATCH (p:Person)-[:DIRECTED]->(m:Movie) WHERE p.name=$n RETURN p, collect(m) AS movies")
    .bind("Lilly Wachowski").to("n")
    .fetchAs(Director.class)
    .mappedBy((typeSystem, record) -> new Director(
        record.get("p").asNode().get("name").asString(),
        record.get("movies").asList(v -> new Movie(v.get("title").asString()))
    )).one();
完整API文档:references/neo4j-client.md

Transaction Management

事务管理

java
@Service
@Transactional                      // class-level: all methods transactional
public class PersonService {
    @Transactional(readOnly = true) // read-only hint
    public Optional<PersonEntity> findByName(String name) { ... }

    @Transactional                  // explicit write
    public PersonEntity save(PersonEntity p) { return repository.save(p); }
}
Neo4jTransactionManager
auto-configured. Do NOT mix with JPA
PlatformTransactionManager
without explicit qualifier. Use
@Transactional
on concrete class, not interface.

java
@Service
@Transactional                      // 类级别:所有方法均为事务性
public class PersonService {
    @Transactional(readOnly = true) // 只读提示
    public Optional<PersonEntity> findByName(String name) { ... }

    @Transactional                  // 显式写入事务
    public PersonEntity save(PersonEntity p) { return repository.save(p); }
}
Neo4jTransactionManager
会自动配置。请勿在未指定限定符的情况下与JPA的
PlatformTransactionManager
混合使用。请在具体类上使用
@Transactional
,而非接口。

Spring AI — Neo4jVectorStore

Spring AI — Neo4jVectorStore

Dependency

依赖

xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-neo4j</artifactId>
</dependency>
xml
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-neo4j</artifactId>
</dependency>

application.yml

application.yml配置

yaml
spring:
  ai:
    vectorstore:
      neo4j:
        initialize-schema: true         # creates vector index on first run
        index-name: my-index
        embedding-dimension: 1536       # must match your embedding model
        distance-type: cosine           # cosine (default) or euclidean
        label: Document                 # node label for stored chunks
        embedding-property: embedding   # property for the vector
Requires Neo4j 5.15+. Reuses
spring.neo4j.*
connection config.
yaml
spring:
  ai:
    vectorstore:
      neo4j:
        initialize-schema: true         # 首次运行时创建向量索引
        index-name: my-index
        embedding-dimension: 1536       # 必须与你的嵌入模型维度匹配
        distance-type: cosine           # cosine(默认)或euclidean
        label: Document                 # 存储分片的节点标签
        embedding-property: embedding   # 存储向量的属性名
要求Neo4j版本5.15+。复用
spring.neo4j.*
的连接配置。

Usage

使用示例

java
@Autowired VectorStore vectorStore;

// Store
vectorStore.add(List.of(new Document("text", Map.of("author", "alice"))));

// Similarity search
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.builder().query("spring neo4j").topK(5).similarityThreshold(0.75).build()
);

// With metadata filter
vectorStore.similaritySearch(
    SearchRequest.builder().query("spring neo4j").topK(5)
        .filterExpression("author == 'alice'").build()
);

java
@Autowired VectorStore vectorStore;

// 存储文档
vectorStore.add(List.of(new Document("text", Map.of("author", "alice"))));

// 相似度搜索
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.builder().query("spring neo4j").topK(5).similarityThreshold(0.75).build()
);

// 带元数据过滤的相似度搜索
vectorStore.similaritySearch(
    SearchRequest.builder().query("spring neo4j").topK(5)
        .filterExpression("author == 'alice'").build()
);

Common Errors

常见错误排查

ErrorCauseFix
MappingException: Could not find entity
Entity not scannedCheck
@EnableNeo4jRepositories
base package
Relationships null after loadDefault depth may skip deep relsUse
@Query
with
RETURN m, collect(r), collect(p)
N+1 queriesPer-entity relationship fetchRewrite with single
@Query
; use projections
OptimisticLockingFailureException
Stale
@Version
on concurrent write
Retry in service layer
IllegalStateException: Cannot mix reactive/imperative
Both repo types in same contextPick one stack
Projection null fieldsGetter name mismatchMatch getter to property name; check
@Property
alias
@Query
empty with rels
Missing
collect(r), collect(p)
Return root node + rels + related nodes together
Cannot delete node, node has relationships
deleteById
without detach
Use
@Query
with
DETACH DELETE
Transaction not rolling back
@Transactional
on interface
Apply on concrete service class

错误信息原因修复方案
MappingException: Could not find entity
实体未被扫描到检查
@EnableNeo4jRepositories
的基础包配置
加载后关系字段为null默认深度可能跳过深层关系使用包含
RETURN m, collect(r), collect(p)
@Query
N+1查询问题逐实体获取关系重写为单条
@Query
;使用投影
OptimisticLockingFailureException
并发写入时
@Version
版本过期
在服务层添加重试逻辑
IllegalStateException: Cannot mix reactive/imperative
同一上下文中同时存在两种类型的仓库选择其中一种技术栈
投影字段为nullGetter名称不匹配确保Getter与属性名称一致;检查
@Property
别名
带关系的
@Query
返回空结果
缺少
collect(r), collect(p)
同时返回根节点、关系及关联节点
Cannot delete node, node has relationships
使用
deleteById
未解除关系
使用带
DETACH DELETE
@Query
事务未回滚
@Transactional
标注在接口上
将注解应用于具体服务类

Relationship Loading — Key Rule

关系加载核心规则

SDN loads related entities eagerly up to a configured depth (default: 1 hop). For deeper graphs:
java
// Explicit @Query to control what gets loaded
@Query("""
    MATCH (m:Movie)<-[r:ACTED_IN]-(p:Person)
    WHERE m.title = $title
    RETURN m, collect(r), collect(p)
    """)
Optional<MovieEntity> findByTitleWithCast(String title);
collect(r), collect(p)
in RETURN is required for SDN to map
@RelationshipProperties
correctly.

SDN会按配置深度(默认:1跳)预加载关联实体。对于更深层次的图结构:
java
// 使用显式@Query控制加载内容
@Query("""
    MATCH (m:Movie)<-[r:ACTED_IN]-(p:Person)
    WHERE m.title = $title
    RETURN m, collect(r), collect(p)
    """)
Optional<MovieEntity> findByTitleWithCast(String title);
返回语句中的
collect(r), collect(p)
是SDN正确映射
@RelationshipProperties
的必要条件。

References

参考文档

Checklist

检查清单

  • @Node
    uses explicit label string, not default class name
  • @Id @GeneratedValue
    (or
    @Id
    +
    @Version
    for business key with optimistic lock)
  • @RelationshipProperties
    class has
    @RelationshipId
    and
    @TargetNode
  • @Relationship
    direction is explicit (OUTGOING / INCOMING)
  • @Query
    Cypher uses
    $paramName
    — no string concatenation
  • Relationship-rich
    @Query
    returns
    collect(r), collect(p)
    alongside root node
  • Database name set in
    application.yml
    (avoids default DB ambiguity)
  • Unique constraint exists in DB for any business key used in repository lookups
  • @Transactional
    on concrete service class (not interface)
  • No imperative + reactive mix in same application context
  • Credentials in env vars;
    .env
    in
    .gitignore
  • spring.ai.vectorstore.neo4j.initialize-schema: true
    for first run (Spring AI)
  • @Node
    使用显式标签字符串,而非默认类名
  • 使用
    @Id @GeneratedValue
    (或
    @Id
    +
    @Version
    实现带乐观锁的业务主键)
  • @RelationshipProperties
    类包含
    @RelationshipId
    @TargetNode
  • @Relationship
    显式指定方向(OUTGOING / INCOMING)
  • @Query
    中的Cypher使用
    $paramName
    参数绑定——无字符串拼接
  • 包含关系的
    @Query
    返回
    collect(r), collect(p)
    及根节点
  • application.yml
    中设置数据库名称(避免默认数据库歧义)
  • 仓库查询使用的业务主键在数据库中存在唯一约束
  • @Transactional
    标注在具体服务类上(而非接口)
  • 同一应用上下文中未混合命令式与响应式方式
  • 凭证存储在环境变量中;
    .env
    已加入
    .gitignore
  • 首次运行Spring AI时设置
    spring.ai.vectorstore.neo4j.initialize-schema: true