原题目:Spring认证中国教育管理中心-Spring Data Neo4j教程二(Spring中国教育管理中心)

Spring认证-Spring Data Neo4j教程二

  1. 对象映射
    以下局部将解释图表和域之间的映射过程。它分为两局部。第一局部解释了理论映射和可用工具,用于形容如何将节点、关系和属性映射到对象。第二局部将介绍 Spring Data 的对象映射基础知识。它提供了无关通用映射的贵重提醒,为什么您应该更喜爱不可变域对象以及如何应用 Java 或 Kotlin 对它们进行建模。

6.1。基于元数据的映射
要充分利用 SDN 中的对象映射性能,您应该应用注解对映射的对象进行@Node注解。只管映射框架没有必要具备此正文(您的 POJO 已正确映射,即便没有任何正文),但它容许类门路扫描器查找并预处理您的域对象以提取必要的元数据。如果你不应用这个注解,你的应用程序在你第一次存储一个域对象时会受到轻微的性能影响,因为映射框架须要建设它的外部元数据模型,以便它晓得你的域对象的属性以及如何保持他们。

6.1.1.映射正文概述
来自 SDN
@Node:在类级别利用以批示该类是映射到数据库的候选对象。
@Id:利用于字段级别以标记用于标识目标的字段。
@GeneratedValue:在字段级别利用,@Id以指定应如何生成惟一标识符。
@Property:利用于字段级别以批改从属性到属性的映射。
@CompositeProperty:在字段级别利用于 Map 类型的属性,应作为复合材料回读。请参阅复合属性。
@Relationship:利用于字段级别以指定关系的详细信息。
@DynamicLabels:利用于字段级别以指定动静标签的起源。
@RelationshipProperties:在类级别利用以批示该类作为关系属性的指标。
@TargetNode: 利用在一个类的字段上@RelationshipProperties,从另一端的角度来标记该关系的指标。
以下正文用于指定转换并确保与 OGM 的向后兼容性。

@DateLong
@DateString
@ConvertWith
无关这方面的更多信息,请参阅转换。

来自 Spring Data commons
@org.springframework.data.annotation.Id和 SDN一样@Id,其实@Id是用 Spring Data Common 的 Id-annotation 标注的。
@CreatedBy:利用于字段级别以批示节点的创建者。
@CreatedDate:利用于字段级别以批示节点的创立日期。
@LastModifiedBy:利用于字段级别以批示对节点的最初更改的作者。
@LastModifiedDate:在字段级别利用以批示节点的最初批改日期。
@PersistenceConstructor:利用于一个构造函数,以在读取实体时将其标记为首选构造函数。
@Persistent:在类级别利用以批示该类是映射到数据库的候选对象。
@Version:利用于字段级别,用于乐观锁定并查看保留操作的批改。初始值为零,每次更新时都会主动减少。
@ReadOnlyProperty:利用于字段级别以将属性标记为只读。该属性将在数据库读取期间被水合,但不受写入影响。当用于关系时,请留神,如果不相干,则该汇合中的任何相干实体都不会保留。
查看第 10 章,理解无关审计反对的所有正文。

6.1.2.根本构建块:@Node
注解用于将@Node类标记为受管域类,受映射上下文的类门路扫描。

要将对象映射到图中的节点,反之亦然,咱们须要一个标签来标识要映射到和从的类。

@Node有一个属性labels,容许您配置一个或多个标签,以便在读取和写入带正文的类的实例时应用。该value属性是 的别名labels。如果您不指定标签,则简略类名将用作主标签。如果您想提供多个标签,您能够:

为属性提供一个数组labels。数组中的第一个元素将被视为主标签。
为 提供一个值primaryLabel并将附加标签放入labels.
主标签应始终是反映您的域类的最具体的标签。

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

对于类层次结构的阐明
@Node注解不是从超类型和接口继承的。然而,您能够在每个继承级别独自正文您的域类。这容许多态查问:您能够传入基类或两头类并为您的节点检索正确的具体实例。这仅反对带有正文的形象基@Node。在此类上定义的标签将与具体实现的标签一起用作附加标签。

