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

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

若要充分利用 SDN 中的对象映射功能,应使用注释对映射对象进行注释。 尽管映射框架没有必要具有此注释(即使没有任何注释,您的 POJO 也会正确映射),但它允许类路径扫描程序查找并预处理您的域对象以提取必要的元数据。 如果不使用此批注,则应用程序在首次存储域对象时会受到轻微的性能影响,因为映射框架需要构建其内部元数据模型,以便了解域对象的属性以及如何持久化它们。@NodeSpring中文文档

映射注释概述

从 SDN

  • @Node:在类级别应用,以指示此类是映射到数据库的候选项。Spring中文文档

  • @Id:应用于字段级别,以标记用于标识目的的字段。Spring中文文档

  • @GeneratedValue:在字段级别应用,并指定应如何生成唯一标识符。@IdSpring中文文档

  • @Property:应用于字段级别以修改从属性到属性的映射。Spring中文文档

  • @CompositeProperty:在字段级别应用于应作为复合回读的 Map 类型的属性。请参见复合属性Spring中文文档

  • @Relationship:应用于字段级别以指定关系的详细信息。Spring中文文档

  • @DynamicLabels:应用于字段级别以指定动态标签的来源。Spring中文文档

  • @RelationshipProperties:在类级别应用,以指示此类作为关系属性的目标。Spring中文文档

  • @TargetNode:应用于类的字段,该字段带有注释,以从另一端的角度标记该关系的目标。@RelationshipPropertiesSpring中文文档

以下注释用于指定转换并确保与 OGM 的向后兼容性。Spring中文文档

有关更多信息,请参阅转换Spring中文文档

来自Spring Data commons

  • @org.springframework.data.annotation.Id实际上,与SDN相同,是用Spring Data Common的Id-annotation进行注释的。@Id@IdSpring中文文档

  • @CreatedBy:应用于字段级别以指示节点的创建者。Spring中文文档

  • @CreatedDate:应用于字段级别,以指示节点的创建日期。Spring中文文档

  • @LastModifiedBy:应用于字段级别,以指示上次对节点进行更改的作者。Spring中文文档

  • @LastModifiedDate:应用于字段级别,以指示节点的上次修改日期。Spring中文文档

  • @PersistenceCreator:应用于一个构造函数,以在读取实体时将其标记为首选构造函数。Spring中文文档

  • @Persistent:在类级别应用,以指示此类是映射到数据库的候选项。Spring中文文档

  • @Version:应用于字段级别,用于乐观锁定,并检查保存操作的修改。 初始值为零,每次更新时都会自动增加。Spring中文文档

  • @ReadOnlyProperty:在字段级别应用以将属性标记为只读。在数据库读取期间,该属性将被冻结, 但不受写入的影响。在关系上使用时,请注意,该集合中的任何相关实体都不会被保留 如果没有其他关系。Spring中文文档

查看审核,了解有关审核支持的所有注释。Spring中文文档

基本构建块:@Node

注释用于将类标记为托管域类,并受映射上下文的类路径扫描的约束。@NodeSpring中文文档

为了将 Object 映射到图中的节点,反之亦然,我们需要一个标签来标识要映射到和从中映射的类。Spring中文文档

@Node具有一个属性,允许您配置在读取和写入带批注类的实例时要使用的一个或多个标签。 该属性是 的别名。 如果未指定标签,则简单类名将用作主标签。 如果要提供多个标签,可以:labelsvaluelabelsSpring中文文档

  1. 向属性提供数组。 数组中的第一个元素将被视为主标签。labelsSpring中文文档

  2. 提供 的值,并将其他标签放在 中。primaryLabellabelsSpring中文文档

主标签应始终是反映域类的最具体标签。Spring中文文档

对于通过存储库或 Neo4j 模板编写的带注释类的每个实例,将写入图中至少具有主标签的一个节点。 反之亦然,所有带有主标签的节点都将映射到带注释的类的实例。Spring中文文档

关于类层次结构的说明

注释不是从超类型和接口继承的。 但是,您可以在每个继承级别单独注释域类。 这允许多态查询:您可以传入基类或中间类,并检索节点的正确具体实例。 这仅支持用 注释的抽象库。 在此类上定义的标签将与具体实现的标签一起用作附加标签。@Node@NodeSpring中文文档

对于某些方案,我们还支持域类层次结构中的接口:Spring中文文档

域模型在单独的模块中,与接口名称相同的主标签
public interface SomeInterface { (1)

    String getName();

    SomeInterface getRelated();
}

@Node("SomeInterface") (2)
public static class SomeInterfaceEntity implements SomeInterface {

