最新的稳定版本请使用 Spring Data Neo4j 7.3.1Spring中文文档

最新的稳定版本请使用 Spring Data Neo4j 7.3.1Spring中文文档

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

由于 Spring Data Neo4j 在后台工作在很大程度上是面向记录的,因此请务必牢记这一点,而不是为同一“根节点”构建具有多个记录的结果集。Spring中文文档

请查看常见问题解答,了解使用存储库自定义查询的替代形式,特别是 如何将自定义查询与自定义映射结合使用:自定义查询和自定义映射
请查看常见问题解答,了解使用存储库自定义查询的替代形式,特别是 如何将自定义查询与自定义映射结合使用:自定义查询和自定义映射

关系查询

谨防笛卡尔积

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

多条记录(缩短)
+------------------------------------------------------------------------------------------+
| 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"})       |
+------------------------------------------------------------------------------------------+

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

每个根节点获取一条记录

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

单条记录(缩短)
+------------------------------------------------------------------------+
| m        | collect(r)                     | collect(p)                 |
+------------------------------------------------------------------------+
| (:Movie) | [[:ACTED_IN], [:ACTED_IN], ...]| [(:Person), (:Person),...] |
+------------------------------------------------------------------------+

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

更深入地了解图表

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

为此,上面的示例还应包含 on that get 返回首字母 .MoviesPersonsMovieSpring中文文档

image$movie graph deep
图 1.《黑客帝国》和《基努·里维斯》的例子

数据库端缩减

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

朴素的基于路径的方法
MATCH p=(m:Movie{title: 'The Matrix'})<-[:ACTED_IN]-(:Person)-[:ACTED_IN*..0]->(:Movie)
RETURN p;

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

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

因为从《黑客帝国》到另一部电影有多条路径,所以结果仍然不会是单一的记录。 这就是 Cypher 的 reduce 函数发挥作用的地方。Spring中文文档

减少节点和关系
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;

该函数允许我们展平来自各种路径的节点和关系。 因此,我们将得到一个类似于每个根节点获取一条记录的元组,但集合中混合了关系类型或节点。reduceSpring中文文档

客户端减少

如果缩减应该在客户端进行,Spring Data Neo4j 还允许您映射关系或节点列表的列表。 尽管如此,要求仍然适用返回的记录应包含所有信息,以正确冻结生成的实体实例。Spring中文文档

从路径中收集节点和关系
MATCH p=(m:Movie{title: 'The Matrix'})<-[:ACTED_IN]-(:Person)-[:ACTED_IN*..0]->(:Movie)
RETURN m, collect(nodes(p)), collect(relationships(p));

附加语句创建的列表格式如下:collectSpring中文文档

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

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

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

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

给出的是一个如下所示的图表:Spring中文文档

image$自定义查询.paths
图2.具有传出关系的图形

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

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

正如你所看到的,关系只是外向的。生成的查找器方法(包括 )将始终尝试匹配 要映射的根节点。从那时起,将映射所有相关对象。在只返回一个对象的查询中, 返回该根对象。在返回多个对象的查询中,将返回所有匹配的对象。传出和传入关系 当然,从这些对象返回的对象是填充的。findByIdSpring中文文档

假设以下 Cypher 查询:Spring中文文档

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

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

这可以通过返回整个路径来更改:Spring中文文档

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

在这里,我们确实想使用这样一个事实,即路径实际上返回 3 行,其中包含指向所有 4 个节点的路径。所有 4 个节点都将是 填充、链接在一起并返回。pSpring中文文档

自定义查询中的参数

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

ARepository.java
public interface ARepository extends Neo4jRepository<AnAggregateRoot, String> {

	@Query("MATCH (a:AnAggregateRoot {name: $name}) RETURN a") (1)
	Optional<AnAggregateRoot> findByCustomQuery(String name);
}
1 在这里,我们通过其名称来指代参数。 您也可以改用 etc.。$0
您需要编译 Java 8+ 项目,以使命名参数无需进一步的注释即可工作。 Spring Boot Maven 和 Gradle 插件会自动为您执行此操作。 如果由于任何原因不可行,则可以显式添加和指定名称,也可以使用参数索引。-parameters@Param

