15_spring_data_neo4j简单教程

news/2025/10/6 1:05:10/文章来源:https://www.cnblogs.com/suveng/p/19127200

Spring Data Neo4j 简单教程

简介

Spring Data Neo4j 是 Spring Data 项目的一部分,它提供了对 Neo4j 图数据库的集成支持。通过 Spring Data Neo4j,开发者可以轻松地在 Spring Boot 应用中使用 Neo4j 数据库,利用图数据库的优势处理复杂的关系数据。

环境准备

1. 安装 Neo4j

首先需要安装 Neo4j 数据库。可以通过以下方式安装:

  • Docker 方式:
docker run \--publish=7474:7474 --publish=7687:7687 \--env NEO4J_AUTH=neo4j/your_password \--env NEO4J_PLUGINS=["apoc"] \neo4j:5.15.0-community

注意:

  • 使用 neo4j:5.15.0-community 指定了具体的Neo4j版本(社区版)
  • 推荐使用稳定版本,避免使用 latest 标签,以确保环境一致性
  • 如果需要企业版功能,可以使用 neo4j:5.15.0-enterprise
  • 此命令不包含数据卷挂载,容器删除后数据会丢失。如需持久化数据,可以添加 --volume 参数

Neo4j社区版与企业版的区别

Neo4j提供两个主要版本:社区版(Community Edition)和企业版(Enterprise Edition),它们有以下主要区别:

社区版(Community Edition)

  • 免费使用:完全免费,适合开发、测试和小规模生产环境
  • 核心功能:包含完整的图数据库核心功能
  • 基础安全:提供基本的认证和授权功能
  • 单机部署:支持单实例部署
  • 基础备份:提供基本的备份和恢复功能
  • 适用场景:个人项目、学习、开发环境、小型应用

企业版(Enterprise Edition)

  • 商业许可:需要购买商业许可证
  • 高级安全:提供LDAP/Active Directory集成、Kerberos认证、SSL/TLS加密等
  • 集群支持:支持因果集群(Causal Clustering),实现高可用性和水平扩展
  • 高级备份:提供在线备份、增量备份和更强大的恢复功能
  • 监控和管理:包含更详细的监控指标和管理工具
  • 性能优化:包含更多性能优化功能
  • 适用场景:大型企业应用、高可用性要求的生产环境、需要处理大规模数据的场景

选择建议

  • 对于学习、开发和小型项目,社区版已经足够
  • 对于需要高可用性、高级安全功能或大规模部署的生产环境,建议使用企业版
  • 可以先使用社区版进行开发,后续根据需要升级到企业版

注意:

  • NEO4J_AUTH=neo4j/your_password 设置用户名和密码,请将 your_password 替换为您自己的密码
  • NEO4J_PLUGINS=["apoc"] 可选,安装APOC插件以提供更多功能
  • 首次启动时,Neo4j会使用提供的凭据创建用户

关于APOC插件

APOC (Awesome Procedures On Cypher) 是 Neo4j 的一个扩展插件,提供了许多额外的函数和过程,大大增强了 Neo4j 的功能。APOC插件包含:

  1. 数据处理功能:JSON处理、XML处理、日期时间操作等
  2. 图算法:最短路径、中心性算法、社区检测等
  3. 数据导入导出:从各种数据源(CSV、JSON、XML、数据库等)导入数据
  4. 图重构:节点合并、关系合并、图变换等
  5. 元数据操作:索引管理、约束管理、模式信息查询等
  6. 虚拟关系和节点:动态创建不实际存储在数据库中的关系和节点

APOC插件是可选的,但对于生产环境和复杂应用场景非常有用。如果您不需要这些额外功能,可以移除 NEO4J_PLUGINS=["apoc"] 环境变量。

  • 官方下载安装:访问 Neo4j 官网 下载对应平台的安装包

2. 创建 Spring Boot 项目

创建一个新的 Spring Boot 项目,推荐使用 Spring Boot 3.x 版本(如 3.2.0)以确保与 Neo4j 5.x 的兼容性。

pom.xml 中添加以下依赖:

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.0</version><relativePath/>
</parent><dependencies><!-- Neo4j 数据库支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-neo4j</artifactId></dependency><!-- Web 支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 测试依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- JUnit 5 (通常已包含在 spring-boot-starter-test 中) --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency><!-- Mockito (通常已包含在 spring-boot-starter-test 中) --><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><scope>test</scope></dependency><!-- 测试容器 - 用于集成测试中的 Neo4j 实例 --><dependency><groupId>org.testcontainers</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency><dependency><groupId>org.testcontainers</groupId><artifactId>neo4j</artifactId><scope>test</scope></dependency>
</dependencies>

