此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data Neo4j 7.4.4!spring-doc.cadn.net.cn

基于元数据的映射

要充分利用 SDN 中的对象映射功能,您应该使用@Node注解。 尽管 Map 框架不需要具有此 Comments(即使没有任何 Comments,您的 POJO 也被正确映射),但它允许 Classpath 扫描器查找并预处理您的域对象以提取必要的元数据。 如果不使用此注释,则应用程序在首次存储域对象时的性能会受到轻微影响,因为映射框架需要构建其内部元数据模型,以便它了解域对象的属性以及如何持久保存它们。spring-doc.cadn.net.cn

映射注释概述

从 SDN

以下注释用于指定转换并确保与 OGM 的向后兼容性。spring-doc.cadn.net.cn

有关更多信息,请参阅 Conversionsspring-doc.cadn.net.cn

来自 Spring Data commons

  • @org.springframework.data.annotation.Id@Id事实上,从 SDN 来看,@Id使用 Spring Data Common 的 Id-annotation 进行注释。spring-doc.cadn.net.cn

  • @CreatedBy:在字段级别应用,以指示节点的创建者。spring-doc.cadn.net.cn

  • @CreatedDate:在字段级别应用,以指示节点的创建日期。spring-doc.cadn.net.cn

  • @LastModifiedBy:在字段级别应用,以指示对节点的上次更改的作者。spring-doc.cadn.net.cn

  • @LastModifiedDate:在字段级别应用,以指示节点的上次修改日期。spring-doc.cadn.net.cn

  • @PersistenceCreator:应用于一个构造函数,以将其标记为读取实体时的首选构造函数。spring-doc.cadn.net.cn

  • @Persistent:在类级别应用,以指示此类是映射到数据库的候选项。spring-doc.cadn.net.cn

  • @Version:应用于字段级别,用于乐观锁定,并检查保存作的修改。 初始值为 0,每次更新时都会自动递增。spring-doc.cadn.net.cn

  • @ReadOnlyProperty:在字段级别应用以将属性标记为只读。该属性将在数据库读取期间被水合, 但不受写入的约束。当用于关系时,请注意,该集合中的任何相关实体都不会被保留 如果没有其他相关。spring-doc.cadn.net.cn

请查看 审计 ,了解有关审计支持的所有注释。spring-doc.cadn.net.cn

基本构建块:@Node

@Nodeannotation 用于将类标记为托管域类,受 Mapping 上下文的 Classpath 扫描。spring-doc.cadn.net.cn

要将 Object 映射到图中的节点(反之亦然),我们需要一个标签来标识要映射到和从的类。spring-doc.cadn.net.cn

@Node具有属性labels这允许您配置一个或多个标签,以便在读取和写入 Annotated 类的实例时使用。 这valueattribute 是labels. 如果未指定标签,则简单类名将用作主标签。 如果要提供多个标签,则可以:spring-doc.cadn.net.cn

  1. labels财产。 数组中的第一个元素将被视为主标签。spring-doc.cadn.net.cn

  2. primaryLabel,然后将其他标签放入labels.spring-doc.cadn.net.cn

主标签应始终是反映您的域类的最具体标签。spring-doc.cadn.net.cn

对于通过存储库或 Neo4j 模板编写的带注释类的每个实例,将写入图中至少具有主标签的一个节点。 反之亦然,所有具有 primary 标签的节点都将映射到带 Comments 的类的实例。spring-doc.cadn.net.cn

关于类层次结构的说明

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

在某些情况下,我们还支持 domain-class-hierarchies 中的接口:spring-doc.cadn.net.cn

Domain model 的 Domain 模型,与接口名称相同的主标签
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在 implementation 类上,它 可能在另一个模块中。请注意,该值与接口的名称完全相同 实现。无法重命名。

也可以使用不同的主标签而不是接口名称:spring-doc.cadn.net.cn

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

    String getName();

    SomeInterface2 getRelated();
}

public static class SomeInterfaceEntity2 implements SomeInterface2 {

    // Overrides omitted for brevity
}
1 @Node接口上的注释

也可以使用接口的不同实现并拥有多晶态域模型。 执行此作时,至少需要两个标签:一个用于确定接口的标签,一个用于确定具体类的标签:spring-doc.cadn.net.cn

多种实现
@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-doc.cadn.net.cn

使用多个不同接口实现所需的数据结构
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");
});
接口无法定义标识符字段。 因此,它们不是存储库的有效实体类型。

动态或 “runtime” 托管标签

所有标签都通过简单的类名隐式定义,或通过@Node注释是静态的。 它们在运行时无法更改。 如果您需要可在运行时作的其他标签,则可以使用@DynamicLabels.@DynamicLabels是字段级别的注释,并标记java.util.Collection<String>(一个ListSet) 作为动态标签的源。spring-doc.cadn.net.cn

如果存在此注释,则所有标签都存在于节点上,而不是通过@Node和类名将在加载期间收集到该集合中。 在写入期间,节点的所有标签都将替换为静态定义的标签以及集合的内容。spring-doc.cadn.net.cn

如果您有其他应用程序向节点添加其他标签,请不要使用@DynamicLabels. 如果@DynamicLabels存在于托管实体上,则生成的标签集将是写入数据库的 “事实”。

识别实例:@Id

@Node在类和具有特定标签的节点之间创建映射,我们还需要在该类(对象)的各个实例和节点的实例之间建立连接。spring-doc.cadn.net.cn

这是@Id开始发挥作用。@Id将类的属性标记为对象的唯一标识符。 该唯一标识符在最佳世界中是唯一的业务密钥,或者换句话说,自然密钥。@Id可用于具有受支持的 simple 类型的所有属性。spring-doc.cadn.net.cn

