spring-data-neo4j
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSpring 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=secretConfigure 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 for blocking, imperative operations
Neo4jRepository - Use for non-blocking, reactive operations
ReactiveNeo4jRepository - 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 for parameter placeholders
$parameterName - Use annotation when parameter name differs from method parameter
@Param - 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 for imperative or
Neo4jRepositoryfor reactiveReactiveNeo4jRepository - Use query derivation for simple queries
- Write custom @Query for complex graph patterns
- Don't mix imperative and reactive in same application
- 对于命令式操作继承,对于响应式操作继承
Neo4jRepositoryReactiveNeo4jRepository - 简单查询使用查询推导
- 复杂图模式使用自定义@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 Cypher queries
withFixture() - Use for test slicing
@DataNeo4jTest - Test both successful and edge-case scenarios
- 使用Neo4j Harness进行集成测试
- 通过Cypher查询提供测试数据
withFixture() - 使用进行测试切片
@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
- 事务管理
- 性能调优