    @Id
    @GeneratedValue
    private Long id;

    private final String name;

    private SomeInterface related;

    public SomeInterfaceEntity(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public SomeInterface getRelated() {
        return related;
    }
}
1 只是普通的接口名称,就像您命名域一样
2 由于我们需要同步主标签,我们把实现类,它 可能在另一个模块中。请注意,该值与接口名称完全相同 实现。无法重命名。@Node

也可以使用不同的主标签而不是接口名称:Spring中文文档

不同的主标签
@Node("PrimaryLabelWN") (1)
public interface SomeInterface2 {

    String getName();

    SomeInterface2 getRelated();
}

public static class SomeInterfaceEntity2 implements SomeInterface2 {

    // Overrides omitted for brevity
}
1 将注释放在界面上@Node

也可以使用接口的不同实现,并具有多态域模型。 执行此操作时,至少需要两个标签:一个用于确定接口的标签,一个用于确定具体类的标签:Spring中文文档

多种实现
@Node("SomeInterface3") (1)
public interface SomeInterface3 {

    String getName();

    SomeInterface3 getRelated();
}

@Node("SomeInterface3a") (2)
public static class SomeInterfaceImpl3a implements SomeInterface3 {

    // Overrides omitted for brevity
}
@Node("SomeInterface3b") (3)
public static class SomeInterfaceImpl3b implements SomeInterface3 {

    // Overrides omitted for brevity
}

@Node
public static class ParentModel { (4)

    @Id
    @GeneratedValue
    private Long id;

    private SomeInterface3 related1; (5)

