FAQ
How does SDN relate to Neo4j-OGM?
Neo4j-OGM 是一个对象图形映射库,Spring Data Neo4j 的早期版本主要使用它作为其后端,用于将节点和关系映射到域对象。当前的 SDN does not need 和 does not support Neo4j-OGM.SDN 专门使用 Spring Data 的映射上下文来扫描类和构建元模型。
虽然这样将 SDN 固定在了 Spring 生态系统中,但是它有几个优势,其中包括更小的 CPU 和内存使用量方面的占用空间,特别是 Spring 的映射上下文中所有功能。
Why should I use SDN in favor of SDN+OGM
SDN 有 SDN+OGM 中不存在的几个功能,尤其是
-
对 Spring 的反应式故事提供完全支持,包括反应式事务
-
对 Query By Example 提供完全支持
-
对完全不可变实体提供完全支持
-
支持派生查找器方法的所有修饰符和变体,包括空间查询
Does SDN support embedded Neo4j?
嵌入式 Neo4j 具有多个方面:
Does SDN interact directly with an embedded instance?
否。嵌入式数据库通常由 org.neo4j.graphdb.GraphDatabaseService
的实例表示,并且没有现成的 Bolt 连接器。
然而,SDN 可以与 Neo4j 的测试框架很好地配合使用,该测试框架专门旨在作为真实数据库的直接替代品。通过 the Spring Boot starter for the driver 实现了对 Neo4j 3.5、4.x 和 5.x 测试框架的支持。查看对应的模块 org.neo4j.driver:neo4j-java-driver-test-harness-spring-boot-autoconfigure
。
Which Neo4j Java Driver can be used and how?
SDN 依赖于 Neo4j Java 驱动程序。每个 SDN 版本都使用在发布时与最新 Neo4j 兼容的 Neo4j Java 驱动程序版本。虽然 Neo4j Java 驱动程序的修补程序版本通常可以直接代换,但是,SDN 确保即使是较低版本也可以互换,因为它会在需要时检查是否存在方法或接口更改。
因此,你可以将任何 4.x Neo4j Java 驱动程序与任何 SDN 6.x 版本一起使用,并将任何 5.x Neo4j 驱动程序与任何 SDN 7.x 版本一起使用。
With Spring Boot
现在,Spring boot 部署是基于 Spring Data 的应用程序最可能的部署。请使用 Spring Boots 依赖关系管理来更改驱动程序版本,如下所示:
<properties>
<neo4j-java-driver.version>5.4.0</neo4j-java-driver.version>
</properties>
或
neo4j-java-driver.version = 5.4.0
Without Spring Boot
如果没有 Spring Boot,你只需手动声明依赖关系。对于 Maven,我们建议使用 <dependencyManagement />
部分,如下所示:
<dependencyManagement> <dependency> <groupId>org.neo4j.driver</groupId> <artifactId>neo4j-java-driver</artifactId> <version>5.4.0</version> </dependency> </dependencyManagement>
Neo4j 4 supports multiple databases - How can I use them?
您可以静态配置数据库名称或运行您自己的数据库名称提供程序。请记住,SDN 不会为您创建数据库。您可以借助 migrations tool 或当然也可以直接使用简单脚本来执行此操作。
Statically configured
在你的 Spring Boot 配置中配置要使用的数据库名称,如下所示(对于基于 YML 或环境的配置,当然也适用相同的属性,只要应用 Spring Boot 的约定即可):
spring.data.neo4j.database = yourDatabase
有了此配置,SDN 存储库的所有实例(响应式的和命令式的)以及 ReactiveNeo4jTemplate
和 Neo4jTemplate
生成的所有查询都将在数据库 yourDatabase
中执行。
Dynamically configured
根据你的 Spring 应用程序类型提供类型为 Neo4jDatabaseNameProvider
或 ReactiveDatabaseSelectionProvider
的 bean。
例如,该 bean 可以使用 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;
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/Neo4jConfig.java[]
小心不要将从一个数据库检索的实体与另一个数据库混淆。数据库名称对每个新事务都是必需的,因此在调用过程中更改数据库名称时,您最终获取的实体可能比预期少或多。或更糟糕的是,您不可避免地会将错误的实体存储在错误的数据库中。 |
The Spring Boot Neo4j health indicator targets the default database, how can I change that?
Spring Boot 同时提供命令式和响应式 Neo4j health indicators.。两种变体都能够检测应用程序上下文中 org.neo4j.driver.Driver
的多重 bean,并为每个实例提供对整体运行状况的贡献。不过,Neo4j 驱动程序连接到服务器,而不是该服务器内的特定数据库。Spring Boot 能够在没有 Spring Data Neo4j 的情况下配置驱动程序,并且由于有关使用哪个数据库的信息与 Spring Data Neo4j 绑定,因此内置运行状况指示器无法获取此信息。
这在许多部署场景中很可能不是问题。但是,如果已配置的数据库用户没有至少对默认数据库的访问权限,则运行状况检查将失败。
可以通过了解数据库选择的自定义 Neo4j 运行状况贡献者来缓解此问题。
Imperative variant
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 | 如果您有多个驱动程序和数据库选择提供程序,则需要为每个组合创建一个指示符 |
2 | 这可确保所有这些指示符都归入到 Neo4j 下,从而替换默认的 Neo4j 健康指示符 |
3 | 这可以防止各个提交者直接显示在健康端点中 |
Reactive variant
响应变体基本上是相同的,它使用响应类型和相应的响应基础设施类:
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+ supports impersonation of different users - How can I use them?
用户模拟在大型多租户设置中特别有用,其中一个物理连接(或技术)用户可以模拟多个租户。根据您的设置,这将显著减少所需的物理驱动程序实例数。
此功能要求服务器端有 Neo4j Enterprise 4.4+,客户端有 4.4+ 驱动程序(org.neo4j.driver:neo4j-java-driver:4.4.0
或更高版本)。
对于命令式和响应版本,您分别需要提供 UserSelectionProvider
或`ReactiveUserSelectionProvider`。需要将相同实例分别传递给 Neo4Client
和 Neo4jTransactionManager
或它们的响应变体。
在 [Bootless 命令式,bootless-imperative-configuration] 和 [响应,bootless-reactive-configuration] 配置中,您只需要提供该类型的 bean:
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 版本。因此,鉴于 [faq.impersonation.userselectionbean] 中的 bean,您需要完全自定义客户端和事务管理器:
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();
}
}
Using a Neo4j cluster instance from Spring Data Neo4j
以下问题适用于 Neo4j AuraDB 以及 Neo4j 本地群集实例。
Do I need specific configuration so that transactions work seamless with a Neo4j Causal Cluster?
不,您不需要。SDN 在内部使用 Neo4j 因果群集书签,而无需您进行任何配置。同一线程或相同响应流中的事务将能够按照您的预期读取它们之前更改的值。
Is it important to use read-only transactions for Neo4j cluster?
是的。Neo4j 群集架构是一种因果群集架构,它区分主服务器和辅助服务器。主服务器要么是单实例,要么是核心实例。它们都可以响应读写操作。写操作从核心实例传播到读取副本或更广泛地说是群集内的关注者。这些关注者是辅助服务器。辅助服务器不响应写操作。
在标准部署方案中,群集中将有一些核心实例和许多读取副本。因此,将操作或查询标记为只读非常重要,以便以这种方式扩展群集,使领导者永远不会过度,并且尽可能将查询传播到读取副本。
Spring Data Neo4j 和底层 Java 驱动程序都不会执行 Cypher 解析,并且这两个构建块在默认情况下都会假定写操作。此决策是为了开箱即用地支持所有操作。如果堆栈中的某个内容在默认情况下假定只读,则堆栈最终可能会将写查询发送到读取副本,并在执行它们时失败。
默认情况下,所有 |
下面介绍了一些选项:
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 并通过 MERGE 构造实现该自定义 @Query ,该 MERGE 构造当然是一个写操作。 |
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 | 在此,对多个存储库的多个调用都包装在一个单一的只读事务中。 |
TransactionTemplate
inside private service methods and / or with the Neo4j clientimport 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 | 使用您需要的特性创建 TransactionTemplate 实例。当然,这也可能是一个全局 Bean。 |
2 | 使用事务模板的原因之一:声明式事务不起作用在包私有或私有方法中,也不起作用在内部方法调用中(想象另一个调用 internalOperation 的 service 中的方法),这是因为它们本质上是用切面和代理实现的。 |
3 | Neo4jClient 是 SDN 提供的固定实用工具。它不能被注释,但它与 Spring 集成。因此,它可以满足您对纯驱动程序所做的一切需求,而无需自动映射和事务。它也遵守声明式事务。 |
Can I retrieve the latest Bookmarks or seed the transaction manager?
正如在 Bookmark Management中简要提到的,无需对书签进行任何配置。但是,检索 SDN 事务系统从数据库接收到的最新书签可能很有用。您可以添加一个 `BookmarkCapture`之类的 `@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 | Let Spring inject those |
2 | 此供应商可以是任何持有您希望带入系统的最新书签的人员 |
3 | 使用它创建书签管理器 |
4 | 将其传递给自定义事务管理器 |
除非您的应用程序需要访问或提供这些数据,否则没有 no 这些需求。如果有疑问,不要执行任何操作。
Can I disable bookmark management?
我们提供了一个 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
对,但我们建议仅在为特定的数据库选择需求配置它们时才这样做。
Do I need to use Neo4j specific annotations?
否。可以自由使用以下等效的 Spring Data 注解:
SDN specific annotation | Spring Data common annotation | Purpose | Difference |
---|---|---|---|
|
|
将带注释的属性标记为唯一 ID。 |
特定注释没有其他功能。 |
|
|
将类标记为持久性实体。 |
|
How do I use assigned ids?
只需使用 @Id
(不使用 @GeneratedValue
),并通过构造函数参数、设置器或 _wither_填充 id 属性。参阅此 blog post,了解有关查找良好 id 的一些一般性说明。
How do I use externally generated ids?
我们提供了 org.springframework.data.neo4j.core.schema.IdGenerator
接口。以任何方式实现它,并按如下方式配置实现:
@Node
public class ThingWithGeneratedId {
@Id @GeneratedValue(TestSequenceGenerator.class)
private String theId;
}
如果将类名传递给 @GeneratedValue
,则此类必须具有无参数的默认构造函数。但是,也可以使用字符串:
@Node
public class ThingWithIdGeneratedByBean {
@Id @GeneratedValue(generatorRef = "idGeneratingBean")
private String theId;
}
通过此方法,idGeneratingBean
指向 Spring 上下文中的 Bean。这对于序列生成可能很有用。
对于 id,非 final 字段不需要 setter。 |
Do I have to create repositories for each domain class?
否。查看 SDN building blocks,然后找到 Neo4jTemplate`或 `ReactiveNeo4jTemplate
。
这些模板知道您的域,并提供检索、编写和计数实体所需的所有基本 CRUD 方法。
这是我们经典的 imperative 模板电影示例:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/spring_boot/TemplateExampleTest.java[]
@DataNeo4jTest
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/spring_boot/TemplateExampleTest.java[]
下面是 reactive 版本,为简洁起见,省略了设置:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/spring_boot/ReactiveTemplateExampleTest.java[]
@DataNeo4jTest
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/spring_boot/ReactiveTemplateExampleTest.java[]
请注意,两个示例都使用了 Spring Boot 中的 @DataNeo4jTest
。
How do I use custom queries with repository methods returning Page<T>
or Slice<T>
?
虽然不必提供除派生查找器方法上的 Pageable
(返回 Page<T>
或 Slice<T>
)以外的任何内容作为参数,但必须准备自定义查询来处理可分页性。[带有 page 和 slice 示例的自定义查询] 提供了所需内容的概览。
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 | 一个派生查找器方法,它为您创建一个查询。它为您处理 Pageable 。您应该使用已排序的分页。 |
2 | 这种方法使用 @Query 来定义一个自定义查询。它返回一个 Slice<Person> 。一个片段并不知道总页数,因此自定义查询不需要专门的计数查询。SDN 会通知你它估计的后续片段。Cypher 模板必须定位到两个 $skip 和 $limit Cypher 参数。如果你忽略它们,SDN 将发出警告。这可能会与你的期望不符。此外,Pageable 应该是未排序的,并且你应提供一个稳定的顺序。我们不会使用分页的排序信息。 |
3 | 此方法返回一页。一页知道确切的总页数。因此,你必须指定一个附加的计数查询。第二种方法中的所有其他限制都适用。 |
Can I map named paths?
一系列连接的节点和关系在 Neo4j 中称为“路径”。Cypher 允许使用标识符命名路径,例如:
p = (a)-[*3..5]->(b)
或者,如臭名昭著的 Movie 图表中包含的路径(在这种情况下,是两个演员之间的最短路径之一):
MATCH p=shortestPath((bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"}))
RETURN p
它看起来像这样:
我们找到了 3 个标记为 Vertex
的节点和 2 个标记为 Movie
的节点。两者都可以使用自定义查询进行映射。假设 Vertex
和 Movie
都具有节点实体,并且 Actor
处理关系:
@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;
}
当对 Vertex
类型的域类使用如 [bacon-distance] 中所示的查询时:
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);
}
它将检索路径中的所有人员并进行映射。如果路径上存在类似 REVIEWED
的关系类型,而且它们也存在于该域中,则这些关系类型将根据路径进行相应填充。
当您使用基于路径查询的数据节点保存数据时,请特别小心。如果未加载所有关系,数据将丢失。
反过来也行。同一查询可用于 Movie
实体。然后它将仅填充电影。以下清单显示了如何执行此操作,以及如何使用路径上未找到的附加数据丰富查询。该数据用于正确填充缺失的关系(在这种情况下,为所有演员)
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);
}
查询返回路径以及收集到的所有关系和相关节点,以便 Movie 实体完全 hydrated。
路径映射适用于单路径,也适用于多条路径记录(由 allShortestPath
函数返回)。
命名的路径可以有效地填充和返回不仅仅是根节点,请参阅 appendix/custom-queries.adoc#custom-query.paths。 |
Is @Query
the only way to use custom queries?
不,@Query
不是 执行自定义查询的唯一方法。当你的自定义查询完全填充你的域时,注解用起来很方便。请记住 SDN 假设你的映射域模型是真实。这意味着如果你通过仅部分填充模型的 @Query
使用自定义查询,那么你就有可能用同一个对象回写数据,最终会导致抹除或覆盖你不在查询中考虑的数据。
因此,请在所有结果形状与你的 domainmodel 相似的情况下或你确定不会将部分映射模型用于写命令的情况下,使用带有 @Query
的仓库和声明式方法。
有什么替代方案?
-
Projections 可能已经足够在图上塑造你的 view:它们可以用作明确定义获取属性和相关实体的深度的:通过对其建模。
-
如果你的目标只是让你的查询条件 dynamic,那就看看
QuerydslPredicateExecutor
,特别是我们自己的变体,CypherdslConditionExecutor
。这两个 mixins 均允许向我们为你创建的完全查询中添加条件。因此,你将使域得到完全填充以及自定义条件。当然,你的条件必须与我们生成的内容一起使用。查找根节点、相关节点等的名称 here。 -
通过
CypherdslStatementExecutor
或ReactiveCypherdslStatementExecutor
使用 Cypher-DSL。Cypher-DSL 预设用于创建动态查询。最终,这无论如何都是 SDN 在后台使用的东西。相应的混入既适用于存储库本身的域类型,也适用于预测(添加条件的混入并不支持的东西)。
如果您认为可以使用带投影的部分动态查询或完全动态查询来解决您的问题,请立即返回 about Spring Data Neo4j Mixins章节。
否则,请阅读这两篇文章: custom repository fragments中我们提供的 levels of abstractions。
为什么现在讨论自定义仓库片段?
-
你可能会遇到需要多个动态查询的更复杂的情况,但是这些查询在概念上仍然属于存储库而不是服务层。
-
你的自定义查询返回一个图形形状结果,它不太适合你的域模型,因此自定义查询还应附带一个自定义映射。
-
你需要与驱动程序交互,即对于不应该通过对象映射的批量加载。
假设以下仓库_声明_,它基本上聚合了一个基本仓库加上 3 个片段:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/repositories/custom_queries/MovieRepository.java[]
该存储库包含 Movies,如 the getting started section所示。
仓库从中扩展的其他接口(DomainResults
、NonDomainResults
和 LowlevelInteractions
)是解决以上所有问题片段。
Using complex, dynamic custom queries but still returning domain types
片段 DomainResults
声明了一个附加方法 findMoviesAlongShortestPath
:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/repositories/custom_queries/MovieRepository.java[]
此方法用 @Transactional(readOnly = true)
进行注解,以指示读者可以回答它。它不能由 SDN 推导出来,但需要一个自定义查询。此自定义查询由该接口的一个实现提供。实现具有相同名称,后缀为 Impl
:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/repositories/custom_queries/MovieRepository.java[]
1 | Neo4jTemplate 由运行时通过 DomainResultsImpl 的构造函数注入。不需要 @Autowired 。 |
2 | Cypher-DSL 用于构建复杂语句(与 path mapping 中的显示内容非常相似)。该语句可以直接传递到模板。 |
模板也针对基于字符串的查询进行重载,因此你也可以将查询写为字符串。这里重要的要点是:
-
该模板“了解”你的域对象并相应地对其进行映射。
-
@Query
并不是定义自定义查询的唯一选项。 -
它们可以通过多种方式生成。
-
@Transactional
注释是受尊敬的。
Using custom queries and custom mappings
很多时候,自定义查询表示自定义结果。所有这些结果都应映射为 @Node
吗?当然不!很多时候,这些对象表示读命令,并不意味着用作写命令。SDN 不能或不想映射 Cypher 中所有可能的东西也不是不可能的。但它确实提供了几个挂钩,可用于运行你自己的映射:在 Neo4jClient
上。使用 SDN Neo4jClient
比驱动程序的优点:
-
Neo4jClient
与 Springs 事务管理集成。 -
它具有可绑定参数的顺畅 API。
-
它有一个流畅的 API,同时公开记录和 Neo4j 类型系统,以便你可以在结果中访问所有内容来执行映射。
声明片段与之前完全一样:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/repositories/custom_queries/MovieRepository.java[]
1 | 这是一个虚构的非域结果。一个真实世界的查询结果可能看起来更复杂。 |
2 | 此片段添加方法。同样,该方法使用 Spring 的 @Transactional 进行注释。 |
如果没有该片段的实现,启动就会失败,所以这里提供实现:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/repositories/custom_queries/MovieRepository.java[]
1 | 这里我们使用 Neo4jClient ,由基础设施提供。 |
2 | 客户端仅使用字符串,但是在呈现为字符串时仍然可以使用 Cypher-DSL。 |
3 | 将单个值绑定到命名参数。还有一个重载来绑定一个完整的参数映射。 |
4 | 这是你想要的 result 类型。 |
5 | 最后,有 mappedBy 方法,为结果中的每个条目公开一个 Record ,并在需要时公开驱动程序类型系统。这是你在其中挂钩自定义映射的 API。 |
整个查询在 Spring 事务上下文中运行,在本例中,是只读事务。
Low level interactions
有时候,你可能希望从仓库中进行批量加载或删除整个子图,或以非常具体的方式与 Neo4j Java 驱动程序进行交互。这也完全有可能。以下示例显示了如何操作:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/repositories/custom_queries/MovieRepository.java[]
1 | 直接使用驱动程序。与所有示例一样:不需要 @Autowired 魔法。所有片段实际上都是可以单独测试的。 |
2 | 用例是虚构的。在这里,我们使用驱动程序管理的事务来删除整个图形,并返回已删除的节点和关系的数量。 |
当然,此交互不会在 Spring 事务中运行,因为驱动程序不知道 Spring。
综合起来,此测试将成功:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$documentation/repositories/custom_queries/CustomQueriesIT.java[]
最后一句话:所有三个接口和实现都由 Spring Data Neo4j 自动选取。无需进一步配置。此外,还可以仅通过一个附加片段(定义所有三个方法的接口)和一个实现来创建相同的整体仓库。然后,实现将具有所有三个注入的抽象(模板、客户端和驱动程序)。
所有内容当然也适用于 reactive 存储库。它们将使用 ReactiveNeo4jTemplate
和 ReactiveNeo4jClient
,以及由驱动程序提供的 reactive 会话。
如果你有所有存储库的重复方法,你可以替换默认存储库实现。
How do I use custom Spring Data Neo4j base repositories?
与共享 Spring Data Commons 文档在 Customize the Base Repository中针对 Spring Data JPA 展示的方式基本相同。只是在我们的案例中,您将从以下内容进行扩展:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$integration/imperative/CustomBaseRepositoryIT.java[]
1 | 此签名是基类所必需的。采用 Neo4jOperations (Neo4jTemplate 的实际规范)和实体信息,并在需要时将它们存储在属性上。 |
在这个示例中,我们禁止使用 findAll
方法。你可以添加接受获取深度的方法,并基于该深度运行自定义查询。可在 [domain-results] 看到一种实现方式。
要为所有声明的存储库启用此基本存储库,请使用以下内容启用 Neo4j 存储库:@EnableNeo4jRepositories(repositoryBaseClass = MyRepositoryImpl.class)
。
How do I audit entities?
所有 Spring Data 注解都受支持。这些注解是
-
org.springframework.data.annotation.CreatedBy
-
org.springframework.data.annotation.CreatedDate
-
org.springframework.data.annotation.LastModifiedBy
-
org.springframework.data.annotation.LastModifiedDate
Auditing 为您提供了如何在 Spring Data Commons 的更大上下文中使用审核的一般视图。以下清单显示了 Spring Data Neo4j 提供的每个配置选项:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$integration/imperative/AuditingIT.java[]
1 | 如果您希望在创建时写入修改数据,则设置为 true |
2 | 使用此属性指定提供审核员(即用户名)的 bean 名称 |
3 | 使用此属性指定提供当前日期的 bean 名称。在这种情况下,固定日期用作上述配置的一部分,是我们测试的一部分 |
reactive 版本基本相同,不同之处在于审计员感知 bean 的类型是 ReactiveAuditorAware
,因此检索审计员是 reactive 流的一部分。
除了这些审计机制之外,你还可以向上下文中添加尽可能多实现 BeforeBindCallback<T>
或 ReactiveBeforeBindCallback<T>
的 bean。Spring Data Neo4j 会选择这些 bean,并在实体持久化之前按顺序(如果它们实现了 Ordered
或用 @Order
进行了注释)调用它们。
它们可以修改实体或返回一个完全新的实体。以下示例向上下文中添加了一个回调,它会在实体被持久化之前更改一个属性:
Unresolved include directive in modules/ROOT/pages/faq.adoc - include::example$integration/imperative/CallbacksIT.java[]
不需要其他配置。
How do I use "Find by example"?
“按示例查找”是 SDN 中的一项新功能。你可以实例化实体或使用现有实体。使用此实例,你可以创建一个 org.springframework.data.domain.Example
。如果你的存储库扩展 org.springframework.data.neo4j.repository.Neo4jRepository
或 org.springframework.data.neo4j.repository.ReactiveNeo4jRepository
,你可以立即使用可用的接受示例的 findBy
方法,如 find-by-example-example 中所示。
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);
你还可以否定单个属性。这将添加一个适当的 NOT
操作,从而将 =
变换为 <>
。所有标量数据类型和所有字符串操作符都受支持:
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);
Do I need Spring Boot to use Spring Data Neo4j?
不需要。自动通过 Spring Boot 配置许多 Spring 方面虽然消除了很多手动苦力并且是设置新 Spring 项目的推荐方法,但你不需要这样做。
上面描述的解决方案需要以下依赖项:
<dependency>
<groupId>{neo4jGroupId}</groupId>
<artifactId>{artifactId}</artifactId>
<version>{version}</version>
</dependency>
Gradle 设置的坐标相同。
若要选择一个不同的数据库 - 无论是静态的还是动态的,都可以添加一个 DatabaseSelectionProvider
类型的 Bean,如 Neo4j 4 supports multiple databases - How can I use them? 中所述。对于 reactive 场景,我们提供了 ReactiveDatabaseSelectionProvider
。
Using Spring Data Neo4j inside a Spring context without Spring Boot
我们提供了两个抽象配置类来支持你引入必要的 bean:org.springframework.data.neo4j.config.AbstractNeo4jConfig
用于命令式数据库访问和 org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig
用于 reactive 版本。它们旨在分别与 @EnableNeo4jRepositories
和 @EnableReactiveNeo4jRepositories
一起使用。请参阅 [bootless-imperative-configuration] 和 [bootless-reactive-configuration] 来查看示例用法。这两个类都要求你覆盖 driver()
,你在其中应创建驱动程序。
若要获取 Neo4j client的命令式版本、模板和对命令式存储库的支持,请使用类似这里所示的内容:
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 | 需要驱动程序 bean。 |
2 | 以静态方式选择名为 yourDatabase 的数据库,其中 optional 。 |
以下列表提供了 reactive Neo4j 客户端和模板,启用 reactive 事务管理和发现 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());
}
}
Using Spring Data Neo4j in a CDI 2.0 environment
为了方便起见,我们提供了带有 Neo4jCdiExtension
的 CDI 扩展。在兼容的 CDI 2.0 容器中运行时,它会通过 Java’s service loader 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 | 与 [bootless-imperative-configuration] 中的普通 Spring 一样,但使用相应的 CDI 基础架构进行了注释。 |
2 | 这是 optional 。但是,如果您运行自定义数据库选择提供程序,则 must 不限定此 bean。 |
如果您在 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();
}
}
}