Spring Data(数据)Neo4j(三)

时间:2022-11-24 15:26:32

Spring Data(数据)Neo4j(三)

附录A:Spring Data Neo4j

转换

内置转换

我们支持开箱即用的各种转换。 在官方驱动程序手册中找到支持的密码类型列表:使用 Cypher 值。

同样支持包装器的基元类型。

域类型

密码类型

直接映射到本机类型

​java.lang.Boolean​

布尔

​boolean[]​

布尔值列表

​java.lang.Long​

整数

​long[]​

整数列表

​java.lang.Double​

​java.lang.String​

字符串

​java.lang.String[]​

字符串列表

​byte[]​

字节数组

​java.lang.Byte​

长度为 1 的字节数组

​java.lang.Character​

长度为 1 的字符串

​char[]​

长度为 1 的字符串列表

​java.util.Date​

格式为 ISO 8601 日期 () 的字符串。 请注意:SDN 将存储所有实例。 如果需要时区,请使用支持时区的类型(即)或将时区存储为单独的属性。​​yyyy-MM-dd’T’HH:mm:ss.SSSZ​​​​Z​​​​java.util.Date​​​​UTC​​​​ZoneDateTime​

​double[]​

浮点列表

​java.lang.Float​

字符串

​float[]​

字符串列表

​java.lang.Integer​

整数

​int[]​

整数列表

​java.util.Locale​

格式为 BCP 47 语言标记的字符串

​java.lang.Short​

整数

​short[]​

整数列表

​java.math.BigDecimal​

字符串

​java.math.BigInteger​

字符串

​java.time.LocalDate​

日期

​java.time.OffsetTime​

时间

​java.time.LocalTime​

当地时间

​java.time.ZonedDateTime​

日期时间

​java.time.LocalDateTime​

本地日期时间

​java.time.Period​

期间

​java.time.Duration​

期间

​org.neo4j.driver.types.IsoDuration​

期间

​org.neo4j.driver.types.Point​

​org.springframework.data.neo4j.types.GeographicPoint2d​

点与CRS 4326

​org.springframework.data.neo4j.types.GeographicPoint3d​

点与 CRS 4979

​org.springframework.data.neo4j.types.CartesianPoint2d​

点与CRS 7203

​org.springframework.data.neo4j.types.CartesianPoint3d​

与 CRS 9157 点

​org.springframework.data.geo.Point​

点与 CRS 4326 和 x/y 对应于经度/经度

的实例​​Enum​

字符串(枚举的名称值)

的实例​​Enum[]​

字符串列表(枚举的名称值)

java.net.URL

字符串

java.net.URI

字符串

自定义转化

对于给定类型的属性

如果您希望在实体中使用自己的类型或作为参数的批注方法,则可以定义并提供自定义转换器实现。 首先,您必须实现并注册转换器应处理的类型。 对于实体属性类型转换器,您需要注意将类型与Neo4j Java 驱动程序相互转换。 如果您的转换器应该只使用存储库中的自定义查询方法,则提供到类型的单向转换就足够了。​​@Query​​​​GenericConverter​​​​Value​​​​Value​

清单 80.自定义转换器实现示例

public class MyCustomTypeConverter implements GenericConverter {

@Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> convertiblePairs = new HashSet<>();
convertiblePairs.add(new ConvertiblePair(MyCustomType.class, Value.class));
convertiblePairs.add(new ConvertiblePair(Value.class, MyCustomType.class));
return convertiblePairs;
}

@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (MyCustomType.class.isAssignableFrom(sourceType.getType())) {
// convert to Neo4j Driver Value
return convertToNeo4jValue(source);
} else {
// convert to MyCustomType
return convertToMyCustomType(source);
}
}

}

要使SDN知道您的转换器,必须在其中注册。 为此,您必须使用类型创建 a。 否则,将仅使用内部默认转换器在后台创建。​​Neo4jConversions​​​​@Bean​​​​org.springframework.data.neo4j.core.convert.Neo4jConversions​​​​Neo4jConversions​

清单 81.自定义转换器实现示例

@Bean
public Neo4jConversions neo4jConversions() {
Set<GenericConverter> additionalConverters = Collections.singleton(new MyCustomTypeConverter());
return new Neo4jConversions(additionalConverters);
}

如果您的应用程序中需要多个转换器,则可以在构造函数中添加任意数量的转换器。​​Neo4jConversions​

仅适用于特定属性

如果您只需要针对某些特定属性进行转化,我们会提供。 这是一个注释,可以放在实体 () 和关系属性 () 的属性上 它定义了 avia 属性 和可选的构造前者。 通过实现给定类型的所有特定转换,可以解决。 此外,还提供了在应用程序上下文中引用任何 Spring Bean 的实现。引用的 bean 将优先于构造新的转换器。​​@ConvertWith​​​​@Node​​​​@RelationshipProperties​​​​Neo4jPersistentPropertyConverter​​​​converter​​​​Neo4jPersistentPropertyConverterFactory​​​​Neo4jPersistentPropertyConverter​​​​@ConvertWith​​​​converterRef​​​​Neo4jPersistentPropertyConverter​

我们提供元注释注释,以便与不使用本机类型的 Neo4j-OGM 方案向后兼容。 这些是在上述概念的基础上建立的元注释注释。​​@DateLong​​​​@DateString​

复合属性

使用,类型或的属性可以存储为复合属性。 映射中的所有条目都将作为属性添加到包含该属性的节点或关系中。 使用配置的前缀或以属性名称为前缀。 虽然我们只为开箱即用的地图提供该功能,但您可以对其进行配置。 作为要使用的转换器。需要知道给定类型如何 分解为地图并从地图中返回。​​@CompositeProperty​​​​Map<String, Object>​​​​Map<? extends Enum, Object>​​​​Neo4jPersistentPropertyToMapConverter​​​​@CompositeProperty​​​​Neo4jPersistentPropertyToMapConverter​

Neo4j客户端

Spring Data Neo4j附带一个Neo4j客户端,在Neo4j的Java驱动程序之上提供了一个薄层。

虽然普通的Java驱动程序是一个非常通用的工具,除了命令式和反应式版本之外,它还提供异步API,但它不与Spring应用程序级事务集成。

SDN 通过惯用客户端的概念尽可能直接地使用驱动程序。

客户有以下主要目标

  1. 集成到 Springs 事务管理中,适用于命令式和响应式场景
  2. 如有必要,参与JTA交易
  3. 为命令式和反应式场景提供一致的 API
  4. 不要增加任何映射开销

SDN依赖于所有这些功能,并使用它们来实现其实体映射功能。

查看SDN 构建块,了解命令式和响应式 Neo4 客户端在我们的堆栈中的位置。

Neo4j客户端有两种风格:

  • ​org.springframework.data.neo4j.core.Neo4jClient​
  • ​org.springframework.data.neo4j.core.ReactiveNeo4jClient​

虽然两个版本都使用相同的词汇和语法提供 API,但它们与 API 不兼容。 两个版本都具有相同的流畅 API 来指定查询、绑定参数和提取结果。

命令式还是被动式?

与 Neo4j 客户端的交互通常以调用

  • ​fetch().one()​
  • ​fetch().first()​
  • ​fetch().all()​
  • ​run()​

命令式版本此时将与数据库交互,并获得请求的结果或摘要,包装在 anor a 中。​​Optional<>​​​​Collection​