映射的实体(所有带有 )的实体作为参数传递给用 自定义查询将转换为嵌套地图。 以下示例将结构表示为 Neo4j 参数。@NodeSpring中文文档

给定 和 注释的类,如电影模型所示MovieVertexActorSpring中文文档

“标准”电影模型
@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 映射参数:MovieSpring中文文档

{
  "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 编写。 值将具有正确的密码类型,并且不需要进一步转换。idlabelsSpring中文文档

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

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

1 在这里,我们通过其名称来指代参数。 您也可以改用 etc.。$0
您需要编译 Java 8+ 项目,以使命名参数无需进一步的注释即可工作。 Spring Boot Maven 和 Gradle 插件会自动为您执行此操作。 如果由于任何原因不可行,则可以显式添加和指定名称,也可以使用参数索引。-parameters@Param
所有关系都是地图列表。动态关系将得到相应的解决。 一对一关系也将序列化为单例列表。因此,要访问一对一的映射 在人与人之间,你会写这个 das .$person.__properties__.BEST_FRIEND[0].__target__.__id__

自定义查询中的 Spring Expression Language

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

下面的示例基本上定义了与上面相同的查询,但使用了一个子句来避免更多的大括号:WHERESpring中文文档

ARepository.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 块以 () 开头,然后按名称 () 引用给定参数。 不要将其与上面的Cypher语法混淆! SpEL 表达式将两个参数连接成一个值,该值最终传递给 appendix/neo4j-client.adoc#neo4j-client。 SpEL 模块以 . 结尾。:#{String#pt1}Spring中文文档

SpEL 还解决了另外两个问题。我们提供了两个扩展,允许将对象传入自定义查询。 还记得自定义查询中的 faq.adoc#custom-queries-with-page-and-slice-examples 吗? 使用扩展,可以将具有动态排序的 Custom Sorter 传递给自定义查询:SortorderByPageableSpring中文文档

orderBy-扩展
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;

public interface MyPersonRepository extends Neo4jRepository<Person, Long> {

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

    @Query(""
        + "MATCH (n:Person) WHERE n.name = $name RETURN n :#{orderBy(#sort)}" (2)
    )
    List<Person> findAllByName(String name, Sort sort);
}
1 A 始终具有 SpEL 上下文中的名称。Pageablepageable
2 A 始终具有 SpEL 上下文中的名称。Sortsort

Spring Expression Language 扩展

文字扩展

该扩展可用于在自定义查询中使标签或关系类型等内容“动态”。 标签和关系类型都不能在 Cypher 中参数化,因此必须给出文字。literalSpring中文文档

文字扩展
interface BaseClassRepository extends Neo4jRepository<Inheritance.BaseClass, Long> {

    @Query("MATCH (n:`:#{literal(#label)}`) RETURN n") (1)
    List<Inheritance.BaseClass> findByLabel(String label);
}
1 扩展名将替换为计算参数的文本值。literal

在这里,该值已用于在标签上动态匹配。 如果作为参数传入到方法,将生成。已添加刻度以正确转义值。SDN 不会这样做 对你来说,这可能不是你在所有情况下都想要的。literalSomeLabelMATCH (n:SomeLabel) RETURN nSpring中文文档

列表扩展

对于多个值,存在并且存在将呈现 所有值的列表或串联列表。allOfanyOf&|Spring中文文档

列表扩展
interface BaseClassRepository extends Neo4jRepository<Inheritance.BaseClass, Long> {

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

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

参考标签

您已经知道如何将 Node 映射到域对象:Spring中文文档

具有多个标签的节点
@Node(primaryLabel = "Bike", labels = {"Gravel", "Easy Trail"})
public class BikeNode {
    @Id String id;

    String name;
}

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

#{#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);
}

此查询将解析为Spring中文文档

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

请注意我们如何使用标准参数: 在大多数情况下,这里没有必要通过以下方式使事情复杂化 添加 SpEL 表达式。nameOrIdSpring中文文档

1 A 始终具有 SpEL 上下文中的名称。Pageablepageable
2 A 始终具有 SpEL 上下文中的名称。Sortsort
1 扩展名将替换为计算参数的文本值。literal