对于最新稳定版本,请使用Spring Data Neo4j 8.0.0spring-doc.cadn.net.cn

基于元数据的映射

为了充分利用SDN中的对象映射功能,你应该用以下方式标注你映射的对象@Node注解。 虽然映射框架不一定需要这种注释(即使没有注释,你的 POJO 映射也正确),但它允许类路径扫描器找到并预处理你的域对象,提取必要的元数据。 如果你不使用这个注释,第一次存储域对象时应用会略有性能下降,因为映射框架需要构建其内部元数据模型,以便了解域对象的属性以及如何持久化它们。spring-doc.cadn.net.cn

映射注释概述

来自SDN

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

关于这方面的更多信息,请参见“转换”。spring-doc.cadn.net.cn

摘自春季数据共享资源

  • @org.springframework.data.annotation.Id同一个@Id事实上,来自SDN,@Id用 Spring Data Common 的 ID 注释标注。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:在现场层面应用时,用于乐观锁定,并检查存档作的修改情况。 初始值为零,每次更新时都会自动调整。spring-doc.cadn.net.cn

  • @ReadOnlyProperty:在字段层面应用,用于标记属性为只读。在数据库读取过程中,属性将被水化, 但不会被写作所束缚。在关系上使用时,请注意该集合中没有相关实体会被持久化 如果没有其他关联。spring-doc.cadn.net.cn

可以看看Auditing,了解所有关于审计支持的注释。spring-doc.cadn.net.cn

基本构建模块:@Node

@Node注释用于将类标记为受管理域类,并受映射上下文类路径扫描的约束。spring-doc.cadn.net.cn

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

@Node具有一个属性标签这允许你配置一个或多个标签,用于读写注释类实例。 这属性是 的别名标签. 如果你没有指定标签,那么简单类名将作为主标签使用。 如果你想提供多个标签,可以选择:spring-doc.cadn.net.cn

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

  2. 给出一个值主标签并加上额外的标签标签.spring-doc.cadn.net.cn

主标签应始终是最具体的标签,能反映你的领域类别。spring-doc.cadn.net.cn

对于通过仓库或 Neo4j 模板编写的注释类实例,图中至少有一个带有主标签的节点被写入。 反之,所有带有主标签的节点都会映射到注释类的实例。spring-doc.cadn.net.cn

关于阶级层级的说明

@Node注释不是从超类型和接口继承而来的。 不过,你可以在每个继承层级单独注释你的域类。 这允许多态查询:你可以传递基础类或中间类,获取节点的正确具体实例。 这只支持标注为@Node. 在此类上定义的标签将作为附加标签与具体实现的标签一起使用。spring-doc.cadn.net.cn

我们还支持某些场景的域类层级接口:spring-doc.cadn.net.cn

域模型放在一个独立模块里,主标签和接口名称一样
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-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 这是一个客户端或父模型,使用某种接口3显然,这两段关系都是透明的
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");
});
接口不能定义标识符字段。 因此,它们并不是仓库的有效实体类型。

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

所有标签都通过简单类名隐式定义,或通过@Node注释是静态的。 它们在运行时无法更改。 如果你需要额外的标签,可以在运行时作,你可以使用@DynamicLabels.@DynamicLabels是字段级的注释,标记属性为java.util.Collection<String>(a列表设置例如)作为动态标签的来源。spring-doc.cadn.net.cn

如果有该注释,则节点上所有标签都存在且未静态映射为@Node而类别名称将在加载过程中被收集到该集合中。 写入过程中,节点的所有标签都会被静态定义的标签加上集合内容替换。spring-doc.cadn.net.cn

如果你有其他应用需要在节点上添加额外标签,就不要使用@DynamicLabels. 如果@DynamicLabels如果存在于托管实体中,生成的标签集就是写入数据库的“真实”标签。

识别实例:@Id

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