对于某些场景,咱们还反对域类层次结构中的接口:

清单 10. 独自模块中的域模型,与接口名称雷同的主标签

public interface SomeInterface {

String getName();SomeInterface getRelated();

}

@Node("SomeInterface")
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;}@Overridepublic String getName() {    return name;}@Overridepublic SomeInterface getRelated() {    return related;}

}
Spring认证-Spring Data Neo4j教程二
只是简略的接口名称,就像您命名您的域一样

因为咱们须要同步主标签,咱们搁置@Node了实现类,它可能在另一个模块中。请留神,该值与实现的接口名称完全相同。重命名是不可能的。

也能够应用不同的主标签而不是接口名称:

清单 11. 不同的主标签

@Node("PrimaryLabelWN")
public interface SomeInterface2 {

String getName();SomeInterface2 getRelated();

}

public static class SomeInterfaceEntity2 implements SomeInterface2 {

// Overrides omitted for brevity

}
将@Node注解放在界面上

还能够应用接口的不同实现并具备多态域模型。这样做时,至多须要两个标签:一个确定接口的标签和一个确定具体类的标签:

清单 12. 多个实现

@Node("SomeInterface3")
public interface SomeInterface3 {

String getName();SomeInterface3 getRelated();

}

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

// Overrides omitted for brevity

}
@Node("SomeInterface3b")
public static class SomeInterfaceImpl3b implements SomeInterface3 {

// Overrides omitted for brevity

}

@Node
public static class ParentModel {

@Id@GeneratedValueprivate Long id;private SomeInterface3 related1; private SomeInterface3 related2;

}
Spring认证-Spring Data Neo4j教程二
本场景须要显式指定标识接口的标签

这实用于第一个……

以及第二次施行

这是一个客户端或父模型,SomeInterface3通明地用于两个关系

未指定具体类型

所需的数据结构如上面的测试所示。OGM 也会这样写:

清单 13. 应用多个不同接口实现所需的数据结构

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>(例如 aList或Set)的属性标记为动静标签的起源。

如果存在此正文,则节点上存在且未通过动态映射的所有标签@Node和类名称将在加载期间收集到该汇合中。在写入期间,节点的所有标签都将替换为动态定义的标签加上汇合的内容。

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

6.1.3.辨认实例:@Id
在@Node创立类和具备特定标签的节点之间的映射时,咱们还须要在该类(对象)的各个实例和节点实例之间建设连贯。

这就是@Id发挥作用的中央。 @Id将类的属性标记为对象的惟一标识符。该惟一标识符在最佳世界中是惟一的业务密钥,或者换句话说,是天然密钥。 @Id可用于所有受反对的简略类型的属性。

然而,天然键很难找到。例如,人们的名字很少是惟一的,随着工夫的推移而变动或更糟,不是每个人都有名字和姓氏。

因而,咱们反对两种不同类型的代理键。

long在或类型的属性上Long,@Id能够与 一起应用@GeneratedValue。这会将 Neo4j 外部 id(不是节点或关系上的属性,通常不可见)映射到属性,并容许 SDN 检索类的各个实例。

@GeneratedValue提供属性generatorClass。 generatorClass可用于指定实现IdGenerator. AnIdGenerator是一个性能接口,它generateId采纳主标签和实例来为其生成 Id。咱们反对UUIDStringGenerator作为一种开箱即用的实现。

您还能够在@GeneratedValuevia上从应用程序上下文中指定一个 Spring Bean generatorRef。该 bean 也须要实现IdGenerator,但能够利用上下文中的所有内容,包含与数据库交互的 Neo4j 客户端或模板。

6.1.4。乐观锁定:@Version
Spring Data Neo4j 通过在类型化字段上应用@Version正文来反对乐观锁定。Long此属性将在更新期间主动递增,不得手动批改。