测试依赖说明

  1. spring-boot-starter-test

    • Spring Boot 的核心测试启动器
    • 包含了大多数测试所需的库,如 JUnit 5、Mockito、AssertJ 等
    • 提供了 Spring Boot 测试上下文支持
  2. junit-jupiter

    • JUnit 5 的核心模块
    • 提供了现代的测试框架和注解(@Test、@BeforeEach、@AfterEach 等)
    • 支持参数化测试和动态测试
  3. mockito-core

    • 强大的模拟框架
    • 用于创建和管理模拟对象
    • 支持 BDD(行为驱动开发)风格的测试
  4. testcontainers

    • 提供轻量级的、一次性的数据库实例
    • 用于集成测试,确保测试环境的一致性
    • 支持多种数据库,包括 Neo4j
  5. testcontainers-neo4j

    • TestContainers 的 Neo4j 模块
    • 专门用于在测试中启动和管理 Neo4j 容器
    • 确保每个测试都有干净的数据库环境

测试配置

src/test/resources/application-test.yml 中添加测试配置:

spring:neo4j:uri: bolt://localhost:7687authentication:username: neo4jpassword: test_passwordlogging:level:org.springframework.data.neo4j: DEBUG

使用 TestContainers 进行集成测试

如果需要使用 TestContainers 进行集成测试,可以创建以下测试基类:

import org.junit.jupiter.api.BeforeAll;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public abstract class Neo4jIntegrationTestBase {@Containerstatic Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:5.15.0-community").withAdminPassword("test_password");@DynamicPropertySourcestatic void neo4jProperties(DynamicPropertyRegistry registry) {registry.add("spring.neo4j.uri", neo4jContainer::getBoltUrl);registry.add("spring.neo4j.authentication.username", () -> "neo4j");registry.add("spring.neo4j.authentication.password", () -> "test_password");}
}

然后集成测试类可以继承这个基类:

public class MovieIntegrationTest extends Neo4jIntegrationTestBase {// 测试代码...
}

版本兼容性说明

  • Spring Boot 3.x 与 Neo4j 5.x 兼容性最佳
  • 如果使用 Spring Boot 2.x,建议使用 Neo4j 4.x 版本
  • Spring Boot 3.x 需要 Java 17 或更高版本

配置连接

application.propertiesapplication.yml 中配置 Neo4j 连接信息:

# Neo4j 连接配置
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=your_password

或者使用 YAML 格式:

spring:neo4j:uri: bolt://localhost:7687authentication:username: neo4jpassword: your_password

定义节点实体

使用 @Node 注解定义图数据库中的节点实体:

import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Property;
import org.springframework.data.neo4j.core.schema.Relationship;@Node("Person")
public class Person {@Idprivate Long id;@Property("name")private String name;@Property("born")private Integer born;// 构造函数、getter和setter方法public Person() {}public Person(String name, Integer born) {this.name = name;this.born = born;}// 省略getter和setter方法...
}

定义关系

使用 @Relationship 注解定义节点之间的关系:

@Node("Person")
public class Person {// ... 其他属性@Relationship(type = "ACTED_IN", direction = Relationship.Direction.OUTGOING)private List<Movie> actedInMovies = new ArrayList<>();@Relationship(type = "DIRECTED", direction = Relationship.Direction.OUTGOING)private List<Movie> directedMovies = new ArrayList<>();// ... 其他代码
}@Node("Movie")
public class Movie {@Idprivate Long id;@Property("title")private String title;@Property("released")private Integer released;@Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING)private List<Person> actors = new ArrayList<>();@Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING)private List<Person> directors = new ArrayList<>();// 构造函数、getter和setter方法public Movie() {}public Movie(String title, Integer released) {this.title = title;this.released = released;}// 省略getter和setter方法...
}

创建仓库接口

创建继承自 Neo4jRepository 的仓库接口:

import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;@Repository
public interface PersonRepository extends Neo4jRepository<Person, Long> {// 根据名称查找人物Person findByName(String name);// 根据出生年份查找人物List<Person> findByBorn(Integer year);// 自定义查询:查找参演过特定电影的人物@Query("MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE m.title = $title RETURN p")List<Person> findActorsInMovie(String title);
}@Repository
public interface MovieRepository extends Neo4jRepository<Movie, Long> {// 根据标题查找电影Movie findByTitle(String title);// 根据发行年份查找电影List<Movie> findByReleased(Integer year);// 自定义查询:查找特定导演执导的电影@Query("MATCH (p:Person)-[:DIRECTED]->(m:Movie) WHERE p.name = $directorName RETURN m")List<Movie> findMoviesByDirector(String directorName);
}

创建服务层

创建服务层来处理业务逻辑:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class MovieService {private final PersonRepository personRepository;private final MovieRepository movieRepository;@Autowiredpublic MovieService(PersonRepository personRepository, MovieRepository movieRepository) {this.personRepository = personRepository;this.movieRepository = movieRepository;}// 添加人物public Person addPerson(Person person) {return personRepository.save(person);}// 添加电影public Movie addMovie(Movie movie) {return movieRepository.save(movie);}// 查找所有人物public List<Person> findAllPersons() {return personRepository.findAll();}// 查找所有电影public List<Movie> findAllMovies() {return movieRepository.findAll();}// 建立演员与电影的关系public Person addActorToMovie(String personName, String movieTitle) {Person person = personRepository.findByName(personName);Movie movie = movieRepository.findByTitle(movieTitle);if (person != null && movie != null) {person.getActedInMovies().add(movie);return personRepository.save(person);}return null;}// 建立导演与电影的关系public Person addDirectorToMovie(String personName, String movieTitle) {Person person = personRepository.findByName(personName);Movie movie = movieRepository.findByTitle(movieTitle);if (person != null && movie != null) {person.getDirectedMovies().add(movie);return personRepository.save(person);}return null;}// 查找参演特定电影的演员public List<Person> findActorsInMovie(String movieTitle) {return personRepository.findActorsInMovie(movieTitle);}// 查找特定导演执导的电影public List<Movie> findMoviesByDirector(String directorName) {return movieRepository.findMoviesByDirector(directorName);}
}