这里是@Id这就开始发挥作用了。@Id将类的属性标记为对象的唯一标识符。 在理想情况下,这个唯一标识符就是唯一的业务密钥,换句话说,是自然密钥。@Id可用于所有支持简单类型的属性。spring-doc.cadn.net.cn

不过自然键其实很难找到。 例如,人们的名字很少是独一无二的,会随着时间变化,甚至更糟的是,并非每个人都有名字和姓氏。spring-doc.cadn.net.cn

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

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

@GeneratedValue提供属性发电机类.发电机类可以用来指定一个实现的类IdGenerator. 一IdGenerator是一个函数式接口,其生成ID取主标签和实例生成 ID。 我们支持UUIDStringGenerator作为开箱即用的实现。spring-doc.cadn.net.cn

你也可以在应用上下文中指定一个 Spring Bean@GeneratedValue通过生成器参考. 那颗豆子也需要实现IdGenerator但可以利用上下文中的一切,包括 Neo4j 客户端或模板来与数据库交互。spring-doc.cadn.net.cn

不要跳过关于唯一ID处理与配置中关于ID处理的重要说明

乐观锁定:@Version

Spring Data Neo4j 支持通过使用@Version对 a 的注释打字字段。 该属性在更新时会自动递增,且不得手动修改。spring-doc.cadn.net.cn

例如,如果不同线程中的两个事务想修改同一个对象的版本x,第一个作将成功持久化到数据库。 此时,版本字段会被递增,所以是x+1. 第二个作将失败,且OptimisticLockingFailureException因为它想用版本修改对象x但数据库中已不存在。 在这种情况下,作需要重新尝试,从数据库中重新获取当前版本的对象开始。spring-doc.cadn.net.cn

@Version如果使用企业ID,属性也是必须的。 Spring Data Neo4j 会检查该字段,以确定该实体是新实体还是已被持久化。spring-doc.cadn.net.cn

属性映射:@Property

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

如果你使用已有的 Neo4j 架构,或者只是想根据需求调整映射,你需要使用@Property. 这名称用于指定数据库中属性的名称。spring-doc.cadn.net.cn

连接节点:@Relationship

@Relationship注释可用于所有非简单类型的属性。 它适用于其他类型的属性,注释为@Node或其收藏和地图。spring-doc.cadn.net.cn

类型或者属性允许配置关系类型,方向允许指定方向。 SDN的默认方向是关系。方向#外出.spring-doc.cadn.net.cn

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

映射关系性质

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

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

关系性质角色
@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

举个例子,一部电影演员,你想找一部电影里的所有演员。 如果电影和演员之间的关系是单向的,这不会有问题。 在双向情况下,SDN会获取该电影及其演员,同时也会获取该角色根据关系定义定义的其他电影。 最坏的情况是,这会连锁到为单个实体获取整个图。spring-doc.cadn.net.cn

一个完整的例子

将这些因素结合起来,我们可以创建一个简单的域名。 我们用电影和扮演不同角色的人:spring-doc.cadn.net.cn

例子1。这电影实体
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 标签。如果你只是用普通 ,标签默认是类名@Node.
2 每个实体都必须有一个ID。 我们使用电影名称作为唯一标识。
3 这显示了@Property作为一种用不同名称来表示字段和图属性名称的方法。
4 这构成了与某人建立的关系。
5 这是你的应用代码以及SDN都应使用的构造函数。

这里的人被分为两个角色,演员董事. 域类相同:spring-doc.cadn.net.cn

例子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;
	}

}
我们没有双向建模电影与人之间的关系。 为什么? 我们看到电影实体作为总根,拥有关系。 另一方面,我们希望能够从数据库中提取所有人,而无需选择所有与他们关联的电影。 在尝试将数据库中的每一个关系向各个方向映射之前,请考虑你的应用用例。 虽然你可以这样做,但你可能会在目标图中重建图数据库,这并不是映射框架的初衷。 如果你必须建模你的圆形或双向域,且不想取整个图, 你可以通过投影定义你想获取的数据的细粒度描述。