例如,如果不同线程中的两个事务想要应用 version 批改同一个对象x,则第一个操作将胜利长久化到数据库中。此时版本字段会递增,所以是x+1. 第二个操作将失败,
OptimisticLockingFailureException因为它想用x 数据库中不再存在的版本批改对象。在这种状况下,操作须要重试,从从数据库中从新获取具备以后版本的对象开始。

6.1.5。映射属性:@Property
-annotated 类的所有属性@Node都将作为 Neo4j 节点和关系的属性长久化。无需进一步配置,Java 或 Kotlin 类中的属性名称将用作 Neo4j 属性。

如果您正在应用现有的 Neo4j 架构,或者只是想依据您的须要调整映射,则须要应用@Property. name用于指定数据库内属性的名称。

6.1.6。连贯节点:@Relationship
@Relationship注解可用于所有非简略类型的属性。它实用于用其余类型正文的属性@Node或其汇合和映射。

type或value属性容许配置关系的类型,容许direction指定方向。SDN 中的默认方向是Relationship.Direction#OUTGOING.

咱们反对动静关系。动静关系示意为Map<String, AnnotatedDomainClass>或Map<Enum, AnnotatedDomainClass>。在这种状况下,与其余域类的关系类型由 maps 键给出,不能通过@Relationship.

映射关系属性
Neo4j 不仅反对在节点上定义属性,还反对在关系上定义属性。为了在模型中表白这些属性,SDN 提供@RelationshipProperties了利用于一个简略的 Java 类。在属性类中,必须恰好有一个字段被标记为@TargetNode定义关系指向的实体。或者,在INCOMING关系上下文中,来自。

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

清单 14. 关系属性 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;    }

}
您必须为生成的外部 ID ( @RelationshipId) 定义一个属性,以便 SDN 能够在保留期间确定能够平安笼罩哪些关系而不会失落属性。如果 SDN 没有找到存储外部节点 id 的字段,它会在启动过程中失败。

清单 15. 为实体定义关系属性

@Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
private List<Roles> actorsAndRoles;
关系查问备注
一般来说,创立查问的关系/跃点没有限度。SDN 从您的建模节点解析整个可达图。

这就是说,当存在双向映射关系的想法时,这意味着您在实体的两端定义关系,您可能会失去比您冀望的更多的货色。

思考一个电影有演员的例子,你想获取某部电影及其所有演员。如果从电影到演员的关系只是单向的,这不会有问题。在双向场景中,SDN 将获取特定电影、其演员以及依据关系定义为该演员定义的其余电影。在最坏的状况下,这将级联到获取单个实体的整个图。

6.1.7。一个残缺的例子
将所有这些放在一起,咱们能够创立一个简略的域。咱们应用不同角色的电影和人物:

示例 3. 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")
public class MovieEntity {

    @Id     private final String title;    @Property("tagline")     private final String description;    @Relationship(type = "ACTED_IN", direction = Direction.INCOMING)     private List<Roles> actorsAndRoles;    @Relationship(type = "DIRECTED", direction = Direction.INCOMING)    private List<PersonEntity> directors = new ArrayList<>();    public MovieEntity(String title, String description) {             this.title = title;            this.description = description;    }    // Getters omitted for brevity

}
@Node用于将此类标记为托管实体。它还用于配置 Neo4j 标签。如果您只是应用 plain ,标签默认为类的名称@Node。

每个实体都必须有一个 id。咱们应用电影的名称作为惟一标识符。

这显示@Property了为字段应用与图形属性不同的名称的一种形式。

这配置了与人的传入关系。

这是您的利用程序代码和 SDN 应用的构造函数。

人们在这里被映射为两个角色,actors并且directors。域类是一样的:

示例 4. 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;    }

}
Spring认证-Spring Data Neo4j教程二
咱们还没有在两个方向上模仿电影和人之间的关系。这是为什么?咱们将MovieEntity视为聚合根,领有关系。另一方面,咱们心愿可能从数据库中提取所有人,而无需抉择与他们关联的所有电影。在尝试将数据库中的每个关系映射到各个方向之前,请思考您的应用程序的用例。尽管您能够这样做,但您最终可能会在对象图中重建图形数据库,这不是映射框架的用意。