相反,反应式版本将返回所请求类型的发布者。 在订阅发布者之前,不会与数据库交互和检索结果。 发布者只能订阅一次。

获取客户端的实例

与 SDN 中的大多数内容一样,这两个客户端都依赖于配置的驱动程序实例。

清单 82.创建命令式 Neo4j 客户端的实例

public class Demo {

public static void main(String...args) {

Driver driver = GraphDatabase
.driver("neo4j://localhost:7687", AuthTokens.basic("neo4j", "secret"));

Neo4jClient client = Neo4jClient.create(driver);
}
}

驱动程序只能针对 4.0 数据库打开反应式会话,并且在任何较低版本上都会失败并出现异常。

清单 83.创建反应式 Neo4j 客户端的实例

public class Demo {

public static void main(String...args) {

Driver driver = GraphDatabase
.driver("neo4j://localhost:7687", AuthTokens.basic("neo4j", "secret"));

ReactiveNeo4jClient client = ReactiveNeo4jClient.create(driver);
}
}

请确保对客户端使用的驱动程序实例与用于提供 aorin 的驱动程序实例相同,以防已启用事务。 如果使用驱动程序的另一个实例,客户端将无法同步事务。​​Neo4jTransactionManager​​​​ReactiveNeo4jTransactionManager​

我们的 Spring 启动启动器提供了一个现成的 Neo4j 客户端 bean,适合环境(命令式或响应式),您通常不必配置自己的实例。

用法

选择目标数据库

Neo4j 客户端已做好充分准备,可以与 Neo4j 4.0 的多数据库功能一起使用。除非您另行指定,否则客户端使用默认数据库。 客户端的流畅 API 允许在声明要执行的查询后只指定一次目标数据库。清单 84用反应式客户端演示了它:

清单 84.选择目标数据库

Flux<Map<String, Object>> allActors = client
.query("MATCH (p:Person) RETURN p")
.in("neo4j")
.fetch()
.all();

选择要在其中执行查询的目标数据库。

指定查询

与客户端的交互从查询开始。 查询可以由普通 a 定义。 供应商将尽可能晚地进行评估,并且可以由任何查询构建器提供。​​String​​​​Supplier<String>​

清单 85.指定查询

Mono<Map<String, Object>> firstActor = client
.query(() -> "MATCH (p:Person) RETURN p")
.fetch()
.first();
检索结果

如前面的列表所示,与客户的交互总是以调用和应收到多少结果结束。 被动和命令式客户报价​​fetch​

​one()​

期望查询中只有一个结果

​first()​

预期结果并返回第一条记录

​all()​

检索返回的所有记录

命令式客户端分别返回沙子,而反应式客户端返回沙子,后者仅在订阅时才执行。​​Optional<T>​​​​Collection<T>​​​​Mono<T>​​​​Flux<T>​

如果不希望查询产生任何结果,请在指定查询后使用。​​run()​

清单 86.以反应方式检索结果摘要

Mono<ResultSummary> summary = reactiveClient
.query("MATCH (m:Movie) where m.title = 'Aeon Flux' DETACH DELETE m")
.run();

summary
.map(ResultSummary::counters)
.subscribe(counters ->
System.out.println(counters.nodesDeleted() + " nodes have been deleted")
);

实际查询在此处通过订阅发布者触发。

请花点时间比较两个列表,并在触发实际查询时了解差异。

清单 87.以命令式方式检索结果摘要

ResultSummary resultSummary = imperativeClient
.query("MATCH (m:Movie) where m.title = 'Aeon Flux' DETACH DELETE m")
.run();

SummaryCounters counters = resultSummary.counters();
System.out.println(counters.nodesDeleted() + " nodes have been deleted")

在这里,查询立即被触发。

映射参数

查询可以包含命名参数 (),Neo4j 客户端可以轻松地将值绑定到它们。​​$someName​

客户端不会检查是否绑定了所有参数或值是否过多。 这留给司机。 但是,客户端会阻止您使用参数名称两次。

您可以绑定 Java 驱动程序无需转换即可理解的简单类型或复杂类。 对于复杂类,您需要提供绑定程序函数,如此列表中所示。 请查看驱动程序手册,了解支持哪些简单类型。

清单 88.映射简单类型

Map<String, Object> parameters = new HashMap<>();
parameters.put("name", "Li.*");

Flux<Map<String, Object>> directorAndMovies = client
.query(
"MATCH (p:Person) - [:DIRECTED] -> (m:Movie {title: $title}), (p) - [:WROTE] -> (om:Movie) " +
"WHERE p.name =~ $name " +
" AND p.born < $someDate.year " +
"RETURN p, om"
)
.bind("The Matrix").to("title")
.bind(LocalDate.of(1979, 9, 21)).to("someDate")
.bindAll(parameters)
.fetch()
.all();

有一个用于绑定简单类型的流畅 API。

或者,可以通过命名参数映射绑定参数。

SDN 执行许多复杂的映射,它使用可以从客户端使用的相同 API。

您可以为 Neo4j 客户端提供 afor 任何给定的域对象(如清单 89中的自行车所有者),以将这些域对象映射到驱动程序可以理解的参数。​​Function<T, Map<String, Object>>​

清单 89.域类型示例

public class Director {

private final String name;

private final List<Movie> movies;

Director(String name, List<Movie> movies) {
this.name = name;
this.movies = new ArrayList<>(movies);
}

public String getName() {
return name;
}

public List<Movie> getMovies() {
return Collections.unmodifiableList(movies);
}
}

public class Movie {

private final String title;

public Movie(String title) {
this.title = title;
}

public String getTitle() {
return title;
}
}

映射函数必须填写查询中可能出现的所有命名参数,如清单 90所示:

清单 90.使用映射函数绑定域对象

Director joseph = new Director("Joseph Kosinski",
Arrays.asList(new Movie("Tron Legacy"), new Movie("Top Gun: Maverick")));

Mono<ResultSummary> summary = client
.query(""
+ "MERGE (p:Person {name: $name}) "
+ "WITH p UNWIND $movies as movie "
+ "MERGE (m:Movie {title: movie}) "
+ "MERGE (p) - [o:DIRECTED] -> (m) "
)
.bind(joseph).with(director -> {
Map<String, Object> mappedValues = new HashMap<>();
List<String> movies = director.getMovies().stream()
.map(Movie::getTitle).collect(Collectors.toList());
mappedValues.put("name", director.getName());
mappedValues.put("movies", movies);
return mappedValues;
})
.run();


该方法允许指定绑定器函数。​​with​

使用结果对象

这两个客户端都返回地图的集合或发布者 ()。 这些地图与查询可能生成的记录完全对应。​​Map<String, Object>​

此外,您可以插入自己的通路来重现您的域对象。​​BiFunction<TypeSystem, Record, T>​​​​fetchAs​

清单 91.使用映射函数读取域对象

Mono<Director> lily = client
.query(""
+ " MATCH (p:Person {name: $name}) - [:DIRECTED] -> (m:Movie)"
+ "RETURN p, collect(m) as movies")
.bind("Lilly Wachowski").to("name")
.fetchAs(Director.class).mappedBy((TypeSystem t, Record record) -> {
List<Movie> movies = record.get("movies")
.asList(v -> new Movie((v.get("title").asString())));
return new Director(record.get("name").asString(), movies);
})
.one();

​TypeSystem​​提供对基础 Java 驱动程序用于填充记录的类型的访问权限。

使用域感知映射函数