创建控制器

创建 REST API 控制器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/api")
public class MovieController {private final MovieService movieService;@Autowiredpublic MovieController(MovieService movieService) {this.movieService = movieService;}// 添加人物@PostMapping("/persons")public ResponseEntity<Person> addPerson(@RequestBody Person person) {Person savedPerson = movieService.addPerson(person);return ResponseEntity.ok(savedPerson);}// 添加电影@PostMapping("/movies")public ResponseEntity<Movie> addMovie(@RequestBody Movie movie) {Movie savedMovie = movieService.addMovie(movie);return ResponseEntity.ok(savedMovie);}// 获取所有人物@GetMapping("/persons")public ResponseEntity<List<Person>> getAllPersons() {List<Person> persons = movieService.findAllPersons();return ResponseEntity.ok(persons);}// 获取所有电影@GetMapping("/movies")public ResponseEntity<List<Movie>> getAllMovies() {List<Movie> movies = movieService.findAllMovies();return ResponseEntity.ok(movies);}// 添加演员到电影@PostMapping("/movies/{movieTitle}/actors")public ResponseEntity<Person> addActorToMovie(@PathVariable String movieTitle, @RequestParam String personName) {Person person = movieService.addActorToMovie(personName, movieTitle);if (person != null) {return ResponseEntity.ok(person);}return ResponseEntity.notFound().build();}// 添加导演到电影@PostMapping("/movies/{movieTitle}/directors")public ResponseEntity<Person> addDirectorToMovie(@PathVariable String movieTitle, @RequestParam String personName) {Person person = movieService.addDirectorToMovie(personName, movieTitle);if (person != null) {return ResponseEntity.ok(person);}return ResponseEntity.notFound().build();}// 获取电影中的演员@GetMapping("/movies/{movieTitle}/actors")public ResponseEntity<List<Person>> getActorsInMovie(@PathVariable String movieTitle) {List<Person> actors = movieService.findActorsInMovie(movieTitle);return ResponseEntity.ok(actors);}// 获取导演的电影@GetMapping("/directors/{directorName}/movies")public ResponseEntity<List<Movie>> getMoviesByDirector(@PathVariable String directorName) {List<Movie> movies = movieService.findMoviesByDirector(directorName);return ResponseEntity.ok(movies);}
}

自定义查询

Spring Data Neo4j 支持使用 @Query 注解执行自定义 Cypher 查询:

@Repository
public interface MovieRepository extends Neo4jRepository<Movie, Long> {// 查找演员数量超过指定数量的电影@Query("MATCH (m:Movie)<-[r:ACTED_IN]-(p:Person) " +"WITH m, count(p) as actorCount " +"WHERE actorCount > $minActors " +"RETURN m")List<Movie> findMoviesWithMoreThanActors(@Param("minActors") Integer minActors);// 查找既是演员又是导演的人物@Query("MATCH (p:Person)-[:ACTED_IN]->(:Movie), " +"(p:Person)-[:DIRECTED]->(:Movie) " +"RETURN DISTINCT p")List<Person> findActorsWhoAreAlsoDirectors();// 查找演员合作过的其他演员@Query("MATCH (p1:Person {name: $actorName})-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person) " +"WHERE p1 <> p2 " +"RETURN DISTINCT p2")List<Person> findCoActors(@Param("actorName") String actorName);
}

事务管理

Spring Data Neo4j 支持 Spring 的事务管理。可以使用 @Transactional 注解来管理事务:

import org.springframework.transaction.annotation.Transactional;@Service
public class MovieService {// ... 其他代码@Transactionalpublic void createMovieWithCast(Movie movie, List<Person> actors, Person director) {// 保存电影Movie savedMovie = movieRepository.save(movie);// 保存演员List<Person> savedActors = new ArrayList<>();for (Person actor : actors) {Person savedActor = personRepository.save(actor);savedActor.getActedInMovies().add(savedMovie);savedActors.add(personRepository.save(savedActor));}// 保存导演Person savedDirector = personRepository.save(director);savedDirector.getDirectedMovies().add(savedMovie);personRepository.save(savedDirector);}
}

测试应用

使用TDD(测试驱动开发)思想,我们需要为应用的所有核心功能编写全面的测试。以下是完整的测试用例:

