|
该版本仍在开发中,尚未被视为稳定。对于最新稳定版本,请使用Spring Data Neo4j 8.0.0! |
常见问题
SDN与Neo4j-OGM有什么关系?
Neo4j-OGM 是一个对象图映射库,主要被 Spring Data Neo4j 的早期版本用作后端,用于将映射节点和关系重载到域对象中。 当前的SDN不需要也不支持Neo4j-OGM。 SDN 专门使用 Spring Data 的映射上下文来扫描类和构建元模型。
虽然这使 SDN 与 Spring 生态系统紧密结合,但它有几个优势,其中包括 CPU 和内存占用更小的占用,尤其是 Spring 映射上下文的所有功能。
为什么我应该用SDN而不是SDN+OGM
SDN具有几个SDN+OGM中没有的功能,尤其是
-
对Springs响应式故事的全面支持,包括响应式交易
-
对示例查询的全面支持
-
完全支持完全不可变的实体
-
支持所有衍生查找方法的修饰符及其变体,包括空间查询
SDN支持嵌入式Neo4j吗?
嵌入式Neo4j具有多重方面:
SDN是否直接与嵌入式实例交互?
不。
嵌入式数据库通常由org.neo4j.graphdb.GraphDatabaseService而且开箱即用没有螺栓连接器。
不过,SDN可以很好地配合Neo4j的测试框架,测试框架专门设计为直接替代真实数据库。
Neo4j 3.5、4.x 和 5.x 测试线束的支持通过驱动的 Spring Boot 启动器实现。
看看对应的模块org.neo4j.driver:neo4j-java-driver-test-harness-spring-boot-autoconfigure.
可以使用哪款 Neo4j Java 驱动,以及如何使用?
SDN 依赖于 Neo4j Java 驱动。 每个SDN版本都使用与当时最新Neo4j兼容的Neo4j Java驱动版本 释放。 虽然 Neo4j Java 驱动的补丁版本通常是直接替换的,但 SDN 确保即使是次要版本 可以互换,因为它会检查方法或接口变更的存在与否(如有需要)。
因此,你可以使用任何4.x Neo4j Java驱动,配合任何SDN 6.x版本, 以及任何带有SDN 7.x版本的5.x Neo4j驱动。
Neo4j 4 支持多个数据库——我该如何使用它们?
你可以静态配置数据库名称,或者运行自己的数据库名称提供者。 请记住,SDN不会帮你创建数据库。 你可以借助迁移工具实现,当然也可以先用一个简单的脚本。
静态配置
将数据库名称配置为在你的 Spring Boot 配置中使用,就像这样(当然,同样的属性也适用于 YML 或基于环境的配置,并且应用了 Spring Boot 的惯例):
spring.data.neo4j.database = yourDatabase
有了该配置,所有由 SDN 仓库实例(包括响应式和命令式)生成的所有查询,以及ReactiveNeo4j模板分别Neo4j模板将对数据库执行你的数据库.
动态配置
提供具有类型Neo4jDatabaseNameProvider或ReactiveDatabaseSelectionProvider这取决于你春季申请的类型。
比如说,这个“豆子”可以用Spring的安全上下文来检索租户。 这是一个使用 Spring Security 保护的命令式应用程序的工作示例:
import org.neo4j.springframework.data.core.DatabaseSelection;
import org.neo4j.springframework.data.core.DatabaseSelectionProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
@Configuration
public class Neo4jConfig {
@Bean
DatabaseSelectionProvider databaseSelectionProvider() {
return () -> Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername)
.map(DatabaseSelection::byName)
.orElseGet(DatabaseSelection::undecided);
}
}
| 注意不要把从一个数据库检索到的实体和另一个数据库混淆。 每次新交易都会请求数据库名称,因此在调用之间更改数据库名称时,实体数量可能会比预期少或多。 更糟的是,你可能不可避免地把错误的实体存储在错误的数据库里。 |
Spring Boot Neo4j 的健康指示器指向默认数据库,我该如何更改?
Spring Boot 配备了强制性和被动的 Neo4j 健康指标。这两种变体都能检测多个org.neo4j.driver.Driver在应用上下文中提供
对每个实例的整体健康状况都有贡献。
不过 Neo4j 驱动确实连接到的是服务器,而不是连接到服务器内部的特定数据库。
Spring Boot 能够在不使用 Spring Data Neo4j 的情况下配置驱动,并作为使用数据库的信息
与Spring Data Neo4j绑定,这些信息无法被内置健康指示器提供。
在许多部署场景中,这很可能不是问题。 然而,如果配置的数据库用户没有至少对默认数据库的访问权限,健康检查将失败。
通过了解数据库选择的定制 Neo4j 健康贡献者可以缓解这个问题。
祈使变体
import java.util.Optional;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Result;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.summary.DatabaseInfo;
import org.neo4j.driver.summary.ResultSummary;
import org.neo4j.driver.summary.ServerInfo;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.data.neo4j.core.DatabaseSelection;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.util.StringUtils;
public class DatabaseSelectionAwareNeo4jHealthIndicator extends AbstractHealthIndicator {
private final Driver driver;
private final DatabaseSelectionProvider databaseSelectionProvider;
public DatabaseSelectionAwareNeo4jHealthIndicator(
Driver driver, DatabaseSelectionProvider databaseSelectionProvider
) {
this.driver = driver;
this.databaseSelectionProvider = databaseSelectionProvider;
}
@Override
protected void doHealthCheck(Health.Builder builder) {
try {
SessionConfig sessionConfig = Optional
.ofNullable(databaseSelectionProvider.getDatabaseSelection())
.filter(databaseSelection -> databaseSelection != DatabaseSelection.undecided())
.map(DatabaseSelection::getValue)
.map(v -> SessionConfig.builder().withDatabase(v).build())
.orElseGet(SessionConfig::defaultConfig);
class Tuple {
String edition;
ResultSummary resultSummary;
Tuple(String edition, ResultSummary resultSummary) {
this.edition = edition;
this.resultSummary = resultSummary;
}
}
String query =
"CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition";
Tuple health = driver.session(sessionConfig)
.writeTransaction(tx -> {
Result result = tx.run(query);
String edition = result.single().get("edition").asString();
return new Tuple(edition, result.consume());
});
addHealthDetails(builder, health.edition, health.resultSummary);
} catch (Exception ex) {
builder.down().withException(ex);
}
}
static void addHealthDetails(Health.Builder builder, String edition, ResultSummary resultSummary) {
ServerInfo serverInfo = resultSummary.server();
builder.up()
.withDetail(
"server", serverInfo.version() + "@" + serverInfo.address())
.withDetail("edition", edition);
DatabaseInfo databaseInfo = resultSummary.database();
if (StringUtils.hasText(databaseInfo.name())) {
builder.withDetail("database", databaseInfo.name());
}
}
}
它利用可用的数据库选择执行与 Boot 相同的查询,以检查连接是否健康。 请使用以下配置来应用:
import java.util.Map;
import org.neo4j.driver.Driver;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
@Configuration(proxyBeanMethods = false)
public class Neo4jHealthConfig {
@Bean (1)
DatabaseSelectionAwareNeo4jHealthIndicator databaseSelectionAwareNeo4jHealthIndicator(
Driver driver, DatabaseSelectionProvider databaseSelectionProvider
) {
return new DatabaseSelectionAwareNeo4jHealthIndicator(driver, databaseSelectionProvider);
}
@Bean (2)
HealthContributor neo4jHealthIndicator(
Map<String, DatabaseSelectionAwareNeo4jHealthIndicator> customNeo4jHealthIndicators) {
return CompositeHealthContributor.fromMap(customNeo4jHealthIndicators);
}
@Bean (3)
InitializingBean healthContributorRegistryCleaner(
HealthContributorRegistry healthContributorRegistry,
Map<String, DatabaseSelectionAwareNeo4jHealthIndicator> customNeo4jHealthIndicators
) {
return () -> customNeo4jHealthIndicators.keySet()
.stream()
.map(HealthContributorNameFactory.INSTANCE)
.forEach(healthContributorRegistry::unregisterContributor);
}
}
| 1 | 如果你有多个Drivers和数据库选择提供商,你需要为每个组合创建一个指示器 |
| 2 | 这确保所有这些指标都归入 Neo4j,取代默认的 Neo4j 健康指标 |
| 3 | 这防止了个别贡献者直接出现在健康端点 |
反应变异
响应式变体基本相同,使用反应类型和相应的响应式基础设施类:
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import org.neo4j.driver.Driver;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.reactivestreams.RxResult;
import org.neo4j.driver.reactivestreams.RxSession;
import org.neo4j.driver.summary.DatabaseInfo;
import org.neo4j.driver.summary.ResultSummary;
import org.neo4j.driver.summary.ServerInfo;
import org.reactivestreams.Publisher;
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.data.neo4j.core.DatabaseSelection;
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
import org.springframework.util.StringUtils;
public final class DatabaseSelectionAwareNeo4jReactiveHealthIndicator
extends AbstractReactiveHealthIndicator {
private final Driver driver;
private final ReactiveDatabaseSelectionProvider databaseSelectionProvider;
public DatabaseSelectionAwareNeo4jReactiveHealthIndicator(
Driver driver,
ReactiveDatabaseSelectionProvider databaseSelectionProvider
) {
this.driver = driver;
this.databaseSelectionProvider = databaseSelectionProvider;
}
@Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
String query =
"CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition";
return databaseSelectionProvider.getDatabaseSelection()
.map(databaseSelection -> databaseSelection == DatabaseSelection.undecided() ?
SessionConfig.defaultConfig() :
SessionConfig.builder().withDatabase(databaseSelection.getValue()).build()
)
.flatMap(sessionConfig ->
Mono.usingWhen(
Mono.fromSupplier(() -> driver.rxSession(sessionConfig)),
s -> {
Publisher<Tuple2<String, ResultSummary>> f = s.readTransaction(tx -> {
RxResult result = tx.run(query);
return Mono.from(result.records())
.map((record) -> record.get("edition").asString())
.zipWhen((edition) -> Mono.from(result.consume()));
});
return Mono.fromDirect(f);
},
RxSession::close
)
).map((result) -> {
addHealthDetails(builder, result.getT1(), result.getT2());
return builder.build();
});
}
static void addHealthDetails(Health.Builder builder, String edition, ResultSummary resultSummary) {
ServerInfo serverInfo = resultSummary.server();
builder.up()
.withDetail(
"server", serverInfo.version() + "@" + serverInfo.address())
.withDetail("edition", edition);
DatabaseInfo databaseInfo = resultSummary.database();
if (StringUtils.hasText(databaseInfo.name())) {
builder.withDetail("database", databaseInfo.name());
}
}
}
当然,还有该配置的响应式变体。它需要两个不同的注册表清理工具,就像 Spring Boot 一样 也将现有的响应式指示器包裹起来,以便与非响应式执行器端点一起使用。
import java.util.Map;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
import org.springframework.boot.actuate.health.HealthContributorNameFactory;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class Neo4jHealthConfig {
@Bean
ReactiveHealthContributor neo4jHealthIndicator(
Map<String, DatabaseSelectionAwareNeo4jReactiveHealthIndicator> customNeo4jHealthIndicators) {
return CompositeReactiveHealthContributor.fromMap(customNeo4jHealthIndicators);
}
@Bean
InitializingBean healthContributorRegistryCleaner(HealthContributorRegistry healthContributorRegistry,
Map<String, DatabaseSelectionAwareNeo4jReactiveHealthIndicator> customNeo4jHealthIndicators) {
return () -> customNeo4jHealthIndicators.keySet()
.stream()
.map(HealthContributorNameFactory.INSTANCE)
.forEach(healthContributorRegistry::unregisterContributor);
}
@Bean
InitializingBean reactiveHealthContributorRegistryCleaner(
ReactiveHealthContributorRegistry healthContributorRegistry,
Map<String, DatabaseSelectionAwareNeo4jReactiveHealthIndicator> customNeo4jHealthIndicators) {
return () -> customNeo4jHealthIndicators.keySet()
.stream()
.map(HealthContributorNameFactory.INSTANCE)
.forEach(healthContributorRegistry::unregisterContributor);
}
}
Neo4j 4.4+ 支持模拟不同用户——我该如何使用它们?
用户模拟在大型多租户环境中尤其有趣,其中一个租户物理连接(或技术上) 用户可以冒充多个租户。根据你的配置,这会大大减少所需的物理驱动实例数量。
该功能需要服务器端运行 Neo4j Enterprise 4.4+,客户端安装 4.4+ 驱动(org.neo4j.driver:neo4j-java-driver:4.4.0甚至更高)。
无论是命令式还是响应式,你都需要提供UserSelectionProvider分别是aReactiveUserSelectionProvider.
同样的实例需要传递给Neo4客户端和Neo4jTransactionManager分别是它们的反应型变体。
import org.springframework.data.neo4j.core.UserSelection;
import org.springframework.data.neo4j.core.UserSelectionProvider;
public class CustomConfig {
@Bean
public UserSelectionProvider getUserSelectionProvider() {
return () -> UserSelection.impersonate("someUser");
}
}
在典型的 Spring Boot 场景中,这一功能需要更多的工作,因为 Boot 也支持没有该功能的 SDN 版本。 所以,给定用户选择提供者豆中的豆子,你需要完全自定义客户端和事务管理器:
import org.neo4j.driver.Driver;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.data.neo4j.core.UserSelectionProvider;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
public class CustomConfig {
@Bean
public Neo4jClient neo4jClient(
Driver driver,
DatabaseSelectionProvider databaseSelectionProvider,
UserSelectionProvider userSelectionProvider
) {
return Neo4jClient.with(driver)
.withDatabaseSelectionProvider(databaseSelectionProvider)
.withUserSelectionProvider(userSelectionProvider)
.build();
}
@Bean
public PlatformTransactionManager transactionManager(
Driver driver,
DatabaseSelectionProvider databaseSelectionProvider,
UserSelectionProvider userSelectionProvider
) {
return Neo4jTransactionManager
.with(driver)
.withDatabaseSelectionProvider(databaseSelectionProvider)
.withUserSelectionProvider(userSelectionProvider)
.build();
}
}
使用 Spring Data Neo4j 的 Neo4j 集群实例
以下问题适用于 Neo4j AuraDB 以及 Neo4j 本地集群实例。
我需要特定的配置,才能让交易与 Neo4j Causal Cluster 无缝衔接吗?
不,你不需要。 SDN内部使用Neo4j Causal Cluster书签,无需你这边的任何配置。 同一线程或同一响应式流中相连的事务,可以读取它们之前更改过的值,这也是你所期望的。
在 Neo4j 集群中使用只读事务重要吗?
是的,它是。 Neo4j 集群架构是一种因果集群架构,区分了主服务器和次服务器。 主服务器要么是单实例,要么是核心实例。它们都可以响应读写作。 写作从核心实例传播到集群内部的副本或更一般的跟随者。 那些粉丝是次级服务器。 二级服务器不负责写入作。
在标准部署场景中,集群内会有一些核心实例和许多读副本。 因此,将作或查询标记为只读非常重要,以便以领导者能够 从不被淹没,查询尽可能传播以读取副本。
无论是 Spring Data Neo4j 还是底层的 Java 驱动,都不支持 Cypher 解析,这两个构建模块都假设 默认写入作。这一决定是为了支持所有运营,开箱即用。如果有什么事情发生在 栈默认只读,栈可能会向读副本发送写查询但失败 关于执行他们的决定。
都findById,查找所有ById,findAll(查找所有预定义的存在方法默认标记为只读。 |
以下是一些选项的描述:
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
interface PersonRepository extends Neo4jRepository<Person, Long> {
}
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.transaction.annotation.Transactional;
interface PersonRepository extends Neo4jRepository<Person, Long> {
@Transactional(readOnly = true)
Person findOneByName(String name); (1)
@Transactional(readOnly = true)
@Query("""
CALL apoc.search.nodeAll('{Person: "name",Movie: ["title","tagline"]}','contains','her')
YIELD node AS n RETURN n""")
Person findByCustomQuery(); (2)
}
| 1 | 为什么这个只读不是默认的?虽然它适用于上述派生的查找器(我们实际上知道它是只读的),
我们经常看到用户添加自定义@Query并通过合并构建
当然,这就是一个写作。 |
| 2 | 自定义程序可以做各种各样的作,目前我们无法在这里检查只读还是写。 |
import java.util.Optional;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.transaction.annotation.Transactional;
interface PersonRepository extends Neo4jRepository<Person, Long> {
}
interface MovieRepository extends Neo4jRepository<Movie, Long> {
List<Movie> findByLikedByPersonName(String name);
}
public class PersonService {
private final PersonRepository personRepository;
private final MovieRepository movieRepository;
public PersonService(PersonRepository personRepository,
MovieRepository movieRepository) {
this.personRepository = personRepository;
this.movieRepository = movieRepository;
}
@Transactional(readOnly = true)
public Optional<PersonDetails> getPerson(Long id) { (1)
return this.repository.findById(id)
.map(person -> {
var movies = this.movieRepository
.findByLikedByPersonName(person.getName());
return new PersonDetails(person, movies);
});
}
}
| 1 | 这里,多个仓库的多次调用被包裹在一个只读事务中。 |
交易模板在私有服务方式和/或与 Neo4j 客户端合作时import java.util.Collection;
import org.neo4j.driver.types.Node;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
public class PersonService {
private final TransactionTemplate readOnlyTx;
private final Neo4jClient neo4jClient;
public PersonService(PlatformTransactionManager transactionManager, Neo4jClient neo4jClient) {
this.readOnlyTx = new TransactionTemplate(transactionManager, (1)
new TransactionDefinition() {
@Override public boolean isReadOnly() {
return true;
}
}
);
this.neo4jClient = neo4jClient;
}
void internalOperation() { (2)
Collection<Node> nodes = this.readOnlyTx.execute(state -> {
return neo4jClient.query("MATCH (n) RETURN n").fetchAs(Node.class) (3)
.mappedBy((types, record) -> record.get(0).asNode())
.all();
});
}
}
| 1 | 创建一个交易模板具备你需要的特性。
当然,这也可以是全球的。 |
| 2 | 使用交易模板的第一个原因:声明式交易不起作用
在包私有方法或私有方法中,也不包括内层方法调用(想象另一种方法
在这项服务中,召唤内部运营)由于其本质是通过Aspects实现的
以及代理。 |
| 3 | 这Neo4jClient是SDN提供的固定效用。它无法被注释,但可以与 Spring 集成。
所以它能给你用纯驱动做的所有作,没有自动映射,并且
交易。它还遵守声明式交易。 |
我能获取最新的书签或做种交易管理器吗?
正如书签管理中简要提到的,书签方面无需配置任何内容。
不过,检索SDN事务系统从数据库接收到的最新书签可能会很有用。
你可以添加一个@Bean喜欢书签捕捉具体做法:
import java.util.Set;
import org.neo4j.driver.Bookmark;
import org.springframework.context.ApplicationListener;
public final class BookmarkCapture
implements ApplicationListener<Neo4jBookmarksUpdatedEvent> {
@Override
public void onApplicationEvent(Neo4jBookmarksUpdatedEvent event) {
// We make sure that this event is called only once,
// the thread safe application of those bookmarks is up to your system.
Set<Bookmark> latestBookmarks = event.getBookmarks();
}
}
为了做种事务系统,需要一个类似以下内容的定制事务管理器:
import java.util.Set;
import java.util.function.Supplier;
import org.neo4j.driver.Bookmark;
import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class BookmarkSeedingConfig {
@Bean
public PlatformTransactionManager transactionManager(
Driver driver, DatabaseSelectionProvider databaseNameProvider) { (1)
Supplier<Set<Bookmark>> bookmarkSupplier = () -> { (2)
Bookmark a = null;
Bookmark b = null;
return Set.of(a, b);
};
Neo4jBookmarkManager bookmarkManager =
Neo4jBookmarkManager.create(bookmarkSupplier); (3)
return new Neo4jTransactionManager(
driver, databaseNameProvider, bookmarkManager); (4)
}
}
| 1 | 让Spring注射那些 |
| 2 | 这个提供商可以是任何持有你想导入系统的最新书签的机构 |
| 3 | 用它创建书签管理器 |
| 4 | 把它传递给定制的交易管理器 |
| 除非你的应用需要访问或提供,否则无需做上述任何作 这些数据。如果有疑问,就两者都别做。 |
我可以禁用书签管理吗?
我们提供了一个Noop书签管理器,实际上禁用了书签管理。
| 请自行承担风险使用这个书签管理器,它会通过删除所有书签管理来有效禁用所有书签管理 书签却从不提供。在一个集群中,你很可能会遇到陈旧的阅读体验。在单一车中 比如说,这很可能不会有任何区别。 |
+ 在群体中,这种做法才是合理的,前提是你能容忍陈旧的阅读,且没有风险 覆盖旧数据。
以下配置创建了一个“noop”版本的书签管理器,该版本将从相关类中获取。
import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
@Configuration
public class BookmarksDisabledConfig {
@Bean
public Neo4jBookmarkManager neo4jBookmarkManager() {
return Neo4jBookmarkManager.noop();
}
}
你可以配置Neo4jTransactionManager/Neo4jClient和ReactiveNeo4jTransactionManager/ReactiveNeo4jClient单独使用,但我们建议只有在你已经为特定数据库选择需求配置后才这样做。
我需要使用Neo4j专属的注释吗?
不。 您可以自由使用以下等效的Spring Data注释:
| SDN专用注释 | Spring Data 通用注释 | 目的 | 差异 |
|---|---|---|---|
|
|
将注释属性标记为唯一ID。 |
特定注释没有额外功能。 |
|
|
将该类标记为持久实体。 |
|
我该如何使用分配的ID?
直接用@Id没有@GeneratedValue通过构造参数、设定器或枯萎器填充你的ID属性。
关于寻找好身份证的一些通用建议,可以参考这篇博客文章。
我该如何使用外部生成的ID?
我们提供接口org.springframework.data.neo4j.core.schema.IdGenerator.
你可以用任何你想要的方式实现,并这样配置你的实现:
@Node
public class ThingWithGeneratedId {
@Id @GeneratedValue(TestSequenceGenerator.class)
private String theId;
}
如果你以一个类别的名义传给@GeneratedValue,该类必须有一个无 args 的默认构造子。
不过你也可以用字符串:
@Node
public class ThingWithIdGeneratedByBean {
@Id @GeneratedValue(generatorRef = "idGeneratingBean")
private String theId;
}
说到这里,idGeneratingBean在春季语境中指的是豆子。
这对序列生成可能有用。
| ID的非最终字段不要求设置。 |
我需要为每个领域类别创建仓库吗?
不。
看看SDN的构建模块,找到Neo4j模板或者ReactiveNeo4j模板.
这些模板了解你的领域,提供所有必要的基本CRUD方法,用于检索、写入和计数实体。
这是我们官方电影的示例,使用祈使式模板:
import java.util.Collections;
import java.util.Optional;
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.data.neo4j.core.Neo4jTemplate;
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
import org.springframework.data.neo4j.documentation.domain.PersonEntity;
import org.springframework.data.neo4j.documentation.domain.Roles;
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
import static org.assertj.core.api.Assertions.assertThat;
@Neo4jIntegrationTest
@DataNeo4jTest
public class TemplateExampleTest {
@Test
void shouldSaveAndReadEntities(@Autowired Neo4jTemplate neo4jTemplate) {
MovieEntity movie = new MovieEntity("The Love Bug",
"A movie that follows the adventures of Herbie, Herbie's driver, "
+ "Jim Douglas (Dean Jones), and Jim's love interest, " + "Carole Bennett (Michele Lee)");
Roles roles1 = new Roles(new PersonEntity(1931, "Dean Jones"), Collections.singletonList("Didi"));
Roles roles2 = new Roles(new PersonEntity(1942, "Michele Lee"), Collections.singletonList("Michi"));
movie.getActorsAndRoles().add(roles1);
movie.getActorsAndRoles().add(roles2);
MovieEntity result = neo4jTemplate.save(movie);
assertThat(result.getActorsAndRoles()).allSatisfy(relationship -> assertThat(relationship.getId()).isNotNull());
Optional<PersonEntity> person = neo4jTemplate.findById("Dean Jones", PersonEntity.class);
assertThat(person).map(PersonEntity::getBorn).hasValue(1931);
assertThat(neo4jTemplate.count(PersonEntity.class)).isEqualTo(2L);
}
}
以下是响应式版本,省略了简洁的开场介绍:
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.testcontainers.neo4j.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
import org.springframework.data.neo4j.documentation.domain.PersonEntity;
import org.springframework.data.neo4j.documentation.domain.Roles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
@Testcontainers
@DataNeo4jTest
class ReactiveTemplateExampleTest {
@Container
private static Neo4jContainer neo4jContainer = new Neo4jContainer("neo4j:5");
@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
registry.add("org.neo4j.driver.uri", neo4jContainer::getBoltUrl);
registry.add("org.neo4j.driver.authentication.username", () -> "neo4j");
registry.add("org.neo4j.driver.authentication.password", neo4jContainer::getAdminPassword);
}
@Test
void shouldSaveAndReadEntities(@Autowired ReactiveNeo4jTemplate neo4jTemplate) {
MovieEntity movie = new MovieEntity("The Love Bug",
"A movie that follows the adventures of Herbie, Herbie's driver, Jim Douglas (Dean Jones), and Jim's love interest, Carole Bennett (Michele Lee)");
Roles role1 = new Roles(new PersonEntity(1931, "Dean Jones"), Collections.singletonList("Didi"));
Roles role2 = new Roles(new PersonEntity(1942, "Michele Lee"), Collections.singletonList("Michi"));
movie.getActorsAndRoles().add(role1);
movie.getActorsAndRoles().add(role2);
StepVerifier.create(neo4jTemplate.save(movie)).expectNextCount(1L).verifyComplete();
StepVerifier.create(neo4jTemplate.findById("Dean Jones", PersonEntity.class).map(PersonEntity::getBorn))
.expectNext(1931)
.verifyComplete();
StepVerifier.create(neo4jTemplate.count(PersonEntity.class)).expectNext(2L).verifyComplete();
}
}
请注意,这两个例子都包含@DataNeo4jTest出自《春季靴》。
我该如何使用带有存储库方法返回的自定义查询Page<T>或切<T>?
虽然你不必提供其他任何东西,但可页面作为派生查找方法中的参数
返回 aPage<T>或者切<T>你必须准备自定义查询来处理可分页内容。页面和切片功能会让你了解所需的内容。
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
public interface MyPersonRepository extends Neo4jRepository<Person, Long> {
Page<Person> findByName(String name, Pageable pageable); (1)
@Query(""
+ "MATCH (n:Person) WHERE n.name = $name RETURN n "
+ "ORDER BY n.name ASC SKIP $skip LIMIT $limit"
)
Slice<Person> findSliceByName(String name, Pageable pageable); (2)
@Query(
value = ""
+ "MATCH (n:Person) WHERE n.name = $name RETURN n "
+ "ORDER BY n.name ASC SKIP $skip LIMIT $limit",
countQuery = ""
+ "MATCH (n:Person) WHERE n.name = $name RETURN count(n)"
)
Page<Person> findPageByName(String name, Pageable pageable); (3)
}
| 1 | 一种派生的查找方法,为你创建一个查询。
它处理的是可页面给你的。 你应该使用排序分页功能。 |
| 2 | 该方法使用@Query定义一个自定义查询。它返回切<人>. 切片不知道总页数,因此自定义查询不需要专门的计数查询。SDN 会通知你它估算了下一个切片。Cypher 模板必须同时检测到这两个切片$skip和$limit密码参数。如果你省略了它们,SDN会发出警告。它很可能不会符合你的预期。另外,还有可页面应该是未排序的,你应该提供一个稳定的排序。我们不会使用可分页页面上的排序信息。 |
| 3 | 该方法返回页面。页面知道具体的总页数。因此,你必须指定一个额外的计数查询。第二种方法的所有其他限制都适用。 |
我可以绘制命名路径吗?
一系列连接的节点和关系在 Neo4j 中称为“路径”。Cypher 允许使用标识符命名路径,示例如下:
p = (a)-[*3..5]->(b)
或者像臭名昭著的电影图中那样,包含以下路径(在这种情况下,是两个演员之间的最短路径之一):
MATCH p=shortestPath((bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"}))
RETURN p
它长这样:
我们发现有3个节点被标记为顶点以及2个标记为节点的节点电影. 两者都可以通过自定义查询映射。假设两者都有节点实体顶点和电影以及演员维护关系:
@Node
public final class Person {
@Id @GeneratedValue
private final Long id;
private final String name;
private Integer born;
@Relationship("REVIEWED")
private List<Movie> reviewed = new ArrayList<>();
}
@RelationshipProperties
public final class Actor {
@RelationshipId
private final Long id;
@TargetNode
private final Person person;
private final List<String> roles;
}
@Node
public final class Movie {
@Id
private final String title;
@Property("tagline")
private final String description;
@Relationship(value = "ACTED_IN", direction = Direction.INCOMING)
private final List<Actor> actors;
}
使用如图中所示的查询时,对于类型为 的领域类顶点喜欢这个
interface PeopleRepository extends Neo4jRepository<Person, Long> {
@Query(""
+ "MATCH p=shortestPath((bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n"
+ "RETURN p"
)
List<Person> findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2);
}
它会从路径上检索所有人并绘制他们的地图。如果路径上有像这样的关系类型检讨这些也存在于域中,这些将根据路径填充。
| 使用基于路径查询的节点水合保存数据时,要特别注意。如果不是所有关系都水合,数据将丢失。 |
反过来也行。同样的查询也可以用于电影实体。 然后它只会填充电影。以下列表展示了如何实现这一点,以及如何通过额外数据丰富查询路径上找不到的数据。这些数据用于正确填充缺失的关系(在这种情况下,是所有actor)
interface MovieRepository extends Neo4jRepository<Movie, String> {
@Query(""
+ "MATCH p=shortestPath(\n"
+ "(bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n"
+ "WITH p, [n IN nodes(p) WHERE n:Movie] AS x\n"
+ "UNWIND x AS m\n"
+ "MATCH (m) <-[r:DIRECTED]-(d:Person)\n"
+ "RETURN p, collect(r), collect(d)"
)
List<Movie> findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2);
}
查询返回路径以及所有收集到的关系和相关节点,使电影实体完全水合。
路径映射同样适用于单一条路径,也适用于多条路径记录(这些记录由all最短路径功能。)
| 命名路径可以高效地填充和返回不仅仅是根节点,详见 appendix/custom-queries.adoc#custom-query.paths。 |
是@Query唯一能用自定义查询的方法?
不@Query并不是运行自定义查询的唯一方式。在自定义查询完全填满你的领域的情况下,注释是很方便的。请记住,SDN假设你的映射域模型是真实的。这意味着如果你使用自定义查询,通过以下方式@Query这只填充了部分模型,你可能会用同一个对象写回数据,最终会擦除或覆盖你查询时未考虑的数据。
所以,请使用仓库和声明式方法@Query在所有结果与你的领域模型相似的情况下,或者你确定自己没有用部分映射的模型来写命令。
有哪些替代方案?
-
投影可能已经足以塑造你在图上的观点:它们可以用来定义获取属性和相关实体的深度,以显式的方式:通过建模它们。
-
如果你的目标是只让查询的条件动态化,那可以看看
QuerydslPredicateExecutor尤其是我们自己的变体,比如CypherdslConditionExecutor. 这两个混合都允许在我们为你创建的完整查询中添加条件。因此,你将拥有完整且带有自定义条件的完整域。当然,你的条件必须与我们生成的内容兼容。点击这里查找根节点、相关节点等名称。 -
通过
CypherdslStatementExecutor或者ReactiveCypherdslStatementExecutor. Cypher-DSL 天生用于创建动态查询。归根结底,这还是SDN底层使用的。对应的 Mixins 既能与仓库的域类型本身合作,也能与投影(Mixins 用于添加投影)一起使用 条件则不然)。
如果你认为可以用部分动态查询或全动态查询配合投影来解决你的问题, 请现在回到关于春季数据Neo4j混合的章节。
否则,请先了解两点:自定义仓库片段,以及我们在SDN中提供的抽象层级。
为什么现在谈论自定义仓库片段?
-
你可能遇到更复杂的情况,需要多个动态查询,但这些查询仍然属于 概念上是在仓库中,而非服务层
-
你的自定义查询会返回一个图表形的结果,但与你的领域模型不太匹配 因此,自定义查询也应伴随自定义映射
-
你需要与驱动程序交互,比如批量加载,这些加载本不应该经过对象映射。
假设以下仓库声明,基本上是聚合一个基础仓库加上三个片段:
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
public interface MovieRepository
extends Neo4jRepository<MovieEntity, String>, DomainResults, NonDomainResults, LowlevelInteractions {
}
存储库扩展的附加接口(DomainResults,非域名结果和低层交互)
是解决上述所有关切的片段。
使用复杂、动态的自定义查询,但仍返回域类型
残片DomainResults声明了一种额外的方法查找电影最短路径:
import java.util.List;
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
import org.springframework.data.neo4j.documentation.domain.PersonEntity;
import org.springframework.transaction.annotation.Transactional;
interface DomainResults {
@Transactional(readOnly = true)
List<MovieEntity> findMoviesAlongShortestPath(PersonEntity from, PersonEntity to);
}
该方法被注释为@Transactional(仅读 = 真)以表明读者可以回答。
它不能通过 SDN 推导出来,需要自定义查询。
该自定义查询由该接口的唯一实现提供。
实现名称相同,后缀impl:
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.neo4j.cypherdsl.core.Cypher;
import org.springframework.data.neo4j.core.Neo4jTemplate;
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
import org.springframework.data.neo4j.documentation.domain.PersonEntity;
import static org.neo4j.cypherdsl.core.Cypher.anyNode;
import static org.neo4j.cypherdsl.core.Cypher.listWith;
import static org.neo4j.cypherdsl.core.Cypher.name;
import static org.neo4j.cypherdsl.core.Cypher.node;
import static org.neo4j.cypherdsl.core.Cypher.parameter;
class DomainResultsImpl implements DomainResults {
private final Neo4jTemplate neo4jTemplate; (1)
DomainResultsImpl(Neo4jTemplate neo4jTemplate) {
this.neo4jTemplate = neo4jTemplate;
}
@Override
public List<MovieEntity> findMoviesAlongShortestPath(PersonEntity from, PersonEntity to) {
var p1 = node("Person").withProperties("name", parameter("person1"));
var p2 = node("Person").withProperties("name", parameter("person2"));
var shortestPath = Cypher.shortestK(1).named("p").definedBy(p1.relationshipBetween(p2).unbounded());
var p = shortestPath.getRequiredSymbolicName();
var statement = Cypher.match(shortestPath)
.with(p, listWith(name("n")).in(Cypher.nodes(shortestPath))
.where(anyNode().named("n").hasLabels("Movie"))
.returning()
.as("mn"))
.unwind(name("mn"))
.as("m")
.with(p, name("m"))
.match(node("Person").named("d").relationshipTo(anyNode("m"), "DIRECTED").named("r"))
.returning(p, Cypher.collect(name("r")), Cypher.collect(name("d")))
.build();
Map<String, Object> parameters = new HashMap<>();
parameters.put("person1", from.getName());
parameters.put("person2", to.getName());
return this.neo4jTemplate.findAll(statement, parameters, MovieEntity.class); (2)
}
}
| 1 | 这Neo4j模板通过 的构造子在运行时中注入DomainResultsImpl(域名结果impl).没必要@Autowired. |
| 2 | Cypher-DSL 用于构建一个复杂的语句(与路径映射中所示的几乎相同)。 该语句可以直接传递到模板。 |
模板也有针对字符串查询的超载,所以你也可以把查询写成字符串。 这里重要的结论是:
-
模板“知道”你的域对象并据此映射它们
-
@Query不是定义自定义查询的唯一选项 -
它们可以通过多种方式生成
-
这
@Transactional注释被尊重
使用自定义查询和自定义映射
通常,自定义查询会显示自定义结果。
这些结果是否都映射为@Node?当然不是!很多时候,这些对象代表读取命令
和 都不打算用作写入命令。
SDN也不太可能无法或不愿用Cypher映射所有可能的部分。
不过,它确实提供了几个钩子来运行自己的映射:在Neo4jClient.
使用SDN的好处Neo4jClient在驱动杆上:
-
这
Neo4jClient与 Springs 事务管理集成 -
它有一个流畅的API用于绑定参数
-
它有一个流畅的API,既暴露了记录,也支持Neo4j类型的系统,方便你访问 你结果中的所有内容都用来执行映射
声明片段的过程与之前完全相同:
import java.util.Collection;
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
import org.springframework.transaction.annotation.Transactional;
interface NonDomainResults {
@Transactional(readOnly = true)
Collection<Result> findRelationsToMovie(MovieEntity movie); (1)
class Result {
(2)
public final String name;
public final String typeOfRelation;
Result(String name, String typeOfRelation) {
this.name = name;
this.typeOfRelation = typeOfRelation;
}
}
}
| 1 | 这是一个虚构的非域结果。现实世界的查询结果可能会更复杂。 |
| 2 | 这个片段新增的方法。同样,该方法以 Spring 的注释@Transactional |
如果没有该片段的实现,启动将失败,所以这里是:
import java.util.Collection;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
class NonDomainResultsImpl implements NonDomainResults {
private final Neo4jClient neo4jClient; (1)
NonDomainResultsImpl(Neo4jClient neo4jClient) {
this.neo4jClient = neo4jClient;
}
@Override
public Collection<Result> findRelationsToMovie(MovieEntity movie) {
return this.neo4jClient
.query("" + "MATCH (people:Person)-[relatedTo]-(:Movie {title: $title}) " + "RETURN people.name AS name, "
+ " Type(relatedTo) as typeOfRelation") (2)
.bind(movie.getTitle())
.to("title") (3)
.fetchAs(Result.class) (4)
.mappedBy((typeSystem, record) -> new Result(record.get("name").asString(),
record.get("typeOfRelation").asString())) (5)
.all(); (6)
}
}
| 1 | 这里我们使用Neo4jClient,由基础设施提供的。 |
| 2 | 客户端只在字符串中接收,但在渲染成字符串时仍可以使用密码DSL |
| 3 | 将一个单一的值绑定到一个命名参数上。还有一个超载功能,可以绑定整张参数映射 |
| 4 | 这正是你想要的结果类型 |
| 5 | 最后,还有由方法,暴露其中一枚记录对结果中的每个条目以及如有需要的驱动程序类型系统。
这是你用来钩入自定义映射的API。 |
整个查询运行在 Spring 事务的上下文中,这里是只读事务。
低层次相互作用
有时你可能想从仓库批量加载,删除整个子图,或者以非常特定的方式交互 使用Neo4j Java驱动。这也是可能的。以下示例展示了具体方法:
interface LowlevelInteractions {
int deleteGraph();
}
import org.neo4j.driver.Driver;
import org.neo4j.driver.Session;
import org.neo4j.driver.summary.SummaryCounters;
class LowlevelInteractionsImpl implements LowlevelInteractions {
private final Driver driver; (1)
LowlevelInteractionsImpl(Driver driver) {
this.driver = driver;
}
@Override
public int deleteGraph() {
try (Session session = this.driver.session()) {
SummaryCounters counters = session.executeWrite(tx -> tx.run("MATCH (n) DETACH DELETE n").consume()) (2)
.counters();
return counters.nodesDeleted() + counters.relationshipsDeleted();
}
}
}
| 1 | 直接和Drivers沟通。和所有例子一样:没有必要@Autowired魔法。所有碎片
实际上,这些都可单独测试。 |
| 2 | 使用场景是虚构的。这里我们使用驱动程序管理的交易,删除整个图并返回 已删除的节点和关系 |
这种交互当然不会发生在Spring交易中,因为Drivers不知道Spring的存在。
综合来看,这个测试成功了:
@Test
void customRepositoryFragmentsShouldWork(@Autowired PersonRepository people, @Autowired MovieRepository movies) {
PersonEntity meg = people.findById("Meg Ryan").get();
PersonEntity kevin = people.findById("Kevin Bacon").get();
List<MovieEntity> moviesBetweenMegAndKevin = movies.findMoviesAlongShortestPath(meg, kevin);
assertThat(moviesBetweenMegAndKevin).isNotEmpty();
Collection<NonDomainResults.Result> relatedPeople = movies
.findRelationsToMovie(moviesBetweenMegAndKevin.get(0));
assertThat(relatedPeople).isNotEmpty();
assertThat(movies.deleteGraph()).isGreaterThan(0);
assertThat(movies.findAll()).isEmpty();
assertThat(people.findAll()).isEmpty();
}
最后一点:Spring Data Neo4j 会自动接收这三种接口和实现。 无需进一步配置。 此外,同一整体仓库也可以只用一个额外的片段(定义所有三种方法的接口)创建 以及一个实现。实现时会注入所有三个抽象(模板、客户端和驱动程序)。
当然,所有这些都适用于响应式仓库。
他们会与ReactiveNeo4j模板和ReactiveNeo4jClient以及驱动程序提供的响应式会话。
如果你对所有仓库都有周期性方法,可以替换默认仓库实现。
我如何使用自定义的 Spring Data Neo4j 基础仓库?
基本上,这和共享的 Spring Data Commons 文档中 Customize the Base Repository 中 Spring Data JPA 的做法是一样的。 只说在我们的情况下,你会从
public static class MyRepositoryImpl<T, ID> extends SimpleNeo4jRepository<T, ID> {
MyRepositoryImpl(Neo4jOperations neo4jOperations, Neo4jEntityInformation<T, ID> entityInformation) {
super(neo4jOperations, entityInformation); (1)
}
@Override
public List<T> findAll() {
throw new UnsupportedOperationException("This implementation does not support `findAll`");
}
}
| 1 | 该签名是基类要求的。拿Neo4j运营(实际的规范Neo4j模板)
以及实体信息,并在需要时将其存储在属性上。 |
在此示例中,我们禁止使用findAll(查找所有方法。
你可以添加获取深度的方法,并基于深度运行自定义查询。
其中一种方法见 DomainResults 片段。
要启用该基础仓库,适用于所有声明仓库,需启用具备以下功能的 Neo4j 仓库:@EnableNeo4jRepositories(repositoryBaseClass = MyRepositoryImpl.class).
我该如何审计实体?
支持所有 Spring Data 注释。 这些是
-
org.springframework.data.annotation.CreatedBy -
org.springframework.data.annotation.CreatedDate -
org.springframework.data.annotation.LastModifiedBy -
org.springframework.data.annotation.LastModifiedDate
审计能让你大致了解如何在Spring Data Commons的更大背景下使用审计。 以下列表展示了 Spring Data Neo4j 提供的所有配置选项:
@Configuration
@EnableNeo4jAuditing(modifyOnCreate = false, (1)
auditorAwareRef = "auditorProvider", (2)
dateTimeProviderRef = "fixedDateTimeProvider" (3)
)
class AuditingConfig {
@Bean
AuditorAware<String> auditorProvider() {
return () -> Optional.of("A user");
}
@Bean
DateTimeProvider fixedDateTimeProvider() {
return () -> Optional.of(AuditingITBase.DEFAULT_CREATION_AND_MODIFICATION_DATE);
}
}
| 1 | 如果你希望修改数据在创建时写入,也设置为 true。 |
| 2 | 使用该属性指定提供审计员的豆子名称(即用户名) |
| 3 | 使用该属性指定提供当前日期的豆子名称。在这种情况下 由于上述配置是我们测试的一部分,因此使用固定日期 |
响应式版本基本相同,只是审计员感知的豆子是某种类型ReactiveAuditorAware,
因此,审计员的检索是反应流的一部分。
除了这些审计机制外,你还可以添加尽可能多的 Beans 实现BeforeBindCallback<T>或ReactiveBeforeBindCallback<T>要看上下文。这些豆子会被Spring Data Neo4j接收并按顺序调用(以防他们实现命令或
注释为@Order)就在实体被持久化之前。
他们可以修改实体,也可以退回全新的实体。 以下示例在实体被持久化前,增加了一个回调到上下文,该回调会改变一个属性:
import java.util.UUID;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.mapping.callback.AfterConvertCallback;
import org.springframework.data.neo4j.core.mapping.callback.BeforeBindCallback;
import org.springframework.data.neo4j.integration.shared.common.ThingWithAssignedId;
@Configuration
class CallbacksConfig {
@Bean
BeforeBindCallback<ThingWithAssignedId> nameChanger() {
return entity -> {
ThingWithAssignedId updatedThing = new ThingWithAssignedId(entity.getTheId(),
entity.getName() + " (Edited)");
return updatedThing;
};
}
@Bean
AfterConvertCallback<ThingWithAssignedId> randomValueAssigner() {
return (entity, definition, source) -> {
entity.setRandomValue(UUID.randomUUID().toString());
return entity;
};
}
}
无需额外配置。
我该如何使用“以示例查找”?
“以示例查找”是SDN中的一项新功能。
你实例化一个实体或使用已有的实体。
在这个实例中,你创建了一个org.springframework.data.domain.示例.
如果你的仓库扩展了org.springframework.data.neo4j.repository.Neo4jRepository或org.springframework.data.neo4j.repository.ReactiveNeo4jRepository,你可以立即使用可用的查找者方法采用示例,如 findByExample 所示。
Example<MovieEntity> movieExample = Example.of(new MovieEntity("The Matrix", null));
Flux<MovieEntity> movies = this.movieRepository.findAll(movieExample);
movieExample = Example.of(
new MovieEntity("Matrix", null),
ExampleMatcher
.matchingAny()
.withMatcher(
"title",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING)
)
);
movies = this.movieRepository.findAll(movieExample);
你也可以否定单个属性。这将增加适当的情况不运算,从而将 变为 。
支持所有标量数据类型和所有字符串运算符:=<>
Example<MovieEntity> movieExample = Example.of(
new MovieEntity("Matrix", null),
ExampleMatcher
.matchingAny()
.withMatcher(
"title",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING)
)
.withTransformer("title", Neo4jPropertyValueTransformers.notMatching())
);
Flux<MovieEntity> allMoviesThatNotContainMatrix = this.movieRepository.findAll(movieExample);
我需要 Spring Boot 才能使用 Spring Data Neo4j 吗?
不,你不需要。 虽然通过 Spring Boot 自动配置许多 Spring Aspect 可以省去很多手动繁琐的作,也是设置新 Spring 项目的推荐方法,但你其实不必非得用这个。
上述解需满足以下依赖关系:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>8.1.0-SNAPSHOT</version>
</dependency>
Gradle设置的坐标是一样的。
要选择不同的数据库——无论是静态还是动态——你可以添加一个类型的 Bean数据库选择提供者正如 Neo4j 4 中解释的,支持多个数据库——我该如何使用它们?
对于被动情景,我们提供ReactiveDatabaseSelectionProvider.
在没有 Spring Boot 的 Spring 上下文中使用 Spring Data Neo4j
我们提供两个抽象配置类,帮助您导入所需的豆子:org.springframework.data.neo4j.config.AbstractNeo4jConfig用于命令式数据库访问和org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig对于响应式版本。
它们本来就是用来搭配的@EnableNeo4jRepositories和@EnableReactiveNeo4jRepositories分别。
请参见“启用 Spring Data Neo4j 基础设施以获取命令式数据库访问”和“启用 Spring Data Neo4j 基础设施用于响应式数据库访问”示例使用法。
这两个职业都需要你覆盖驱动程序()你应该创建驱动程序。
要获得 Neo4j 客户端的命令式版本,包含命令式仓库的模板和支持,可以使用类似的方案,如图所示:
import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
@Configuration
@EnableNeo4jRepositories
@EnableTransactionManagement
class MyConfiguration extends AbstractNeo4jConfig {
@Override @Bean
public Driver driver() { (1)
return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret"));
}
@Override
protected Collection<String> getMappingBasePackages() {
return Collections.singletonList(Person.class.getPackage().getName());
}
@Override @Bean (2)
protected DatabaseSelectionProvider databaseSelectionProvider() {
return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider("yourDatabase");
}
}
| 1 | 驱动程序豆是必需的。 |
| 2 | 这会静态选择一个名为你的数据库且为可选。 |
以下列表提供了被动式Neo4j客户端和模板,支持被动式事务管理,并发现与Neo4j相关的仓库:
import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableReactiveNeo4jRepositories
@EnableTransactionManagement
class MyConfiguration extends AbstractReactiveNeo4jConfig {
@Bean
@Override
public Driver driver() {
return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret"));
}
@Override
protected Collection<String> getMappingBasePackages() {
return Collections.singletonList(Person.class.getPackage().getName());
}
}
在CDI 2.0环境中使用 Spring Data Neo4j
为了您的方便,我们提供CDI延期Neo4jCdiExtension.
当运行在兼容的 CDI 2.0 容器中时,它会自动注册并通过 Java 的服务加载程序 SPI 加载。
你唯一需要带入应用的是带注释的类型,它能生成 Neo4j Java 驱动程序:
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
public class Neo4jConfig {
@Produces @ApplicationScoped
public Driver driver() { (1)
return GraphDatabase
.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret"));
}
public void close(@Disposes Driver driver) {
driver.close();
}
@Produces @Singleton
public DatabaseSelectionProvider getDatabaseSelectionProvider() { (2)
return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider("yourDatabase");
}
}
| 1 | 与普通 Spring 在 Enabling Spring Data Neo4j 基础设施中用于必然访问数据库时相同,但标注了相应的 CDI 基础设施。 |
| 2 | 这是可选的。然而,如果你使用自定义数据库选择提供商,你必须不符合该“豆子”的资格。 |
如果你是在 SE 容器中运行——比如 Weld 提供的那种,你可以这样启用扩展:
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import org.springframework.data.neo4j.config.Neo4jCdiExtension;
public class SomeClass {
void someMethod() {
try (SeContainer container = SeContainerInitializer.newInstance()
.disableDiscovery()
.addExtensions(Neo4jCdiExtension.class)
.addBeanClasses(YourDriverFactory.class)
.addPackages(Package.getPackage("your.domain.package"))
.initialize()
) {
SomeRepository someRepository = container.select(SomeRepository.class).get();
}
}
}