如果您知道查询结果将包含应用程序中具有实体定义的节点, 您可以使用 InjectableS 检索其映射函数并在映射期间应用它们。​​MappingContext​

清单 92.使用现有映射函数

BiFunction<TypeSystem, MapAccessor, Movie> mappingFunction = neo4jMappingContext.getRequiredMappingFunctionFor(Movie.class);
Mono<Director> lily = client
.query(""
+ " MATCH (p:Person {name: $name}) - [:DIRECTED] -> (m:Movie)"
+ "RETURN p, collect(m) as movies")
.bind("Lilly Wachowski").to("name")
.fetchAs(Director.class).mappedBy((TypeSystem t, Record record) -> {
List<Movie> movies = record.get("movies")
.asList(movie -> mappingFunction.apply(t, movie));
return new Director(record.get("name").asString(), movies);
})
.one();
使用托管事务时直接与驱动程序交互

如果你不想或不喜欢固执己见的“客户端”方法,你可以让客户端将与数据库的所有交互委托给你的代码。 委派后的交互与客户端的命令式和反应式版本略有不同。​​Neo4jClient​​​​ReactiveNeo4jClient​

命令式版本接受 aas 回调。 返回空的可选选项是可以的。​​Function<StatementRunner, Optional<T>>​

清单 93.将数据库交互委托给命令式​​StatementRunner​

Optional<Long> result = client
.delegateTo((StatementRunner runner) -> {
// Do as many interactions as you want
long numberOfNodes = runner.run("MATCH (n) RETURN count(n) as cnt")
.single().get("cnt").asLong();
return Optional.of(numberOfNodes);
})
// .in("aDatabase")
.run();

选择目标数据库中所述的数据库选择是可选的。

反应式版本接收 a。​​RxStatementRunner​

清单 94.将数据库交互委托给反应式​​RxStatementRunner​

Mono<Integer> result = client
.delegateTo((RxStatementRunner runner) ->
Mono.from(runner.run("MATCH (n:Unused) DELETE n").summary())
.map(ResultSummary::counters)
.map(SummaryCounters::nodesDeleted))
// .in("aDatabase")
.run();

可选择选择目标数据库。

请注意,在清单93和清单 94中,流道的类型只是为了向本手册的读者提供更清晰的信息。

查询创建

本章介绍使用 SDN 抽象层时查询的技术创建。 会有一些简化,因为我们不会讨论所有可能的情况,而是坚持其背后的一般想法。

除了操作之外,操作是处理数据时最常用的操作之一。 保存操作调用通常对数据库发出多个语句,以确保生成的图形模型与给定的 Java 模型匹配。​​find/load​​​​save​

  1. 将创建一个联合语句,如果找不到节点的标识符,则创建一个节点,或者如果节点本身存在,则更新节点的属性。

    (OPTIONAL MATCH (hlp:Person) WHERE id(hlp) = $__id__ WITH hlp WHERE hlp IS NULL CREATE (n:Person) SET n = $__properties__ RETURN id(n) UNION MATCH (n) WHERE id(n) = $__id__ SET n = $__properties__ RETURN id(n))
  2. 如果实体不是新的,则域模型中第一个找到的类型的所有关系都将从数据库中删除。

    (MATCH (startNode)-[rel:Has]→(:Hobby) WHERE id(startNode) = $fromId DELETE rel)
  3. 相关实体将以与根实体相同的方式创建。

    (OPTIONAL MATCH (hlp:Hobby) WHERE id(hlp) = $__id__ WITH hlp WHERE hlp IS NULL CREATE (n:Hobby) SET n = $__properties__ RETURN id(n) UNION MATCH (n) WHERE id(n) = $__id__ SET n = $__properties__ RETURN id(n))
  4. 关系本身将被创建

    (MATCH (startNode) WHERE id(startNode) = $fromId MATCH (endNode) WHERE id(endNode) = 631 MERGE (startNode)-[:Has]→(endNode))
  5. 如果相关实体也与其他实体有关系,则过程与 2 中的过程相同。将开始。
  6. 对于根实体上的下一个定义关系,从 2 开始。但首先替换为下一个

如您所见,SDN尽最大努力使您的图形模型与Java世界保持同步。 这就是为什么我们真的建议您不要加载,操作和保存子图的原因之一,因为这可能会导致关系从数据库中删除。

多个实体

该操作重载了用于接受相同类型的多个实体的功能。 如果使用生成的 id 值或使用乐观锁定,则每个实体都将导致单独的调用。​​save​​​​CREATE​

在其他情况下,SDN 将创建一个包含实体信息的参数列表,并为其提供调用。​​MERGE​

​UNWIND $__entities__ AS entity MERGE (n:Person {customId: entity.$__id__}) SET n = entity.__properties__ RETURN collect(n.customId) AS $__ids__​

参数看起来像

​:params {__entities__: [{__id__: 'aa', __properties__: {name: "PersonName", theId: "aa"}}, {__id__ 'bb', __properties__: {name: "AnotherPersonName", theId: "bb"}}]}​

负荷

该文档不仅会显示查询的MATCH部分的外观,还会显示数据的返回方式。​​load​

最简单的负载操作是调用。 它将所有节点与查询的类型标签匹配,并对 id 值进行筛选。​​findById​

​MATCH (n:Person) WHERE id(n) = 1364​

如果提供了自定义 ID,SDN 将使用已定义为 id 的属性。

​MATCH (n:Person) WHERE n.customId = 'anId'​

要返回的数据定义为地图投影。

​RETURN n{.first_name, .personNumber, __internalNeo4jId__: id(n), __nodeLabels__: labels(n)}​

如您所见,其中有两个特殊字段:The和the。 在将数据映射到 Java 对象时,两者都至关重要。 的值要么是提供的自定义 ID,但在映射过程中必须存在一个要引用的已知字段。 确保可以找到并映射此节点上所有定义的标签。 在使用继承并且您不查询具体类或定义了仅定义超类型的关系的情况下,这是必需的。​​__internalNeo4jId__​​​​__nodeLabels__​​​​__internalNeo4jId__​​​​id(n)​​​​__nodeLabels__​

谈论关系:如果您在实体中定义了关系,它们将作为模式推导式添加到返回的映射中。 上面的返回部分将如下所示:

​RETURN n{.first_name, …, Person_Has_Hobby: [(n)-[:Has]→(n_hobbies:Hobby)|n_hobbies{__internalNeo4jId__: id(n_hobbies), .name, nodeLabels: labels(n_hobbies)}]}​

SDN 使用的地图投影和模式理解可确保仅查询已定义的属性和关系。

如果您有自引用节点或创建可能导致返回数据循环的架构, SDN 回退到级联/数据驱动的查询创建。 从查找特定节点并考虑条件的初始查询开始, 它遍历生成的节点,如果它们的关系也被映射,则会动态创建进一步的查询。 此查询创建和执行循环将继续,直到没有查询找到新的关系或节点。 创建方式类似于保存/更新过程。

自定义查询

Spring Data Neo4j,像所有其他Spring Data模块一样,允许您在存储库中指定自定义查询。 如果您无法通过派生的查询函数表达查找器逻辑,这些将派上用场。

因为Spring Data Neo4j在后台工作时非常注重记录,所以记住这一点很重要,而不是为同一个“根节点”建立一个包含多个记录的结果集。

请同时查看常见问题解答,了解使用存储库自定义查询的替代形式,尤其是 如何将自定义查询与自定义映射一起使用:是@Query使用自定义查询的唯一方法?

