1. 概述

Neo4j 是一款流行的图数据库管理系统,专为基于图模型的数据存储、管理和查询而设计。本文将带你一步步配置项目,并使用 Spring Data Neo4j 的各种组件与 Neo4j 数据库交互。

提示:图数据库特别适合处理高度关联的数据,比如社交网络、推荐系统等场景。如果你还没接触过,可以把它想象成“关系型数据库的增强版”——但别被这个说法误导,两者底层设计理念完全不同。

2. 项目搭建

2.1 依赖配置

首先在项目中添加必要依赖。核心依赖是 Spring Boot Starter Data Neo4j:

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

2.2 测试工具:Neo4j Harness

为了方便测试,我们使用 Neo4j Harness(嵌入式测试工具):

<dependency>
    <groupId>org.neo4j.test</groupId>
    <artifactId>neo4j-harness</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>

踩坑提醒:Harness 能让你在测试中启动内存数据库,无需安装完整的 Neo4j 服务。但注意版本兼容性——这里我们用的是 Neo4j 5.x 版本,需确保与 Spring Boot 版本匹配。

2.3 数据库连接配置

application.properties 中配置连接参数:

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

接着创建配置类指定数据库方言:

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

关键点:NEO4J_5 方向 Spring Data Neo4j 使用 Neo4j 5.x 的语法规范生成查询。如果实际数据库版本不同,这里需要调整。

3. 代码实战

3.1 定义实体类

先创建 Book 实体:

@Node("Book")
public class Book {
    @Id
    private String isbn;
    
    @Property("name")
    private String title;
    
    private Integer year;

    @Relationship(type = "WRITTEN_BY", direction = Relationship.Direction.OUTGOING)
    private Author author;

    // 构造方法、getter/setter 省略
}

注解说明:

  • @Node:标记为图数据库节点
  • @Id:主键标识
  • @Property:当字段名与数据库属性名不同时使用
  • @Relationship:定义节点关系(必须指定类型和方向)

再创建 Author 实体:

@Node("Author")
public class Author {
    @Id
    private Long id;
    
    private String name;

    @Relationship(type = "WRITTEN_BY", direction = Relationship.Direction.INCOMING)
    private List<Book> books;

    // 构造方法、getter/setter 省略
}

注意:Author 实体中关系方向为 INCOMING,与 Book 实体中的 OUTGOING 形成双向映射。

3.2 创建仓库接口

定义 BookRepository

@Repository
public interface BookRepository extends Neo4jRepository<Book, String> {
    Book findOneByTitle(String title);
    List<Book> findAllByYear(Integer year);
}

简单粗暴:Spring Data 会自动根据方法名生成 Cypher 查询,无需手写 SQL。类似 JPA 的体验,但底层是图查询语言。

3.3 自定义查询

使用 @Query 注解编写复杂查询。在 AuthorRepository 中添加:

@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);
}

查询语法要点:

  • MATCH:匹配图模式(类似 SQL 的 JOIN)
  • WHERE:添加过滤条件
  • $name:参数占位符(用 @Param 绑定方法参数)

老鸟须知:Cypher 是图查询语言,语法与 SQL 差异较大。建议先掌握基础模式匹配语法再进阶。

4. 测试实战

4.1 配置测试环境

使用 Neo4j Harness 搭建测试环境:

class BookAndAuthorRepositoryIntegrationTest {

    private static Neo4j newServer;
    
    @BeforeAll
    static void initializeNeo4j() {
        newServer = Neo4jBuilders.newInProcessBuilder()
          .withDisabledServer()
          .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() {
        newServer.close();
    }
    
    @DynamicPropertySource
    static void neo4jProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.neo4j.uri", newServer::boltURI);
        registry.add("spring.neo4j.authentication.username", () -> "neo4j");
        registry.add("spring.neo4j.authentication.password", () -> "test123");
    }
}

关键配置解析:

  1. withDisabledServer():禁用 HTTP 接口(测试无需外部访问)
  2. withFixture():初始化测试数据(Cypher 脚本)
  3. @DynamicPropertySource:动态覆盖配置文件中的连接参数

4.2 仓库测试

添加测试用例:

@DataNeo4jTest
class BookAndAuthorRepositoryIntegrationTest {
    
    // 前面的配置代码...
    
    @Autowired
    private BookRepository bookRepository;
    
    @Autowired
    private AuthorRepository authorRepository;
    
    @Test
    void givenBookExists_whenFindOneByTitle_thenBookIsReturned() {
        Book book = bookRepository.findOneByTitle("The Fellowship of the Ring");
        assertEquals("978-0547928210", book.getIsbn());
    }

    @Test
    void givenOneBookExistsForYear_whenFindAllByYear_thenOneBookIsReturned() {
        List<Book> books = bookRepository.findAllByYear(1954);
        assertEquals(1, books.size());
    }
    
    @Test
    void givenOneBookExistsAfterYear_whenFindBooksAfterYear_thenOneBookIsReturned() {
        List<Book> books = authorRepository.findBooksAfterYear("J. R. R. Tolkien", 1955);
        assertEquals(1, books.size());
    }
}

@DataNeo4jTest 注解会自动配置 Spring Data Neo4j 相关的测试环境,比手动配置更高效。

5. 总结

本文系统介绍了 Spring Data Neo4j 的核心用法:

  1. 项目搭建与依赖配置
  2. 实体关系映射(@Node@Relationship 等)
  3. 仓库接口与自定义查询
  4. 基于 Harness 的集成测试

实战建议:图数据库在复杂关系查询上优势明显,但需注意事务管理和性能优化。生产环境建议使用 Neo4j 集群模式,并配置适当的索引策略。

完整代码示例可在 GitHub 获取。


原始标题:Introduction to Spring Data Neo4j