1. 仓库层测试

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.test.context.ActiveProfiles;import java.util.Arrays;
import java.util.List;
import java.util.Optional;import static org.junit.jupiter.api.Assertions.*;@DataNeo4jTest
@ActiveProfiles("test")
class PersonRepositoryTest {@Autowiredprivate PersonRepository personRepository;@Autowiredprivate MovieRepository movieRepository;private Person tomHanks;private Movie forrestGump;@BeforeEachvoid setUp() {personRepository.deleteAll();movieRepository.deleteAll();tomHanks = new Person("Tom Hanks", 1956);forrestGump = new Movie("Forrest Gump", 1994);personRepository.save(tomHanks);movieRepository.save(forrestGump);}@Testvoid testFindByName() {Optional<Person> foundPerson = Optional.ofNullable(personRepository.findByName("Tom Hanks"));assertTrue(foundPerson.isPresent());assertEquals("Tom Hanks", foundPerson.get().getName());assertEquals(1956, foundPerson.get().getBorn());}@Testvoid testFindByBorn() {List<Person> people = personRepository.findByBorn(1956);assertEquals(1, people.size());assertEquals("Tom Hanks", people.get(0).getName());}@Testvoid testFindActorsInMovie() {// 建立关系tomHanks.getActedInMovies().add(forrestGump);personRepository.save(tomHanks);List<Person> actors = personRepository.findActorsInMovie("Forrest Gump");assertEquals(1, actors.size());assertEquals("Tom Hanks", actors.get(0).getName());}
}@DataNeo4jTest
@ActiveProfiles("test")
class MovieRepositoryTest {@Autowiredprivate MovieRepository movieRepository;@Autowiredprivate PersonRepository personRepository;private Movie forrestGump;private Person tomHanks;private Person robertZemeckis;@BeforeEachvoid setUp() {movieRepository.deleteAll();personRepository.deleteAll();forrestGump = new Movie("Forrest Gump", 1994);tomHanks = new Person("Tom Hanks", 1956);robertZemeckis = new Person("Robert Zemeckis", 1952);movieRepository.save(forrestGump);personRepository.save(tomHanks);personRepository.save(robertZemeckis);}@Testvoid testFindByTitle() {Optional<Movie> foundMovie = Optional.ofNullable(movieRepository.findByTitle("Forrest Gump"));assertTrue(foundMovie.isPresent());assertEquals("Forrest Gump", foundMovie.get().getTitle());assertEquals(1994, foundMovie.get().getReleased());}@Testvoid testFindByReleased() {List<Movie> movies = movieRepository.findByReleased(1994);assertEquals(1, movies.size());assertEquals("Forrest Gump", movies.get(0).getTitle());}@Testvoid testFindMoviesByDirector() {// 建立关系robertZemeckis.getDirectedMovies().add(forrestGump);personRepository.save(robertZemeckis);List<Movie> movies = movieRepository.findMoviesByDirector("Robert Zemeckis");assertEquals(1, movies.size());assertEquals("Forrest Gump", movies.get(0).getTitle());}@Testvoid testFindMoviesWithMoreThanActors() {// 添加更多演员Person actor1 = new Person("Actor 1", 1980);Person actor2 = new Person("Actor 2", 1980);personRepository.saveAll(Arrays.asList(actor1, actor2));// 建立关系forrestGump.getActors().addAll(Arrays.asList(tomHanks, actor1, actor2));movieRepository.save(forrestGump);List<Movie> movies = movieRepository.findMoviesWithMoreThanActors(2);assertEquals(1, movies.size());assertEquals("Forrest Gump", movies.get(0).getTitle());}@Testvoid testFindActorsWhoAreAlsoDirectors() {// 创建一个既是演员又是导演的人Person actorDirector = new Person("Actor Director", 1970);personRepository.save(actorDirector);// 创建两部电影Movie movie1 = new Movie("Movie 1", 2000);Movie movie2 = new Movie("Movie 2", 2001);movieRepository.saveAll(Arrays.asList(movie1, movie2));// 建立关系actorDirector.getActedInMovies().add(movie1);actorDirector.getDirectedMovies().add(movie2);personRepository.save(actorDirector);List<Person> people = personRepository.findActorsWhoAreAlsoDirectors();assertEquals(1, people.size());assertEquals("Actor Director", people.get(0).getName());}
}

2. 服务层测试

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;import java.util.Arrays;
import java.util.List;
import java.util.Optional;import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;@ExtendWith(MockitoExtension.class)
class MovieServiceTest {@Mockprivate PersonRepository personRepository;@Mockprivate MovieRepository movieRepository;@InjectMocksprivate MovieService movieService;private Person tomHanks;private Movie forrestGump;@BeforeEachvoid setUp() {tomHanks = new Person("Tom Hanks", 1956);forrestGump = new Movie("Forrest Gump", 1994);}@Testvoid testAddPerson() {when(personRepository.save(any(Person.class))).thenReturn(tomHanks);Person savedPerson = movieService.addPerson(tomHanks);assertNotNull(savedPerson);assertEquals("Tom Hanks", savedPerson.getName());verify(personRepository, times(1)).save(tomHanks);}@Testvoid testAddMovie() {when(movieRepository.save(any(Movie.class))).thenReturn(forrestGump);Movie savedMovie = movieService.addMovie(forrestGump);assertNotNull(savedMovie);assertEquals("Forrest Gump", savedMovie.getTitle());verify(movieRepository, times(1)).save(forrestGump);}@Testvoid testFindAllPersons() {List<Person> persons = Arrays.asList(tomHanks);when(personRepository.findAll()).thenReturn(persons);List<Person> result = movieService.findAllPersons();assertEquals(1, result.size());assertEquals("Tom Hanks", result.get(0).getName());verify(personRepository, times(1)).findAll();}@Testvoid testFindAllMovies() {List<Movie> movies = Arrays.asList(forrestGump);when(movieRepository.findAll()).thenReturn(movies);List<Movie> result = movieService.findAllMovies();assertEquals(1, result.size());assertEquals("Forrest Gump", result.get(0).getTitle());verify(movieRepository, times(1)).findAll();}@Testvoid testAddActorToMovie_Success() {when(personRepository.findByName("Tom Hanks")).thenReturn(tomHanks);when(movieRepository.findByTitle("Forrest Gump")).thenReturn(forrestGump);when(personRepository.save(any(Person.class))).thenReturn(tomHanks);Person result = movieService.addActorToMovie("Tom Hanks", "Forrest Gump");assertNotNull(result);assertEquals(1, result.getActedInMovies().size());assertEquals("Forrest Gump", result.getActedInMovies().get(0).getTitle());verify(personRepository, times(1)).save(tomHanks);}@Testvoid testAddActorToMovie_PersonNotFound() {when(personRepository.findByName("Tom Hanks")).thenReturn(null);when(movieRepository.findByTitle("Forrest Gump")).thenReturn(forrestGump);Person result = movieService.addActorToMovie("Tom Hanks", "Forrest Gump");assertNull(result);verify(personRepository, never()).save(any(Person.class));}@Testvoid testAddActorToMovie_MovieNotFound() {when(personRepository.findByName("Tom Hanks")).thenReturn(tomHanks);when(movieRepository.findByTitle("Forrest Gump")).thenReturn(null);Person result = movieService.addActorToMovie("Tom Hanks", "Forrest Gump");assertNull(result);verify(personRepository, never()).save(any(Person.class));}@Testvoid testAddDirectorToMovie_Success() {when(personRepository.findByName("Robert Zemeckis")).thenReturn(tomHanks);when(movieRepository.findByTitle("Forrest Gump")).thenReturn(forrestGump);when(personRepository.save(any(Person.class))).thenReturn(tomHanks);Person result = movieService.addDirectorToMovie("Robert Zemeckis", "Forrest Gump");assertNotNull(result);assertEquals(1, result.getDirectedMovies().size());assertEquals("Forrest Gump", result.getDirectedMovies().get(0).getTitle());verify(personRepository, times(1)).save(tomHanks);}@Testvoid testFindActorsInMovie() {List<Person> actors = Arrays.asList(tomHanks);when(personRepository.findActorsInMovie("Forrest Gump")).thenReturn(actors);List<Person> result = movieService.findActorsInMovie("Forrest Gump");assertEquals(1, result.size());assertEquals("Tom Hanks", result.get(0).getName());verify(personRepository, times(1)).findActorsInMovie("Forrest Gump");}@Testvoid testFindMoviesByDirector() {List<Movie> movies = Arrays.asList(forrestGump);when(movieRepository.findMoviesByDirector("Robert Zemeckis")).thenReturn(movies);List<Movie> result = movieService.findMoviesByDirector("Robert Zemeckis");assertEquals(1, result.size());assertEquals("Forrest Gump", result.get(0).getTitle());verify(movieRepository, times(1)).findMoviesByDirector("Robert Zemeckis");}@Testvoid testCreateMovieWithCast() {Person director = new Person("Robert Zemeckis", 1952);List<Person> actors = Arrays.asList(tomHanks);when(movieRepository.save(any(Movie.class))).thenReturn(forrestGump);when(personRepository.save(any(Person.class))).thenReturn(tomHanks).thenReturn(director);movieService.createMovieWithCast(forrestGump, actors, director);verify(movieRepository, times(1)).save(forrestGump);verify(personRepository, times(3)).save(any(Person.class)); // 2 actors + 1 director}
}

3. 控制器层测试

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;import java.util.Arrays;
import java.util.List;import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@WebMvcTest(MovieController.class)
class MovieControllerTest {@Autowiredprivate MockMvc mockMvc;@MockBeanprivate MovieService movieService;@Autowiredprivate ObjectMapper objectMapper;private Person tomHanks;private Movie forrestGump;@BeforeEachvoid setUp() {tomHanks = new Person("Tom Hanks", 1956);tomHanks.setId(1L);forrestGump = new Movie("Forrest Gump", 1994);forrestGump.setId(1L);}@Testvoid testAddPerson() throws Exception {when(movieService.addPerson(any(Person.class))).thenReturn(tomHanks);mockMvc.perform(post("/api/persons").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(tomHanks))).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Tom Hanks")).andExpect(jsonPath("$.born").value(1956));}@Testvoid testAddMovie() throws Exception {when(movieService.addMovie(any(Movie.class))).thenReturn(forrestGump);mockMvc.perform(post("/api/movies").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(forrestGump))).andExpect(status().isOk()).andExpect(jsonPath("$.title").value("Forrest Gump")).andExpect(jsonPath("$.released").value(1994));}@Testvoid testGetAllPersons() throws Exception {List<Person> persons = Arrays.asList(tomHanks);when(movieService.findAllPersons()).thenReturn(persons);mockMvc.perform(get("/api/persons")).andExpect(status().isOk()).andExpect(jsonPath("$[0].name").value("Tom Hanks")).andExpect(jsonPath("$[0].born").value(1956));}@Testvoid testGetAllMovies() throws Exception {List<Movie> movies = Arrays.asList(forrestGump);when(movieService.findAllMovies()).thenReturn(movies);mockMvc.perform(get("/api/movies")).andExpect(status().isOk()).andExpect(jsonPath("$[0].title").value("Forrest Gump")).andExpect(jsonPath("$[0].released").value(1994));}@Testvoid testAddActorToMovie() throws Exception {when(movieService.addActorToMovie("Tom Hanks", "Forrest Gump")).thenReturn(tomHanks);mockMvc.perform(post("/api/movies/Forrest Gump/actors").param("personName", "Tom Hanks")).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Tom Hanks"));}@Testvoid testAddActorToMovie_NotFound() throws Exception {when(movieService.addActorToMovie("Tom Hanks", "Forrest Gump")).thenReturn(null);mockMvc.perform(post("/api/movies/Forrest Gump/actors").param("personName", "Tom Hanks")).andExpect(status().isNotFound());}@Testvoid testAddDirectorToMovie() throws Exception {when(movieService.addDirectorToMovie("Robert Zemeckis", "Forrest Gump")).thenReturn(tomHanks);mockMvc.perform(post("/api/movies/Forrest Gump/directors").param("personName", "Robert Zemeckis")).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Tom Hanks"));}@Testvoid testGetActorsInMovie() throws Exception {List<Person> actors = Arrays.asList(tomHanks);when(movieService.findActorsInMovie("Forrest Gump")).thenReturn(actors);mockMvc.perform(get("/api/movies/Forrest Gump/actors")).andExpect(status().isOk()).andExpect(jsonPath("$[0].name").value("Tom Hanks"));}@Testvoid testGetMoviesByDirector() throws Exception {List<Movie> movies = Arrays.asList(forrestGump);when(movieService.findMoviesByDirector("Robert Zemeckis")).thenReturn(movies);mockMvc.perform(get("/api/directors/Robert Zemeckis/movies")).andExpect(status().isOk()).andExpect(jsonPath("$[0].title").value("Forrest Gump"));}
}

4. 集成测试

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;import java.util.Arrays;
import java.util.List;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class MovieIntegrationTest {@LocalServerPortprivate int port;@Autowiredprivate TestRestTemplate restTemplate;@Autowiredprivate PersonRepository personRepository;@Autowiredprivate MovieRepository movieRepository;@BeforeEachvoid setUp() {personRepository.deleteAll();movieRepository.deleteAll();}@Testvoid testFullWorkflow() {// 1. 添加人物Person person = new Person("Tom Hanks", 1956);ResponseEntity<Person> personResponse = restTemplate.postForEntity("http://localhost:" + port + "/api/persons", person, Person.class);assertEquals(200, personResponse.getStatusCodeValue());assertNotNull(personResponse.getBody().getId());// 2. 添加电影Movie movie = new Movie("Forrest Gump", 1994);ResponseEntity<Movie> movieResponse = restTemplate.postForEntity("http://localhost:" + port + "/api/movies", movie, Movie.class);assertEquals(200, movieResponse.getStatusCodeValue());assertNotNull(movieResponse.getBody().getId());// 3. 添加演员到电影ResponseEntity<Person> actorResponse = restTemplate.postForEntity("http://localhost:" + port + "/api/movies/Forrest Gump/actors?personName=Tom Hanks",null, Person.class);assertEquals(200, actorResponse.getStatusCodeValue());// 4. 查询电影中的演员ResponseEntity<Person[]> actorsResponse = restTemplate.getForEntity("http://localhost:" + port + "/api/movies/Forrest Gump/actors",Person[].class);assertEquals(200, actorsResponse.getStatusCodeValue());List<Person> actors = Arrays.asList(actorsResponse.getBody());assertEquals(1, actors.size());assertEquals("Tom Hanks", actors.get(0).getName());}
}

5. 测试配置

src/test/resources/application-test.yml 中添加测试配置:

spring:neo4j:uri: bolt://localhost:7687authentication:username: neo4jpassword: test_passwordlogging:level:org.springframework.data.neo4j: DEBUG

测试覆盖的功能点

通过以上测试,我们覆盖了以下功能点:

  1. 仓库层

    • 基本CRUD操作
    • 自定义查询方法
    • 关系查询
  2. 服务层

    • 业务逻辑验证
    • 异常处理
    • 事务管理
  3. 控制器层

    • REST API端点
    • 请求/响应序列化
    • HTTP状态码
  4. 集成测试

    • 完整工作流程
    • 组件间交互

这种全面的测试策略确保了应用的各个层次和功能都得到了验证,提高了代码质量和可靠性。

运行应用

创建 Spring Boot 主类:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Neo4jApplication {public static void main(String[] args) {SpringApplication.run(Neo4jApplication.class, args);}
}

运行应用后,可以通过以下方式测试 API:

  1. 添加人物:
curl -X POST http://localhost:8080/api/persons \-H "Content-Type: application/json" \-d '{"name": "Tom Hanks", "born": 1956}'
  1. 添加电影:
curl -X POST http://localhost:8080/api/movies \-H "Content-Type: application/json" \-d '{"title": "Forrest Gump", "released": 1994}'
  1. 添加演员到电影:
curl -X POST http://localhost:8080/api/movies/Forrest%20Gump/actors?personName=Tom%20Hanks
  1. 查询电影中的演员:
curl http://localhost:8080/api/movies/Forrest%20Gump/actors

高级特性

1. 自定义 ID 生成

可以使用 @Id 注解结合 @GeneratedValue 来自定义 ID 生成策略:

import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.GeneratedValue.UUIDGenerator;@Node("Person")
public class Person {@Id@GeneratedValue(UUIDGenerator.class)private String id;// ... 其他属性
}

2. 条件查询

使用 @Query 注解进行条件查询:

@Repository
public interface MovieRepository extends Neo4jRepository<Movie, Long> {// 查找指定年份范围内发行的电影@Query("MATCH (m:Movie) WHERE m.released >= $startYear AND m.released <= $endYear RETURN m")List<Movie> findMoviesByYearRange(@Param("startYear") Integer startYear, @Param("endYear") Integer endYear);
}

3. 分页和排序

Spring Data Neo4j 支持分页和排序:

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;@Repository
public interface MovieRepository extends Neo4jRepository<Movie, Long> {// 分页查询所有电影Page<Movie> findAll(Pageable pageable);// 按发行年份排序查询电影List<Movie> findByReleased(Integer year, Sort sort);
}

在服务层使用:

@Service
public class MovieService {// ... 其他代码public Page<Movie> findMoviesWithPagination(int page, int size) {Pageable pageable = PageRequest.of(page, size);return movieRepository.findAll(pageable);}public List<Movie> findMoviesByYearSorted(Integer year) {Sort sort = Sort.by("title").ascending();return movieRepository.findByReleased(year, sort);}
}

总结

Spring Data Neo4j 提供了强大的功能来操作 Neo4j 图数据库,使得开发者可以:

  1. 使用注解轻松定义节点和关系
  2. 通过仓库接口简化数据访问
  3. 使用自定义 Cypher 查询处理复杂查询需求
  4. 利用 Spring 的事务管理确保数据一致性
  5. 支持分页、排序等高级功能

通过本教程,你应该能够开始使用 Spring Data Neo4j 构建基于图数据库的应用程序,处理复杂的关系数据模型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/928891.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

珠海网站制作哪家便宜久久建筑网 百度网盘

认证与权限频率组件 身份验证是将传入请求与一组标识凭据&#xff08;例如请求来自的用户或其签名的令牌&#xff09;相关联的机制。然后 权限 和 限制 组件决定是否拒绝这个请求。 简单来说就是&#xff1a; 认证确定了你是谁权限确定你能不能访问某个接口限制确定你访问某…

成都网站建设制作设计内蒙古最新消息今天

Python是一种高级编程语言&#xff0c;广泛用于数据科学、人工智能、网络编程等领域。 Python提供了许多内置函数和标准库&#xff0c;可以完成各种任务&#xff1a; 1、print()函数&#xff1a;将文本输出到控制台。可以将字符串、数字和变量等输出到控制台。 2、input()函…

创建自己的网站怎么弄宁乡市住房和城乡建设局网站

原理概述 当一台BGP路由器中存在多条去往同一目标网络的BGP路由时&#xff0c;BGP协议会对这些BGP路由的属性进行比较&#xff0c;以确定去往该目标网络的最优BGP路由&#xff0c;然后将该最优BGP路由与去往同一目标网络的其他协议路由进行比较&#xff0c;从而决定是否将该最优…

重庆商家网站农村自建房设计师哪里找

目录 Python基础&#xff08;八&#xff09;--迭代&#xff0c;生成器&#xff0c;装饰器与元类 1 迭代 1.1 可迭代对象与迭代器 1.2 自定义迭代类型 1.3 迭代合体 2 生成器 2.1 什么是生成器 2.2 生成器表达式 2.3 生成器函数 3 装饰器 3.1 闭包 3.2 什么是装饰器 …

如何向百度举报网站国外网站怎么上

2019独角兽企业重金招聘Python工程师标准>>> 安装。。。后查看 import django django.VERSION #输出版本号&#xff0c;目前自己是py2.7.9和django1.8 1&#xff0c;新建一个django-project django-admin.py startproject project-name 一个project一般为一个项目 …

网站制作费用申请移动互联网开发记事本项目告别

这一篇讲解消费者 文章目录一、依赖配置1. 引入依赖2. 配置文件3. 主配置二、代码Conding2.1. 消费者代码一、依赖配置 1. 引入依赖 <!--springboot整合RabbitMQ依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>sp…

济南seo外贸网站建设小型公司网站建设

AV1 屏幕内容编码 为了提高屏幕捕获内容的压缩性能&#xff0c;AV1采用了几种编码工具&#xff0c;例如用于处理屏幕画面中重复模式的内帧内块复制&#xff08;IntraBC&#xff09;&#xff0c;以及用于处理颜色数量有限的屏幕块的调色板模式。 帧内块拷贝 AV1 编码中的 Intra …

锦州做网站哪家好cloudfare wordpress

文章目录 Spring Boot 约定大于配置&#xff1a;实现自定义配置引言1. Spring Boot 的约定大于配置2. 自定义配置的需求3. 实现自定义配置的步骤4. 示例&#xff1a;自定义 Spring MVC 配置4.1 创建自定义配置类4.2 创建自定义拦截器4.3 测试自定义配置 5. 其他自定义配置场景5…

CF2152G Query Jungle(线段树,重链剖分,*)

CF2152G Query Jungle 子树翻转,求没有黑色子孙的黑色点个数。套上 mincnt 标签和双生 rev 标签即可。不明白提交记录里的人都在写什么鬼。 Code const int inf = 1 << 30;struct Node {int m1 = inf, mc1 = 0,…

代码随想录算法训练营第九天 | leetcode 151 卡特55

反转字符串中的单词 整体思路:先将整体翻转,再进行翻转其中的单词,以空格划分进行单词操作,使用快慢指针思想,快指针获取符合题目要求的字母,慢指针是获取到字母后更新到哪里 代码如下:class Solution { public…

[题解] 分竹子

传送门 题目描述 将 bamboo_len 的竹子砍为若干整数段, 求每段竹子长度的最大乘积. 2 <= bamboo_len <= 58 分析 设将长度为 \(s\) 的竹子分为 \(n\) 段, 每段分别为 \(a_1, a_2, \cdots, a_n\) , 问题转化为求 …

可画在线设计网站网站建设策划书事物选题

如果還不知道什麼是 Pagination 或者還不了解如何使用&#xff0c;請參考&#xff1a; CakePHP Pagination (分頁功能) 。通常在管理後台實作時&#xff0c;常設定許多查詢條件來查詢資料&#xff0c;比如&#xff1a;起始、結束時間。通常這些參數都是用GET的方式在傳遞。以下…

万网网站多少直播营销策划方案范文

transport传输 一、Tansport 转发到Producer二、RtpStreamRecv 处理收到的包三、数据传输到Router&#xff0c;再分发到Consumertips 一、Tansport 转发到Producer Transport收到数据packet后&#xff0c;会解析出packet中所带的ssrc字段&#xff0c;然后基于ssrc找到该数据的…

烟台房地产网站建设flash网站与html5

1 标准误 1.1 定义 标准误&#xff08;Standard Error&#xff09;是用来衡量统计样本估计量&#xff08;如均值、回归系数等&#xff09;与总体参数之间的差异的一种统计量。标准误衡量了样本估计量的变异程度&#xff0c;提供了对总体参数的估计的不确定性的度量。标准误越…

免费做网站怎么做网站3.15网站建设

文章目录1. 题目信息2. 解题2.1 递归查找2.2 改循环1. 题目信息 给定一个非空特殊的二叉树&#xff0c;每个节点都是正数&#xff0c;并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话&#xff0c;那么这个节点的值不大于它的子节点的值。 给出这样的一…

实力强劲的机器视觉公司有哪些:2025年TOP5精选榜单

实力强劲的机器视觉公司有哪些:2025年TOP5精选榜单科技的飞速迭代正重塑工业生产模式,机器视觉公司作为工业自动化的“技术引擎”,凭借图像采集、算法分析等核心能力,为3C电子、汽车制造、半导体等领域提供精准检测…

常见的电子商务网站有南宁网站推广优化

本章节的目的是 【明确目标用户群】 &#xff0c;以更好的服务现有用户。 【知识点】 1.作图 显示中文plt.rcParams[font.sans-serif] [SimHei] # 步骤一&#xff08;替换sans-serif字体&#xff09; plt.rcParams[axes.unicode_minus] False # 步骤二&#xff08;解决坐标轴…

保健品网站建设流程网站模板下载免费

文章目录 前言 1 配置 2 使用 3 MAVLink协议说明 前言 ArduPilot 和任务计划器能够通过使用加密密钥添加数据包签名&#xff0c;为空中 MAVLink 传输增加安全性。这并不加密数据&#xff0c;只是控制自动驾驶仪是否响应 MAVLink 命令。 当自动驾驶仪处于激活状态时&#x…

优质机器视觉教育装备有哪些:2025年TOP5推荐清单

优质机器视觉教育装备有哪些:2025年TOP5推荐清单科技浪潮推动教育领域向智能化转型,机器视觉教育装备作为培养工业自动化、人工智能领域专业人才的核心工具,已广泛应用于高校智能制造专业、职业院校工业机器人课程及…

2025年机器视觉软件平台哪个好:全行业品牌TOP5推荐榜单

2025年机器视觉软件平台哪个好:全行业品牌TOP5推荐榜单科技的飞速发展正深刻改变生产生活方式,机器视觉软件平台作为工业自动化的"智慧之眼",已广泛应用于3C电子、半导体、汽车零部件等众多领域,为品质检…