Neo4j 在 Spring Boot 中的使用详解
Neo4j 是一个流行的图数据库,Spring Boot 提供了对 Neo4j 的良好支持。下面我将详细介绍如何在 Spring Boot 项目中使用 Neo4j。
1. 添加依赖
首先需要在 pom.xml
中添加 Neo4j 相关依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
如果你使用的是 Spring Boot 3.x 及以上版本,还需要添加 Neo4j Java 驱动:
xml
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>5.7.0</version> <!-- 使用最新版本 -->
</dependency>
2. 配置 Neo4j 连接
在 application.properties
或 application.yml
中配置 Neo4j 连接:
properties
# application.properties 配置示例
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=your_password
或者使用 YAML 格式:
yaml
# application.yml 配置示例
spring:
neo4j:
uri: bolt://localhost:7687
authentication:
username: neo4j
password: your_password
3. 定义实体类
使用注解定义 Neo4j 实体和关系:
java
import org.springframework.data.neo4j.core.schema.*;
@Node("Person") // 定义节点标签
public class Person {
@Id @GeneratedValue // 定义ID
private Long id;
@Property("name") // 定义属性
private String name;
@Property("born")
private Integer born;
// 定义关系
@Relationship(type = "ACTED_IN", direction = Relationship.Direction.OUTGOING)
private List<Movie> actedIn = new ArrayList<>();
// 构造方法、getter和setter
// ...
}
@Node("Movie")
public class Movie {
@Id @GeneratedValue
private Long id;
@Property("title")
private String title;
@Property("released")
private Integer released;
// 构造方法、getter和setter
// ...
}
4. 创建 Repository 接口
Spring Data Neo4j 提供了类似于 JPA 的 Repository 接口:
java
import org.springframework.data.neo4j.repository.Neo4jRepository;
public interface PersonRepository extends Neo4jRepository<Person, Long> {
// 自定义查询方法
Person findByName(String name);
List<Person> findByBornGreaterThan(Integer year);
@Query("MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE m.title = $title RETURN p")
List<Person> findActorsByMovieTitle(String title);
}
5. 使用 Neo4jTemplate
对于更复杂的操作,可以使用 Neo4jTemplate
:
java
import org.springframework.data.neo4j.core.Neo4jTemplate;
@Service
public class MovieService {
private final Neo4jTemplate neo4jTemplate;
public MovieService(Neo4jTemplate neo4jTemplate) {
this.neo4jTemplate = neo4jTemplate;
}
public List<Movie> findMoviesByActorName(String actorName) {
String query = "MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name = $name RETURN m";
return neo4jTemplate.findAll(query, Map.of("name", actorName), Movie.class);
}
}
6. 事务管理
Spring Boot 自动为 Neo4j 提供了事务支持:
java
import org.springframework.transaction.annotation.Transactional;
@Service
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
@Transactional
public Person createPersonWithMovies(String name, Integer born, List<Movie> movies) {
Person person = new Person();
person.setName(name);
person.setBorn(born);
person.setActedIn(movies);
return personRepository.save(person);
}
}
7. 高级特性
7.1 自定义转换器
如果需要处理特殊数据类型,可以创建自定义转换器:
java
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import java.time.LocalDate;
@WritingConverter
public class LocalDateToNeo4jValueConverter implements Converter<LocalDate, Value> {
@Override
public Value convert(LocalDate source) {
return Values.value(source.toString());
}
}
然后在配置类中注册:
java
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
@Configuration
public class Neo4jConfig extends AbstractNeo4jConfig {
@Override
protected Collection<Converter<?, ?>> getCustomConverters() {
return List.of(new LocalDateToNeo4jValueConverter());
}
}
7.2 使用原生驱动
有时可能需要直接使用 Neo4j 的原生驱动:
java
import org.neo4j.driver.Driver;
import org.neo4j.driver.Session;
import org.neo4j.driver.Result;
@Service
public class NativeDriverService {
private final Driver driver;
public NativeDriverService(Driver driver) {
this.driver = driver;
}
public List<String> getAllMovieTitles() {
try (Session session = driver.session()) {
Result result = session.run("MATCH (m:Movie) RETURN m.title AS title");
return result.stream()
.map(record -> record.get("title").asString())
.collect(Collectors.toList());
}
}
}
8. 测试
可以使用 @DataNeo4jTest
进行测试:
java
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
@DataNeo4jTest
public class PersonRepositoryTest {
@Autowired
private PersonRepository personRepository;
@Test
public void shouldSaveAndRetrievePerson() {
Person person = new Person();
person.setName("Keanu Reeves");
person.setBorn(1964);
personRepository.save(person);
Person found = personRepository.findByName("Keanu Reeves");
assertThat(found).isNotNull();
assertThat(found.getBorn()).isEqualTo(1964);
}
}
9. 性能优化建议
- 使用投影:只查询需要的字段
- 批量操作:对于大量数据,使用批量操作
- 索引:为常用查询字段创建索引
- 关系方向:正确指定关系方向提高查询效率
- 分页:对于大数据集使用分页查询
10. 常见问题解决
- 连接问题:确保 Neo4j 服务器运行且认证信息正确
- 版本兼容性:确保 Spring Boot 和 Neo4j 驱动版本兼容
- 事务问题 :确保在需要事务的方法上添加
@Transactional
注解 - 懒加载问题:注意实体间关系的加载策略
通过以上步骤,你应该能够在 Spring Boot 应用中成功集成和使用 Neo4j 图数据库。根据你的具体需求,可以进一步探索更高级的特性和优化策略。
注意
在 Neo4j 中,@Id @GeneratedValue
注解组合使用的 id
字段对应的是 Neo4j 数据库内部提供的原生节点ID,而不是你自定义的新 ID。
详细解释
-
Neo4j 原生 ID:
- 每个 Neo4j 节点在创建时都会自动分配一个唯一的、长整型的内部 ID
- 这个 ID 是数据库自动生成的,不可修改
- 通过
@Id @GeneratedValue
注解,你将这个内部 ID 映射到实体类的id
字段
-
特点:
- 这个 ID 是 Neo4j 内部使用的标识符
- 在数据库生命周期内是唯一的,但不建议作为业务标识符使用 ,因为:
- 当删除节点后,其 ID 可能被重新分配给新节点
- 在数据库备份/恢复时 ID 可能会变化
-
最佳实践:
java@Id @GeneratedValue private Long id; // 这个 id 对应 Neo4j 内部节点 ID @Property("userId") // 推荐同时定义业务 ID private String userId; // 业务相关唯一标识
-
替代方案: 如果你想使用自定义 ID (如 UUID),可以这样配置:
java@Id @GeneratedValue(UUIDStringGenerator.class) private String id; // 使用 UUID 作为 ID
需要自定义生成器:
javapublic class UUIDStringGenerator implements IdGenerator<String> { @Override public String generateId(String primaryLabel, Object entity) { return UUID.randomUUID().toString(); } }
-
查询时的区别:
- 使用原生 ID 查找:
personRepository.findById(123L)
- 使用业务 ID 查找:
personRepository.findByUserId("user-001")
- 使用原生 ID 查找:
总结:默认情况下 @Id @GeneratedValue
使用的是 Neo4j 内部节点 ID,适合技术性操作。对于业务场景,建议额外定义业务唯一标识字段。