然而,自然键很难找到。 例如,人们的名字很少是唯一的,会随着时间的推移而变化,或者更糟糕的是,不是每个人都有名字和姓氏。spring-doc.cadn.net.cn

因此,我们支持两种不同类型的代理键spring-doc.cadn.net.cn

String,longLong,@Id可与@GeneratedValue.Longlong映射到 Neo4j 内部 ID。String映射到自 Neo4j 5 以来可用的 elementId。 两者都不是节点或关系上的属性,通常对属性不可见,并允许 SDN 检索类的单个实例。spring-doc.cadn.net.cn

@GeneratedValue提供属性generatorClass.generatorClass可用于指定实现IdGenerator. 一IdGenerator是一个功能接口,其generateId获取主标签和实例以为其生成 Id 。 我们支持UUIDStringGenerator作为一个开箱即用的实现。spring-doc.cadn.net.cn

您还可以从应用程序上下文中指定 Spring Bean@GeneratedValue通过generatorRef. 该 bean 还需要实现IdGenerator,但可以使用上下文中的所有内容,包括 Neo4j 客户端或模板来与数据库交互。spring-doc.cadn.net.cn

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

乐观锁定:@Version

Spring Data Neo4j 通过使用@Version注解Long键入的字段。 此属性将在更新期间自动递增,并且不得手动修改。spring-doc.cadn.net.cn

例如,如果不同线程中的两个事务想要使用 version 修改同一个对象x,则第一个作将成功持久化到数据库中。 此时,version 字段将递增,因此它是x+1. 第二个作将失败,并显示OptimisticLockingFailureException,因为它想要修改x这在数据库中不再存在。 在这种情况下,需要重试该作,首先从数据库中重新提取具有当前版本的对象。spring-doc.cadn.net.cn

@Versionattribute 也是必需的。 Spring Data Neo4j 将检查此字段以确定该实体是新的还是之前已经持久化的。spring-doc.cadn.net.cn

映射属性:@Property

的所有属性@Node-annotated 类将持久化为 Neo4j 节点和关系的属性。 无需进一步配置,Java 或 Kotlin 类中的属性名称将用作 Neo4j 属性。spring-doc.cadn.net.cn

如果您正在使用现有的 Neo4j 模式,或者只是想根据您的需要调整映射,则需要使用@Property. 这name用于指定数据库中属性的名称。spring-doc.cadn.net.cn

连接节点:@Relationship

@Relationshipannotation 可用于非简单类型的所有属性。 它适用于用@Node或其集合和地图。spring-doc.cadn.net.cn

typevalue属性允许配置关系的类型,direction允许指定方向。 SDN 中的默认方向为Relationship.Direction#OUTGOING.spring-doc.cadn.net.cn

我们支持动态关系。 动态关系表示为Map<String, AnnotatedDomainClass>Map<Enum, AnnotatedDomainClass>. 在这种情况下,与其他域类的关系类型由 maps 键提供,不得通过@Relationship.spring-doc.cadn.net.cn

地图关系属性

Neo4j 不仅支持在节点上定义属性,还支持在关系上定义属性。 为了在模型中表达这些属性,SDN 提供了@RelationshipProperties应用于简单的 Java 类。 在 properties 类中,必须只有一个字段标记为@TargetNode来定义关系指向的实体。 或者,在INCOMING关系上下文,是来自。spring-doc.cadn.net.cn

关系属性类及其用法可能如下所示:spring-doc.cadn.net.cn

关系属性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 (@RelationshipId),以便 SDN 可以在保存期间确定哪些关系 可以安全地覆盖而不会丢失属性。 如果 SDN 没有找到存储内部节点 ID 的字段,则启动失败。spring-doc.cadn.net.cn

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

关系查询备注

通常,创建查询的关系/跃点没有限制。 SDN 从建模节点解析整个可访问的图形。spring-doc.cadn.net.cn

也就是说,当有双向映射关系的想法时,这意味着您在实体的两端定义关系, 你可能会得到比你预期的更多。spring-doc.cadn.net.cn

考虑一个示例,其中电影具有 Actors,并且您想要获取包含其所有 Actor 的特定电影。 如果电影演员的关系只是单向的,这就不会有问题。 在双向方案中,SDN 将获取特定电影、其角色以及根据关系定义为此角色定义的其他电影。 在最坏的情况下,这将级联到获取单个实体的整个图形。spring-doc.cadn.net.cn

一个完整的示例

将所有这些放在一起,我们可以创建一个简单的域。 我们使用电影和具有不同角色的人:spring-doc.cadn.net.cn

示例 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 is used to mark this class as a managed entity. It also is used to configure the Neo4j label. The label defaults to the name of the class, if you’re just using plain @Node.
2 Each entity has to have an id. We use the movie’s name as unique identifier.
3 This shows @Property as a way to use a different name for the field than for the graph property.
4 This configures an incoming relationship to a person.
5 This is the constructor to be used by your application code as well as by SDN.

People are mapped in two roles here, actors and directors. The domain class is the same:spring-doc.cadn.net.cn

Example 2. The 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;
	}

}
We haven’t modelled the relationship between movies and people in both direction. Why is that? We see the MovieEntity as the aggregate root, owning the relationships. On the other hand, we want to be able to pull all people from the database without selecting all the movies associated with them. Please consider your application’s use case before you try to map every relationship in your database in every direction. While you can do this, you may end up rebuilding a graph database inside your object graph and this is not the intention of a mapping framework. If you have to model your circular or bidirectional domain and don’t want to fetch the whole graph, you can define a fine-grained description of the data that you want to fetch by using projections.