    private SomeInterface3 related2;
}
1 在此方案中,需要显式指定标识接口的标签
2 这适用于第一个...
3 以及第二次实施
4 这是一个客户端或父模型,透明地用于两个关系SomeInterface3
5 未指定具体类型

所需的数据结构如以下测试所示。OGM 也会写同样的内容:Spring中文文档

使用多个不同接口实现所需的数据结构
Long id;
try (Session session = driver.session(bookmarkCapture.createSessionConfig()); Transaction transaction = session.beginTransaction()) {
    id = transaction.run("" +
        "CREATE (s:ParentModel{name:'s'}) " +
        "CREATE (s)-[:RELATED_1]-> (:SomeInterface3:SomeInterface3b {name:'3b'}) " +
        "CREATE (s)-[:RELATED_2]-> (:SomeInterface3:SomeInterface3a {name:'3a'}) " +
        "RETURN id(s)")
        .single().get(0).asLong();
    transaction.commit();
}

Optional<Inheritance.ParentModel> optionalParentModel = transactionTemplate.execute(tx ->
        template.findById(id, Inheritance.ParentModel.class));

assertThat(optionalParentModel).hasValueSatisfying(v -> {
    assertThat(v.getName()).isEqualTo("s");
    assertThat(v).extracting(Inheritance.ParentModel::getRelated1)
            .isInstanceOf(Inheritance.SomeInterfaceImpl3b.class)
            .extracting(Inheritance.SomeInterface3::getName)
            .isEqualTo("3b");
    assertThat(v).extracting(Inheritance.ParentModel::getRelated2)
            .isInstanceOf(Inheritance.SomeInterfaceImpl3a.class)
            .extracting(Inheritance.SomeInterface3::getName)
            .isEqualTo("3a");
});
接口无法定义标识符字段。 因此,它们不是存储库的有效实体类型。

动态或“运行时”管理标签

通过简单类名或通过注释显式定义的所有标签都是静态的。 在运行时无法更改它们。 如果需要可在运行时操作的其他标签,可以使用 . 是字段级别的注释,并将类型(例如 a 或 )的属性标记为动态标签的来源。@Node@DynamicLabels@DynamicLabelsjava.util.Collection<String>ListSetSpring中文文档

如果存在此注释,则节点上存在的所有标签(未静态映射 via 和类名)将在加载期间收集到该集合中。 在写入过程中,节点的所有标签都将替换为静态定义的标签以及集合的内容。@NodeSpring中文文档

如果您有其他应用程序向节点添加其他标签,请不要使用 . 如果存在于托管实体上,则生成的标签集将是写入数据库的“真相”。@DynamicLabels@DynamicLabels
1 只是普通的接口名称,就像您命名域一样
2 由于我们需要同步主标签,我们把实现类,它 可能在另一个模块中。请注意,该值与接口名称完全相同 实现。无法重命名。@Node
1 将注释放在界面上@Node
1 在此方案中,需要显式指定标识接口的标签
2 这适用于第一个...
3 以及第二次实施
4 这是一个客户端或父模型,透明地用于两个关系SomeInterface3
5 未指定具体类型
接口无法定义标识符字段。 因此,它们不是存储库的有效实体类型。
如果您有其他应用程序向节点添加其他标签,请不要使用 . 如果存在于托管实体上,则生成的标签集将是写入数据库的“真相”。@DynamicLabels@DynamicLabels

识别实例:@Id

在创建类和具有特定标签的节点之间的映射时,我们还需要在该类(对象)的各个实例和节点的实例之间建立连接。@NodeSpring中文文档

这就是发挥作用的地方。 将类的属性标记为对象的唯一标识符。 在最佳世界中,该唯一标识符是唯一的业务密钥,或者换句话说,是自然密钥。 可用于具有受支持的简单类型的所有属性。@Id@Id@IdSpring中文文档

然而,自然钥匙很难找到。 例如,人名很少是唯一的,会随着时间的推移而变化,或者更糟的是,不是每个人都有名字和姓氏。Spring中文文档

因此,我们支持两种不同类型的代理键Spring中文文档

在 或 类型的属性上,可以与 一起使用 。 并映射到 Neo4j 内部 ID。 映射到自 Neo4j 5 以来可用的 elementId。 两者都不是节点或关系上的属性,通常对属性不可见,并允许 SDN 检索类的单个实例。StringlongLong@Id@GeneratedValueLonglongStringSpring中文文档

@GeneratedValue提供属性 。 可用于指定实现 的类。 An 是一个功能接口,它采用主标签和实例来生成 Id。 我们支持开箱即用的实现。generatorClassgeneratorClassIdGeneratorIdGeneratorgenerateIdUUIDStringGeneratorSpring中文文档

您还可以从 via 上的应用程序上下文中指定 Spring Bean。 该 bean 也需要实现 ,但可以利用上下文中的所有内容,包括 Neo4j 客户端或模板来与数据库交互。@GeneratedValuegeneratorRefIdGeneratorSpring中文文档

不要跳过唯一 ID 的处理和预配中有关 ID 处理的重要说明
不要跳过唯一 ID 的处理和预配中有关 ID 处理的重要说明

乐观锁定:@Version

Spring Data Neo4j 通过在类型化字段上使用注释来支持乐观锁定。 此属性将在更新期间自动递增,不得手动修改。@VersionLongSpring中文文档

例如,如果不同线程中的两个事务想要修改具有版本的同一对象,则第一个操作将成功保存到数据库中。 此时,版本字段将递增,因此是 。 第二个操作将失败,并显示 a,因为它想要使用数据库中不再存在的版本修改对象。 在这种情况下,需要重试操作,首先从数据库中重新提取具有当前版本的对象。xx+1OptimisticLockingFailureExceptionxSpring中文文档

如果使用商家 ID,则该属性也是必需的。 Spring Data Neo4j 将检查此字段以确定该实体是新的还是之前已经持久化。@VersionSpring中文文档

映射属性:@Property

-annotated 类的所有属性都将作为 Neo4j 节点和关系的属性持久化。 如果不进一步配置,Java 或 Kotlin 类中的属性名称将用作 Neo4j 属性。@NodeSpring中文文档

如果您正在使用现有的 Neo4j 模式,或者只是想根据您的需要调整映射,则需要使用 . 用于指定数据库内属性的名称。@PropertynameSpring中文文档

连接节点:@Relationship

注释可用于所有非简单类型的属性。 它适用于用其集合和映射注释的其他类型的属性。@Relationship@NodeSpring中文文档

or 属性允许配置关系类型,允许指定方向。 SDN 中的默认方向为 。typevaluedirectionRelationship.Direction#OUTGOINGSpring中文文档

我们支持动态关系。 动态关系表示为 或 。 在这种情况下,与其他域类的关系类型由 maps 键给出,不得通过 .Map<String, AnnotatedDomainClass>Map<Enum, AnnotatedDomainClass>@RelationshipSpring中文文档

映射关系属性

Neo4j 不仅支持在节点上定义属性,还支持在关系上定义属性。 在SDN提供的模型中表示这些属性,以应用于简单的Java类。 在属性类中,必须有一个标记为定义关系指向的实体的字段。 或者,在关系上下文中,来自。@RelationshipProperties@TargetNodeINCOMINGSpring中文文档

关系属性类及其用法可能如下所示:Spring中文文档

关系属性Roles
@RelationshipProperties
public class Roles {

	@RelationshipId
	private Long id;

	private final List<String> roles;

	@TargetNode
	private final PersonEntity person;

	public Roles(PersonEntity person, List<String> roles) {
		this.person = person;
		this.roles = roles;
	}