使用关系的查询

当心笛卡尔积

假设您有一个这样的查询,结果如下:​​MATCH (m:Movie{title: 'The Matrix'})←[r:ACTED_IN]-(p:Person) return m,r,p​

清单 95.多条记录(缩短)

+------------------------------------------------------------------------------------------+
| m | r | p |
+------------------------------------------------------------------------------------------+
| (:Movie) | [:ACTED_IN {roles: ["Emil"]}] | (:Person {name: "Emil Eifrem"}) |
| (:Movie) | [:ACTED_IN {roles: ["Agent Smith"]}] | (:Person {name: "Hugo Weaving}) |
| (:Movie) | [:ACTED_IN {roles: ["Morpheus"]}] | (:Person {name: "Laurence Fishburne"}) |
| (:Movie) | [:ACTED_IN {roles: ["Trinity"]}] | (:Person {name: "Carrie-Anne Moss"}) |
| (:Movie) | [:ACTED_IN {roles: ["Neo"]}] | (:Person {name: "Keanu Reeves"}) |
+------------------------------------------------------------------------------------------+

映射的结果很可能不可用。 如果这将映射到一个列表中,它将包含重复项,但这部电影将只有一个关系。​​Movie​

获取每个根节点一条记录

若要获取正确的对象,需要在查询中收集关系和相关节点:​​MATCH (m:Movie{title: 'The Matrix'})←[r:ACTED_IN]-(p:Person) return m,collect(r),collect(p)​

清单 96.单条记录(缩短)

+------------------------------------------------------------------------+
| m | collect(r) | collect(p) |
+------------------------------------------------------------------------+
| (:Movie) | [[:ACTED_IN], [:ACTED_IN], ...]| [(:Person), (:Person),...] |
+------------------------------------------------------------------------+

将此结果作为单个记录,Spring Data Neo4j可以将所有相关节点正确添加到根节点。

深入图表

上面的示例假设您只是尝试获取相关节点的第一级。 这有时还不够,图形中可能更深的节点也应该是映射实例的一部分。 有两种方法可以实现此目的:数据库端或客户端缩减。

为此,上面的示例也应该包含与首字母一起返回的那个。​​Movies​​​​Persons​​​​Movie​

Spring Data(数据)Neo4j(三)

数据库端缩减

请记住,Spring Data Neo4j只能正确处理基于记录的,一个实体实例的结果需要在一个记录中。 使用Cypher 的路径功能是获取图形中所有分支的有效选项。

清单 97.基于朴素路径的方法

MATCH p=(m:Movie{title: 'The Matrix'})<-[:ACTED_IN]-(:Person)-[:ACTED_IN*..0]->(:Movie)
RETURN p;

这将导致多个路径未合并到一条记录中。 可以调用但Spring Data Neo4j不理解映射过程中路径的概念。 因此,需要提取节点和关系以获得结果。​​collect(p)​

清单 98.提取节点和关系

MATCH p=(m:Movie{title: 'The Matrix'})<-[:ACTED_IN]-(:Person)-[:ACTED_IN*..0]->(:Movie)
RETURN m, nodes(p), relationships(p);

因为从《黑客帝国》到另一部电影有多条路径,结果仍然不会是一条记录。 这就是Cypher的reduce功能发挥作用的地方。

清单 99.减少节点和关系

MATCH p=(m:Movie{title: 'The Matrix'})<-[:ACTED_IN]-(:Person)-[:ACTED_IN*..0]->(:Movie)
WITH collect(p) as paths, m
WITH m,
reduce(a=[], node in reduce(b=[], c in [aa in paths | nodes(aa)] | b + c) | case when node in a then a else a + node end) as nodes,
reduce(d=[], relationship in reduce(e=[], f in [dd in paths | relationships(dd)] | e + f) | case when relationship in d then d else d + relationship end) as relationships
RETURN m, relationships, nodes;

该函数允许我们从各种路径展平节点和关系。 结果,我们将得到一个元组,类似于每个根节点获取一条记录,但在集合中混合了关系类型或节点。​​reduce​

客户端减少

如果缩减应该发生在客户端,Spring Data Neo4j使你能够映射关系或节点列表的列表。 尽管如此,要求仍然适用,即返回的记录应包含所有信息,以正确冻结生成的实体实例。

清单 100.从路径收集节点和关系

MATCH p=(m:Movie{title: 'The Matrix'})<-[:ACTED_IN]-(:Person)-[:ACTED_IN*..0]->(:Movie)
RETURN m, collect(nodes(p)), collect(relationships(p));

附加语句创建列表的格式为:​​collect​

[[rel1, rel2], [rel3, rel4]]

这些列表现在将在映射过程中转换为平面列表。

决定是使用客户端缩减还是数据库端缩减取决于将生成的数据量。 当使用该函数时,所有路径都需要首先在数据库的内存中创建。 另一方面,需要在客户端合并的大量数据会导致那里的内存使用率更高。​​reduce​

使用路径填充和返回实体列表

给出的是一个看起来像这样的图表:

Spring Data(数据)Neo4j(三)

和映射中所示的域模型(为简洁起见,省略了构造函数和访问器):

清单 101.图 3 的域模型。

@Node
public class SomeEntity {

@Id
private final Long number;

private String name;

@Relationship(type = "SOME_RELATION_TO", direction = Relationship.Direction.OUTGOING)
private Set<SomeRelation> someRelationsOut = new HashSet<>();
}

@RelationshipProperties
public class SomeRelation {

@RelationshipId
private Long id;

private String someData;

@TargetNode
private SomeEntity targetPerson;
}

如您所见,关系只是外向的。生成的查找器方法(包括)将始终尝试匹配 要映射的根节点。从那里开始,所有相关对象将被映射。在应仅返回一个对象的查询中, 返回该根对象。在返回许多对象的查询中,将返回所有匹配的对象。传出和传入关系 从返回的对象中返回当然是填充的。​​findById​

假设以下密码查询:

MATCH p = (leaf:SomeEntity {number: $a})-[:SOME_RELATION_TO*]-(:SomeEntity)
RETURN leaf, collect(nodes(p)), collect(relationships(p))

它遵循每个根节点获取一条记录的建议,非常适合叶节点 你想在这里匹配。但是:仅在返回 0 或 1 个映射对象的所有方案中,情况如此。 虽然该查询将像以前一样填充所有关系,但它不会返回所有 4 个对象。

这可以通过返回整个路径来更改:

MATCH p = (leaf:SomeEntity {number: $a})-[:SOME_RELATION_TO*]-(:SomeEntity)
RETURN p

在这里,我们确实想使用路径实际上返回 3 行以及指向所有 4 个节点的路径这一事实。所有 4 个节点都将是 填充,链接在一起并返回。​​p​

自定义查询中的参数

您执行此操作的方式与在 Neo4j 浏览器或 Cypher-Shell 中发出的标准 Cypher 查询完全相同, 语法(从 Neo4j 4.0 开始,Cypher 参数的旧语法已从数据库中删除)。​​$​​​​{foo}​

清单 102..java

public interface ARepository extends Neo4jRepository<AnAggregateRoot, String> {

@Query("MATCH (a:AnAggregateRoot {name: $name}) RETURN a")
Optional<AnAggregateRoot> findByCustomQuery(String name);
}

在这里,我们按其名称引用参数。 您也可以使用等。相反。​​$0​

您需要编译 Java 8+ 项目以使命名参数无需进一步注释即可工作。 Spring Boot Maven 和 Gradle 插件会自动为您执行此操作。 如果由于任何原因这不可行,您可以添加并显式指定名称或使用参数索引。​​-parameters​​​​@Param​

映射实体(带 a 的所有内容)作为参数传递给带有注释的函数 自定义查询将转换为嵌套映射。 下面的示例将结构表示为 Neo4j 参数。​​@Node​

给定的是 a,和类注释,如电影模型所示:​​Movie​​​​Vertex​​​​Actor​

清单 103.“标准”电影模型

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

@Relationship(value = "DIRECTED", direction = Direction.INCOMING)
private final List<Person> directors;
}

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

interface MovieRepository extends Neo4jRepository<Movie, String> {

@Query("MATCH (m:Movie {title: $movie.__id__})\n"
+ "MATCH (m) <- [r:DIRECTED|REVIEWED|ACTED_IN] - (p:Person)\n"
+ "return m, collect(r), collect(p)")
Movie findByMovie(@Param("movie") Movie movie);
}

将 的实例传递给上面的存储库方法,将生成以下 Neo4j 映射参数:​​Movie​

{
"movie": {
"__labels__": [
"Movie"
],
"__id__": "The Da Vinci Code",
"__properties__": {
"ACTED_IN": [
{
"__properties__": {
"roles": [
"Sophie Neveu"
]
},
"__target__": {
"__labels__": [
"Person"
],
"__id__": 402,
"__properties__": {
"name": "Audrey Tautou",
"born": 1976
}
}
},
{
"__properties__": {
"roles": [
"Sir Leight Teabing"
]
},
"__target__": {
"__labels__": [
"Person"
],
"__id__": 401,
"__properties__": {
"name": "Ian McKellen",
"born": 1939
}
}
},
{
"__properties__": {
"roles": [
"Dr. Robert Langdon"
]
},
"__target__": {
"__labels__": [
"Person"
],
"__id__": 360,
"__properties__": {
"name": "Tom Hanks",
"born": 1956
}
}
},
{
"__properties__": {
"roles": [
"Silas"
]
},
"__target__": {
"__labels__": [
"Person"
],
"__id__": 403,
"__properties__": {
"name": "Paul Bettany",
"born": 1971
}
}
}
],
"DIRECTED": [
{
"__labels__": [
"Person"
],
"__id__": 404,
"__properties__": {
"name": "Ron Howard",
"born": 1954
}
}
],
"tagline": "Break The Codes",
"released": 2006
}
}
}

节点由映射表示。映射将始终包含映射的 id 属性。 将提供静态和动态的所有下标签。 所有属性(以及关系类型)都显示在这些映射中,就像实体在图形中显示时一样 由 SDN 编写。 值将具有正确的密码类型,不需要进一步转换。​id​​labels

所有关系都是映射列表。动态关系将相应地解析。 一对一关系也将序列化为单一实例列表。因此,要访问一对一的映射 在人与人之间,你会写这个DAS。​​$person.__properties__.BEST_FRIEND[0].__target__.__id__​

如果实体与不同类型的其他节点具有相同类型的关系,则它们都将显示在同一列表中。 如果您需要这样的映射,并且还需要使用这些自定义参数,则必须相应地展开它。 一种方法是相关的子查询(需要 Neo4j 4.1+)。

自定义查询中的 Spring 表达式语言

Spring 表达式语言 (SpEL) 可用于内部的自定义查询。 这里的冒号是指参数,在参数有意义的地方应该使用这样的表达式。 但是,当使用我们的文字扩展时,您可以在标准密码的地方使用 SpEL 表达式 不允许使用参数(例如标签或关系类型)。 这是在经过 SpEL 评估的查询中定义文本块的标准 Spring Data 方法。​​:#{}​

下面的示例基本上定义了与上面相同的查询,但使用 aclause 来避免更多的大括号:​​WHERE​

清单 104..java

public interface ARepository extends Neo4jRepository<AnAggregateRoot, String> {

@Query("MATCH (a:AnAggregateRoot) WHERE a.name = :#{#pt1 + #pt2} RETURN a")
Optional<AnAggregateRoot> findByCustomQueryWithSpEL(String pt1, String pt2);
}

被阻止的 SpEL 以 开头,然后通过名称 () 引用给定参数。 不要将其与上面的密码语法混淆! SpEL 表达式将两个参数连接成一个值,最终传递给Neo4jClient。 SpEL 块以 结束。​​:#{​​​​String​​​​#pt1​​​​}​

SpEL还解决了另外两个问题。我们提供了两个扩展,允许将对象传递到自定义查询中。 还记得自定义查询中的清单 62吗? 使用扩展名,您可以将动态排序传递给自定义查询:​​Sort​​​​orderBy​​​​Pageable​

清单 105.按扩展订购

public interface MyPersonRepository extends Neo4jRepository<Person, Long> {

@Query(""
+ "MATCH (n:Person) WHERE n.name = $name RETURN n "
+ ":#{orderBy(#pageable)} SKIP $skip LIMIT $limit"
)
Slice<Person> findSliceByName(String name, Pageable pageable);

@Query(""
+ "MATCH (n:Person) WHERE n.name = $name RETURN n :#{orderBy(#sort)}"
)
List<Person> findAllByName(String name, Sort sort);
}

A在 SpEL 上下文中始终具有名称。​​Pageable​​​​pageable​

A在 SpEL 上下文中始终具有名称。​​Sort​​​​sort​

扩展可用于在自定义查询中使标签或关系类型等内容“动态”。 标签和关系类型都不能在 Cypher 中参数化,因此必须为它们提供文字。​​literal​

清单 106.文字扩展

interface BaseClassRepository extends Neo4jRepository<Inheritance.BaseClass, Long> {

@Query("MATCH (n:`:#{literal(#label)}`) RETURN n")
List<Inheritance.BaseClass> findByLabel(String label);
}

扩展名将替换为计算参数的文本值。​​literal​

在这里,该值已用于在标签上动态匹配。 如果将 inas 参数传递给方法,将生成。已添加刻度以正确转义值。SDN不会这样做 对于您来说,这可能不是您在所有情况下都想要的。​​literal​​​​SomeLabel​​​​MATCH (n:SomeLabel) RETURN n​

引用标签

您已经知道如何将节点映射到域对象:

清单 107.具有许多标签的节点

@Node(primaryLabel = "Bike", labels = {"Gravel", "Easy Trail"})
public class BikeNode {
@Id String id;

String name;
}

此节点有几个标签,在自定义查询中一直重复它们很容易出错:您可能会 忘记一个或打错字。我们提供以下表达式来缓解此问题:请注意,这个不是以冒号开头的!您可以在存储库方法上使用它,这些方法带有以下注释:​​#{#staticLabels}​​​​@Query​

列出 108.in 操作​​#{#staticLabels}​

public interface BikeRepository extends Neo4jRepository<Bike, String> {

@Query("MATCH (n:#{#staticLabels}) WHERE n.id = $nameOrId OR n.name = $nameOrId RETURN n")
Optional<Bike> findByNameOrId(@Param("nameOrId") String nameOrId);
}

此查询将解析为

MATCH (n:`Bike`:`Gravel`:`Easy Trail`) WHERE n.id = $nameOrId OR n.name = $nameOrId RETURN n

请注意我们如何为以下选项使用标准参数: 在大多数情况下,没有必要在这里使事情复杂化 添加 SpEL 表达式。​​nameOrId​

空间类型

Spring Data Neo4j支持以下空间类型

支持的转换

  • Spring Data common's(必须是数据库中的 WGS 84-2D/SRID 4326 点)Point
  • ​GeographicPoint2d​​(WGS84 2D/SRID 4326)
  • ​GeographicPoint3d​​(WGS84 3D/SRID 4979)
  • ​CartesianPoint2d​​(笛卡尔 2D/SRID 7203)
  • ​CartesianPoint3d​​(笛卡尔 3D/SRID 9157)

派生查找器关键字

如果您使用的是本机 Neo4j Java 驱动程序类型, 您可以在派生的查找器方法中使用以下关键字和参数类型。​​org.neo4j.driver.types.Point​

区域内查询:

  • ​findBy[…]Within(org.springframework.data.geo.Circle circle)​
  • ​findBy[…]Within(org.springframework.data.geo.Box box)​
  • ​findBy[…]Within(org.springframework.data.neo4j.repository.query.BoundingBox boundingBox)​

你也可以使用 abut 需要将其传递给 aby 调用。​​org.springframework.data.geo.Polygon​​​​BoundingBox​​​​BoundingBox#of​

在某个点附近查询:

  • ​findBy[…]Near(org.neo4j.driver.types.Point point)​​- 返回按到给定点的距离升序排序的结果
  • ​findBy[…]Near(Point point, org.springframework.data.geo.Distance max)​
  • ​findBy[…]Near(Point point, org.springframework.data.domain.Range<Distance> between)​
  • ​findBy[…]Near(Range<Distance> between, Point p)​

从 SDN+OGM 迁移到 SDN

过去 SDN+OGM 迁移的已知问题

SDN+OGM多年来已经有相当的历史,我们知道迁移大型应用程序系统既不是一件有趣的事情,也不是提供立竿见影的利润的事情。 从旧版本的Spring Data Neo4j迁移到较新版本时,我们观察到的主要问题大致如下:

跳过了不止一次重大升级

虽然Neo4j-OGM可以独立使用,但Spring Data Neo4j不能。 它在很大程度上依赖于 Spring 数据,因此也依赖于 Spring 框架本身,这最终会影响应用程序的大部分内容。 根据应用程序的结构,即任何框架部分泄漏到业务代码中的程度,您就越需要调整应用程序。 当您的应用程序中有多个 Spring 数据模块时,如果您访问了与图形数据库位于同一服务层的关系数据库,情况会变得更糟。 更新两个对象映射框架并不好玩。

依赖于通过 Spring 数据本身配置的嵌入式数据库

SDN+OGM项目中的嵌入式数据库由Neo4j-OGM配置。 假设您想从 Neo4j 3.0 升级到 3.5,您不能不升级整个应用程序。 为什么? 当您选择将数据库嵌入到应用程序中时,您将自己绑定到配置此嵌入式数据库的模块中。 要拥有另一个嵌入式数据库版本,您必须升级配置它的模块,因为旧数据库不支持新数据库。 由于始终有一个与Neo4j-OGM对应的Spring Data版本,因此您也必须对其进行升级。 然而,Spring 数据依赖于 Spring 框架,然后第一个项目符号中的参数适用。

不确定要包含哪些构建基块

要正确使用术语并不容易。 我们在这里编写了SDN+OGM设置的构建块。 可能是巧合地添加了所有这些依赖项,并且您正在处理许多相互冲突的依赖项。

根据这些观察结果,我们建议在从 SDN+OGM 切换到 SDN 之前,确保在当前应用程序中仅使用 Bolt 或 http 传输。 因此,您的应用程序和应用程序的访问层在很大程度上独立于数据库的版本。 从该状态开始,请考虑从 SDN+OGM 迁移到 SDN。

准备从 SDN+OGM Lovelace 或 SDN+OGM Moore 迁移到 SDN

Lovelace发布系列对应于 SDN 5.1.x 和 OGM 3.1.x,而Moore是 SDN 5.2.x 和 OGM 3.2.x。

首先,您必须确保您的应用程序通过 Bolt 协议在服务器模式下针对 Neo4j 运行,这意味着在以下三种情况下工作:

您处于嵌入式状态

您已经通过 OGM 工具添加并启动了项目和数据库。 这不再受支持,您必须设置一个标准的 Neo4j 服务器(支持独立和集群)。​​org.neo4j:neo4j-ogm-embedded-driver​​​​org.neo4j:neo4j​

必须删除上述依赖项。

从嵌入式解决方案迁移可能是最困难的迁移,因为您还需要设置服务器。 然而,它本身就是给你很多价值的: 将来,您将能够升级数据库本身,而无需考虑应用程序框架和数据访问框架。

您正在使用 HTTP 传输

您已经添加并配置了一个类似 url。 依赖关系必须替换为并且您需要配置一个 Bolt url 喜欢或使用 newscheme,它也负责路由。​​org.neo4j:neo4j-ogm-http-driver​​​​http://user:password@localhost:7474​​​​org.neo4j:neo4j-ogm-bolt-driver​​​​bolt://localhost:7687​​​​neo4j://​

您已经在间接使用 Bolt

默认的SDN+OGM项目间接使用纯Java驱动程序。 您可以保留现有网址。​​org.neo4j:neo4j-ogm-bolt-driver​

迁移

一旦确定SDN + OGM应用程序按预期在Bolt上运行,就可以开始迁移到SDN。

  • 删除所有依赖项org.neo4j:neo4j-ogm-*
  • 不支持通过 abean 配置 SDN,而是通过我们新的 Java 驱动程序启动器进行驱动程序的所有配置。 您尤其需要调整 url 和身份验证的属性,参见清单 109org.neo4j.ogm.config.Configuration

不能通过 XML 配置 SDN。 如果您使用SDN + OGM应用程序执行此操作,请确保您了解Spring应用程序的注释驱动或功能配置。 如今最简单的选择是Spring Boot。 有了我们的启动器,除了连接 URL 和身份验证之外的所有必要位都已为您配置。

清单 109.新旧物业比较

# Old
spring.data.neo4j.embedded.enabled=false # No longer supported
spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=secret

# New
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret

当 SDN 和驱动程序最终完全替换旧设置时,这些新属性将来可能会再次更改。

最后,添加新的依赖项,请参阅第 7 章了解 Gradle 和 Maven。

然后,您就可以替换批注了:


新增功能

​org.neo4j.ogm.annotation.NodeEntity​

​org.springframework.data.neo4j.core.schema.Node​

​org.neo4j.ogm.annotation.GeneratedValue​

​org.springframework.data.neo4j.core.schema.GeneratedValue​

​org.neo4j.ogm.annotation.Id​

​org.springframework.data.neo4j.core.schema.Id​

​org.neo4j.ogm.annotation.Property​

​org.springframework.data.neo4j.core.schema.Property​

​org.neo4j.ogm.annotation.Relationship​

​org.springframework.data.neo4j.core.schema.Relationship​

​org.springframework.data.neo4j.annotation.EnableBookmarkManagement​

无需更换,无需

​org.springframework.data.neo4j.annotation.UseBookmark​

无需更换,无需

​org.springframework.data.neo4j.annotation.QueryResult​

使用投影;不再支持任意结果映射

一些 Neo4j-OGM 注释在 SDN 中还没有相应的注释,有些永远不会有。 我们将添加到上面的列表中,因为我们支持其他功能。

书签管理

Bothandas 以及接口及其唯一的实现已经消失,不再需要了。​​@EnableBookmarkManagement​​​​@UseBookmark​​​​org.springframework.data.neo4j.bookmark.BookmarkManager​​​​org.springframework.data.neo4j.bookmark.CaffeineBookmarkManager​

SDN 对所有事务使用书签,无需配置。 您可以删除 bean 声明以及依赖项。​​CaffeineBookmarkManager​​​​com.github.ben-manes.caffeine:caffeine​

如果绝对必须,可以按照这些说明禁用自动书签管理。

自动创建约束和索引

SDN 5.3 及更早版本提供了来自 Neo4j-OGM 的“自动索引管理器”。

@Index,并且已被移除,无需更换。 为什么? 我们认为创建架构 - 即使对于无模式数据库 - 也不是域建模的一部分。 你可能会争辩说SDN模型是模式,但比我们回答说我们甚至更喜欢命令-查询分离, 这意味着我们宁愿定义单独的读取和写入模型。 这些对于写“无聊”的东西和阅读图形形状的答案非常方便。@CompositeIndex@Required

除此之外,其中一些注释的值分别与特定的Neo4j版本或版本相关联,这使得它们 难以维护。

然而,最好的论据是生产:虽然所有生成模式的工具在开发过程中确实很有帮助,但对于强制执行严格方案的数据库更是如此, 它们在生产环境中往往不是那么好:您如何处理同时运行的不同版本的应用程序? 版本 A 断言由较新版本 B 创建的索引?

我们认为最好预先控制这一点,并建议使用基于Liquigraph或Neo4j迁移等工具的受控数据库迁移。 后者已经在JHipster项目中与SDN一起使用。 这两个项目的共同点是,它们将架构的当前版本存储在数据库中,并确保架构在更新之前符合预期。

从以前的 Neo4j-OGM 注释迁移出来会影响,清单 110 中给出了一个示例:​​@Index​​​​@CompositeIndex​​​​@Required​

清单 110.利用 Neo4j-OGM 自动索引管理器的类

@CompositeIndex(properties = {"tagline", "released"})
public class Movie {

@Id @GeneratedValue Long id;

@Index(unique = true)
private String title;

private String description;

private String tagline;

@Required
private Integer released;
}

它的注释等效于Cypher中的以下方案(从Neo4j 4.2开始):

清单 111.基于密码的迁移示例

CREATE CONSTRAINT movies_unique_title ON (m:Movie) ASSERT m.title IS UNIQUE;
CREATE CONSTRAINT movies_released_exists ON (m:Movie) ASSERT EXISTS (m.released);
CREATE INDEX movies_tagline_released_idx FOR (m:Movie) ON (m.tagline, m.released);

使用没有等同于。 请注意,唯一索引已经意味着索引。​​@Index​​​​unique = true​​​​CREATE INDEX movie_title_index FOR (m:Movie) ON (m.title)​

构建弹簧数据 Neo4j

要求

  • JDK 8+(可以是OpenJDK或Oracle JDK)
  • Maven 3.6.2(我们提供 Maven 包装器,分别参见项目根目录;包装器会自动下载相应的 Maven 版本)mvnwmvnw.cmd
  • 一个 Neo4j 3.5.+ 数据库,要么
  • 在本地运行
  • 或间接通过Testcontainers和Docker
关于 JDK 版本

选择 JDK 8 是一个受多个方面影响的决定

  • SDN是一个Spring Data项目。 Spring Data commons 基线仍然是 JDK 8,Spring Framework 的基线也是如此。 因此,保持 JDK 8 基线是很自然的。
  • 虽然从JDK 11(这是Oracle当前的JavaLTS版本)开始的项目有所增加,但许多现有项目仍在JDK 8上。我们不想从一开始就失去他们作为用户。

运行构建

以下部分是替代方法,并按增加的工作量大致排序。

所有生成都需要项目的本地副本:

清单 112.克隆安全网络

$ git clone git@github.com:spring-projects/spring-data-neo4j.git

在继续操作之前,请验证本地安装的 JDK 版本。 输出应类似:

清单 113.验证您的 JDK

$ java -version
java version "12.0.1" 2019-04-16
Java(TM) SE Runtime Environment (build 12.0.1+12)
Java HotSpot(TM) 64-Bit Server VM (build 12.0.1+12, mixed mode, sharing)
安装了 Docker
使用默认图像

如果您没有安装Docker,请转到Docker Desktop。 简而言之,Docker 是一种工具,可帮助您在所谓的容器中使用操作系统级虚拟化运行轻量级软件映像。

我们的构建使用Testcontainers Neo4j来启动数据库实例。

清单 114.在 Linux / macOS 上使用默认设置构建

$ ./mvnw clean verify

在 Windows 计算机上,使用

清单 115.在 Windows 上使用默认设置进行构建

$ mvnw.cmd clean verify

输出应相似。

使用其他图像

可以使用的映像版本可以通过如下所示的环境变量进行配置:

清单 116.使用不同的 Neo4j Docker 镜像构建

$ SDN_NEO4J_VERSION=3.5.11-enterprise SDN_NEO4J_ACCEPT_COMMERCIAL_EDITION=yes ./mvnw clean verify

这里我们使用的是3.5.11企业版,也接受许可协议。

如果以内联方式指定环境变量对您不起作用,请参阅您的操作系统或 shell 手册,了解如何定义环境变量。

针对本地运行的数据库

针对本地运行的数据库运行擦除其完整内容。

针对本地运行的数据库进行构建速度更快,因为它不会每次都重新启动容器。 我们在开发过程中经常这样做。

您可以在我们的下载中心免费获得Neo4j的副本。

请下载适用于您的操作系统的版本,然后按照说明启动它。 必需的步骤是在启动数据库后打开浏览器并转到​​http://localhost:7474​​,并将默认密码更改为您喜欢的密码。​​neo4j​

之后,您可以通过指定 localURL 来运行完整的构建:​​bolt​

清单 117.使用本地运行的数据库生成

$ SDN_NEO4J_URL=bolt://localhost:7687 SDN_NEO4J_PASSWORD=secret ./mvnw clean verify

控制生成的环境变量摘要

名字

默认值

意义

​SDN_NEO4J_VERSION​

3.5.6

要使用的 Neo4j docker 镜像版本,请参阅Neo4j Docker 官方镜像

​SDN_NEO4J_ACCEPT_COMMERCIAL_EDITION​

某些测试可能需要企业版的 Neo4j。 我们在内部针对企业版进行构建和测试,但我们不会强迫您 如果您不想接受许可证,请接受许可证。

​SDN_NEO4J_URL​

未设置

设置此环境允许连接到本地运行的 Neo4j 实例。 我们在开发过程中经常使用它。

​SDN_NEO4J_PASSWORD​

未设置

配置了的实例的用户的密码。​​neo4j​​​​SDN_NEO4J_URL​

您需要将两者设置为使用本地实例。​​SDN_NEO4J_URL​​​​SDN_NEO4J_PASSWORD​

格纹和好友

目前没有质量门来确保代码/测试比率保持原样,但请考虑在您的贡献中添加测试。

我们有一些相当温和的checkstyle规则,或多或少地执行默认的Java格式规则。 您的构建将因格式错误或未使用的导入等内容而中断。

附录 B:存储库查询关键字

支持的查询方法主题关键字

下表列出了 Spring 数据存储库查询派生机制通常支持的主题关键字,以表达谓词。 有关支持的关键字的确切列表,请参阅特定于商店的文档,因为此处列出的某些关键字可能在特定商店中不受支持。

表 3.查询主题关键字

关键词

描述

​find…By​​​, , , , , ​​read…By​​​​get…By​​​​query…By​​​​search…By​​​​stream…By​

常规查询方法通常返回存储库类型、主子类型或结果包装器(如),或任何其他特定于存储的结果包装器。可以用作,或与其他关键字结合使用。​​Collection​​​​Streamable​​​​Page​​​​GeoResults​​​​findBy…​​​​findMyDomainTypeBy…​

​exists…By​

存在投影,返回通常为结果。​​boolean​

​count…By​

计数投影返回数值结果。

​delete…By​​​, ​​remove…By​

删除查询方法不返回任何结果 () 或删除计数。​​void​

​…First<number>…​​​, ​​…Top<number>…​

将查询结果限制为第一个结果。此关键字可以出现在主题(和其他关键字)之间的任何位置。​​<number>​​​​find​​​​by​

​…Distinct…​

使用非重复查询仅返回唯一结果。请参阅特定于商店的文档是否支持该功能。此关键字可以出现在主题(和其他关键字)之间的任何位置。​​find​​​​by​

支持的查询方法谓词关键字和修饰符

下表列出了 Spring 数据存储库查询派生机制通常支持的谓词关键字。 但是,请参阅特定于商店的文档,了解支持的关键字的确切列表,因为此处列出的某些关键字可能在特定商店中不受支持。

表 4.查询谓词关键字

逻辑关键字

关键字表达式

​AND​

​And​

​OR​

​Or​

​AFTER​

​After​​​, ​​IsAfter​

​BEFORE​

​Before​​​, ​​IsBefore​

​CONTAINING​

​Containing​​​, , ​​IsContaining​​​​Contains​

​BETWEEN​

​Between​​​, ​​IsBetween​

​ENDING_WITH​

​EndingWith​​​, , ​​IsEndingWith​​​​EndsWith​

​EXISTS​

​Exists​

​FALSE​

​False​​​, ​​IsFalse​

​GREATER_THAN​

​GreaterThan​​​, ​​IsGreaterThan​

​GREATER_THAN_EQUALS​

​GreaterThanEqual​​​, ​​IsGreaterThanEqual​

​IN​

​In​​​, ​​IsIn​

​IS​

​Is​​​,,(或无关键字)​​Equals​

​IS_EMPTY​

​IsEmpty​​​, ​​Empty​

​IS_NOT_EMPTY​

​IsNotEmpty​​​, ​​NotEmpty​

​IS_NOT_NULL​

​NotNull​​​, ​​IsNotNull​

​IS_NULL​

​Null​​​, ​​IsNull​

​LESS_THAN​

​LessThan​​​, ​​IsLessThan​

​LESS_THAN_EQUAL​

​LessThanEqual​​​, ​​IsLessThanEqual​

​LIKE​

​Like​​​, ​​IsLike​

​NEAR​

​Near​​​, ​​IsNear​

​NOT​

​Not​​​, ​​IsNot​

​NOT_IN​

​NotIn​​​, ​​IsNotIn​

​NOT_LIKE​

​NotLike​​​, ​​IsNotLike​

​REGEX​

​Regex​​​, , ​​MatchesRegex​​​​Matches​

​STARTING_WITH​

​StartingWith​​​, , ​​IsStartingWith​​​​StartsWith​

​TRUE​

​True​​​, ​​IsTrue​

​WITHIN​

​Within​​​, ​​IsWithin​

除了筛选器谓词之外,还支持以下修饰符列表:

表 5.查询谓词修饰符关键字

关键词

描述

​IgnoreCase​​​, ​​IgnoringCase​

与谓词关键字一起使用,用于不区分大小写的比较。

​AllIgnoreCase​​​, ​​AllIgnoringCase​

忽略所有合适属性的大小写。在查询方法谓词中的某处使用。

​OrderBy…​

指定静态排序顺序,后跟属性路径和方向(例如)。​​OrderByFirstnameAscLastnameDesc​

附录 C:存储库查询返回类型

支持的查询返回类型

下表列出了 Spring 数据存储库通常支持的返回类型。 但是,请参阅特定于商店的文档以获取支持的返回类型的确切列表,因为此处列出的某些类型可能在特定商店中不受支持。


地理空间类型(如、和)仅适用于支持地理空间查询的数据存储。 某些存储模块可能会定义自己的结果包装器类型。​​GeoResult​​​​GeoResults​​​​GeoPage​

表 6.查询返回类型

返回类型

描述

​void​

表示无返回值。

Java 原语。

包装器类型

Java 包装器类型。

​T​

一个独特的实体。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​null​​​​IncorrectResultSizeDataAccessException​

​Iterator<T>​

一。​​Iterator​

​Collection<T>​

一个。​​Collection​

​List<T>​

一个。​​List​

​Optional<T>​

爪哇8或番石榴。期望查询方法最多返回一个结果。如果未找到结果,则返回 oris 。多个结果触发 an.​​Optional​​​​Optional.empty()​​​​Optional.absent()​​​​IncorrectResultSizeDataAccessException​

​Option<T>​

要么是斯卡拉,要么是vavrtype。在语义上与前面描述的Java 8的行为相同。​​Option​​​​Optional​

​Stream<T>​

A Java 8 .​​Stream​

​Streamable<T>​

该直接的便利扩展公开了流式传输,映射和过滤结果,连接它们等的方法。​​Iterable​

实现和采用构造函数或工厂方法参数的类型​​Streamable​​​​Streamable​

公开采用 aas 参数的构造函数或/工厂方法的类型。有关详细信息,请参见第 9.4.6.2 节​。​​….of(…)​​​​….valueOf(…)​​​​Streamable​

瓦夫尔,,,​​Seq​​​​List​​​​Map​​​​Set​

Vavr 集合类型。有关详细信息,请参见​​第 9.4.6.3 节​​。

​Future<T>​

A. 期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。​​Future​​​​@Async​

​CompletableFuture<T>​

A Java 8.期望对方法进行注释,并且需要启用 Spring 的异步方法执行功能。​​CompletableFuture​​​​@Async​

​Slice<T>​

一个大小的数据块,指示是否有更多可用数据。需要方法参数。​​Pageable​

​Page<T>​

A 包含其他信息,例如结果总数。需要方法参数。​​Slice​​​​Pageable​

​GeoResult<T>​

包含附加信息(如到参考位置的距离)的结果条目。

​GeoResults<T>​

包含附加信息的列表,例如到参考位置的平均距离。​​GeoResult<T>​

​GeoPage<T>​

Awith,例如到参考位置的平均距离。​​Page​​​​GeoResult<T>​

​Mono<T>​

使用反应式存储库发射零个或一个元素的项目反应器。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Mono​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Flux<T>​

使用反应式存储库发射零个、一个或多个元素的项目反应器。返回的查询还可以发出无限数量的元素。​​Flux​​​​Flux​

​Single<T>​

一个 RxJava使用反应式存储库发出单个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Single​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Maybe<T>​

使用反应式存储库的 RxJavaemitting 零个或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回。多个结果触发 an.​​Maybe​​​​Mono.empty()​​​​IncorrectResultSizeDataAccessException​

​Flowable<T>​

使用反应式存储库的 RxJavaemitting 零个、一个或多个元素。返回的查询还可以发出无限数量的元素。​​Flowable​​​​Flowable​