	public List<String> getRoles() {
		return roles;
	}

	@Override
	public String toString() {
		return "Roles{" +
				"id=" + id +
				'}' + this.hashCode();
	}
}

必须为生成的内部 ID () 定义属性,以便 SDN 可以在保存期间确定哪些关系 可以安全地覆盖而不会丢失属性。 如果 SDN 找不到用于存储内部节点 ID 的字段,则在启动过程中将失败。@RelationshipIdSpring中文文档

定义实体的关系属性
@Relationship(type = "ACTED_IN", direction = Direction.INCOMING) (1)
private List<Roles> actorsAndRoles = new ArrayList<>();

关系查询备注

通常,创建查询对关系/跃点没有限制。 SDN 分析来自建模节点的整个可访问图。Spring中文文档

也就是说,当存在双向映射关系的想法时,这意味着您在实体的两端定义关系, 你可能会得到比你预期的更多。Spring中文文档

考虑一个示例,其中一部电影有演员,并且您想要获取某部电影及其所有演员。 如果从电影演员的关系只是单向的,这不会有问题。 在双向方案中,SDN 将获取特定电影、其 Actor,以及根据关系的定义为该 Actor 定义的其他电影。 在最坏的情况下,这将级联到获取单个实体的整个图。Spring中文文档

一个完整的例子

把所有这些放在一起,我们可以创建一个简单的域。 我们使用电影和具有不同角色的人:Spring中文文档

例 1.这MovieEntity
import java.util.ArrayList;
import java.util.List;

import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Property;
import org.springframework.data.neo4j.core.schema.Relationship;
import org.springframework.data.neo4j.core.schema.Relationship.Direction;

@Node("Movie") (1)
public class MovieEntity {

	@Id (2)
	private final String title;

	@Property("tagline") (3)
	private final String description;

	@Relationship(type = "ACTED_IN", direction = Direction.INCOMING) (4)
	private List<Roles> actorsAndRoles = new ArrayList<>();

	@Relationship(type = "DIRECTED", direction = Direction.INCOMING)
	private List<PersonEntity> directors = new ArrayList<>();

	public MovieEntity(String title, String description) { (5)
		this.title = title;
		this.description = description;
	}

	// Getters omitted for brevity
}
1 @Node用于将此类标记为托管实体。 它还用于配置 Neo4j 标签。 标签默认为类的名称,如果您只使用 plain .@Node
2 每个实体都必须有一个 ID。 我们使用电影的名称作为唯一标识符。
3 这显示为一种为字段使用与图形属性不同的名称的方法。@Property
4 这将配置与某人的传入关系。
5 这是应用程序代码和 SDN 要使用的构造函数。

在这里,人们被映射到两个角色,以及 . 域类是相同的:actorsdirectorsSpring中文文档

例 2.这PersonEntity
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;

@Node("Person")
public class PersonEntity {

	@Id private final String name;

	private final Integer born;

	public PersonEntity(Integer born, String name) {
		this.born = born;
		this.name = name;
	}

	public Integer getBorn() {
		return born;
	}

	public String getName() {
		return name;
	}

}
我们还没有在两个方向上模拟电影和人之间的关系。 为什么? 我们将其视为聚合根,拥有关系。 另一方面,我们希望能够从数据库中提取所有人,而无需选择与他们关联的所有电影。 在尝试在各个方向映射数据库中的每个关系之前,请考虑应用程序的用例。 虽然你可以这样做,但你最终可能会在你的对象图中重建一个图数据库,这不是映射框架的意图。 如果您必须对圆形或双向域进行建模,并且不想获取整个图形, 您可以使用投影定义要提取的数据的细化描述。MovieEntity
1 @Node用于将此类标记为托管实体。 它还用于配置 Neo4j 标签。 标签默认为类的名称,如果您只使用 plain .@Node
2 每个实体都必须有一个 ID。 我们使用电影的名称作为唯一标识符。
3 这显示为一种为字段使用与图形属性不同的名称的方法。@Property
4 这将配置与某人的传入关系。
5 这是应用程序代码和 SDN 要使用的构造函数。
我们还没有在两个方向上模拟电影和人之间的关系。 为什么? 我们将其视为聚合根,拥有关系。 另一方面,我们希望能够从数据库中提取所有人,而无需选择与他们关联的所有电影。 在尝试在各个方向映射数据库中的每个关系之前,请考虑应用程序的用例。 虽然你可以这样做,但你最终可能会在你的对象图中重建一个图数据库,这不是映射框架的意图。 如果您必须对圆形或双向域进行建模,并且不想获取整个图形, 您可以使用投影定义要提取的数据的细化描述。MovieEntity