当前位置: 首页 > 后端技术 > Java

Spring认证中国教育管理中心-SpringDataNeo4j教程二

时间:2023-04-01 16:51:02 Java

原标题:Spring认证中国教育管理中心-SpringDataNeo4j教程二(Spring中国教育管理中心)Spring认证-SpringDataNeo4j教程二对象映射以下部分将解释图和域之间的映射过程。它分为两部分。第一部分解释了实际映射和可用于描述如何将节点、关系和属性映射到对象的工具。第二部分将介绍SpringData的对象映射基础知识。它提供了关于通用映射的有价值的提示,为什么您应该更喜欢不可变的域对象以及如何使用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有关更多信息,请参阅转换。来自SpringDatacommons@org.springframework.data.annotation.Id与SDN中的@Id相同。@Id其实就是用SpringDataCommon的Id注解来标注的。@CreatedBy:应用在字段级别,表示节点的创建者。@CreatedDate:应用于字段级别,表示节点的创建日期。@LastModifiedBy:应用于字段级别,以指示对节点的最后更改的作者。@LastModifiedDate:应用于字段级别,表示节点的最后修改日期。@PersistenceConstructor:应用于构造函数以在读取实体时将其标记为首选构造函数。@Persistent:应用于类级别,表示该类是映射到数据库的候选者。@Version:应用于字段级别,用于乐观锁定和检查保存操作的修改。初始值为零,并在每次更新时自动递增。@ReadOnlyProperty:应用于字段级别,将属性标记为只读。该属性将在数据库读取期间被水合,但不受写入影响。当与关系一起使用时,请注意,如果集合中的任何相关实体不相关,则它们将不会被保留。有关审计支持的所有说明,请参阅第10章。6.1.2.基本构建块:@Node注释用于将@Node类标记为托管域类,受制于映射上下文的类路径扫描。要将对象映射到图中的节点,反之亦然,我们需要一个标签来标识要映射到和从中映射的类。@Node有一个属性labels,它允许你配置一个或多个标签,以便在读取和写入注释类的实例时使用。value属性是标签的别名。如果你没有指定标签,简单的类名将被用作主标签。如果要提供多个标签,您可以:为属性提供一个数组标签。数组中的第一个元素将被视为主标签。为primaryLabel提供值并将附加标签放入标签中。主标签应该始终是反映您的域类的最具体的标签。对于通过存储库或通过Neo4j模板编写的注释类的每个实例,将至少写入一个在图中具有主要标签的节点。反之亦然,所有具有主标签的节点都将映射到注释类的实例。关于类层次结构的注释@Node注释不是从超类型和接口继承的。但是,您可以在每个继承级别单独注释域类。这允许多态查询:您可以传入基类或中间类并为您的节点检索正确的具体实例。这只支持抽象基础@Node注解。在此类上定义的标签将与特定于实现的标签一起用作附加标签。对于某些场景,我们还支持域类层次结构中的接口:清单10.单独模块中的域模型,与接口同名的主标签publicinterfaceSomeInterface{StringgetName();SomeInterfacegetRelated();}@Node("SomeInterface")publicstaticclassSomeInterfaceEntityimplementsSomeInterface{@Id@GeneratedValueprivateLongid;privatefinalStringname;privateSomeInterfacerelated;publicSomeInterfaceEntity(Stringname){this.name=name;}@OverridepublicStringgetName(){returnname;}@OverridepublicSomeInterfacegetRelated(){返回相关;}}SpringAuthentication-SpringDataNeo4jTutorial二是简单的接口名称,就像你命名你的域由于我们需要同步主要标签,我们把@Node实现类,它可能在另一个模块中。请注意,此值与实现的接口名称完全相同。无法重命名。也可以使用不同的主标签而不是接口名称:清单11.不同的主标签@Node("PrimaryLabelWN")/为简洁起见省略了重写}在接口上放置@Node注释还可以使用接口的不同实现并具有多态域模型。这样做时,至少需要两个标记:一个标识接口,一个标识具体类:清单12.多个实现@Node("SomeInterface3")publicinterfaceSomeInterface3{StringgetName();SomeInterface3getRelated();{id@GeneratedValueprivateLongid;privateSomeInterface3related1;privateSomeInterface3related2;}SpringAuthentication-SpringDataNeo4j教程二这个场景需要显式指定标识接口的标签这个适用于第一个...和第二个实现这是一个client端或父模型,SomeInterface3透明地用于两个关系,这些关系未指定以下测试中显示的数据结构所需的具体类型。OGM也会这样写:清单13.使用多个不同的接口实现需要的数据结构Longid;try(Sessionsession=driver.session(bookmarkCapture.createSessionConfig());Transactiontransaction=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'})"+"RETURNid(s)").single().get(0).asLong();transaction.commit();}OptionaloptionalParentModel=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).提取(Inheritance.ParentModel::getRelated2).isInstanceOf(Inheritance.SomeInterfaceImpl3a.class).extracting(Inheritance.SomeInterface3::getName).isEqualTo("3a");});接口无法定义标识符字段,因此它们不是存储库的有效实体类型。动态或“运行时”托管标签所有通过简单类名隐式定义或通过@Node注释显式定义的标签都是静态的。它们不能在运行时更改。如果您需要可以在运行时操作的额外标签,您可以使用@DynamicLabels。@DynamicLabels是字段级注解,将java.util.Collection类型的属性(例如aList或Set)标记为动态标签源。如果存在这个注解,所有存在于节点上且未静态映射的标签@Node和类名将在加载期间收集到此集合中。在写入期间,节点的所有标签都被静态定义的标签加上集合的内容替换。如果您有其他应用程序向节点添加其他标签,请不要使用@DynamicLabels。如果托管实体上存在@DynamicLabels,则生成的标签集将是写入数据库的“真相”。6.1.3.Identifyinstances:@Id当@Node创建一个类和一个带有特定标签的节点的映射时,我们还需要在类(对象)的每个实例和节点实例之间建立连接。这就是@Id发挥作用的地方。@Id将类的属性标记为对象的唯一标识符。在最好的情况下,这个唯一标识符是一个唯一的业务密钥,或者换句话说,一个自然密钥。@Id可用于所有支持的简单类型的属性。但是,很难找到自然键。例如,人们的名字很少是唯一的,会随着时间而改变,或者更糟的是,并不是每个人都有名字和姓氏。因此,我们支持两种不同类型的代理键。在long或Long类型的属性上,@Id可以与@GeneratedValue一起使用。这会将Neo4j内部ID(不是节点或关系上的属性,通常不可见)映射到属性,并允许SDN检索类的各个实例。@GeneratedValue提供属性generatorClass。generatorClass可用于指定IdGenerator的实现。AnIdGenerator是一个功能接口,它采用主标记和generateId的实例来为其生成Id。我们支持UUIDStringGenerator作为开箱即用的实现。您还可以从@GeneratedValuevia上的应用程序上下文中指定一个SpringBeangeneratorRef。此bean还需要实现IdGenerator,但可以利用上下文中的所有内容,包括与数据库交互的Neo4j客户端或模板。6.1.4.乐观锁定:@VersionSpringDataNeo4j通过在类型字段上使用@Version注释来支持乐观锁定。Long此属性将在更新期间自动递增,不得手动修改。例如,如果不同线程中的两个事务要修改同一个版本为x的对象,则第一个操作将成功持久化到数据库。此时版本字段会递增,所以x+1。第二个操作将失败并抛出OptimisticLockingFailureException,因为它要使用数据库中不再存在的x版本修改对象。在这种情况下,需要重试该操作,首先从数据库中重新获取具有当前版本的对象。6.1.5.映射属性:@Property注释类@Node的所有属性将作为Neo4j节点和关系的属性持久化。无需进一步配置,Java或Kotlin类中的属性名称将用作Neo4j属性。如果您正在使用现有的Neo4j模式,或者只是想根据您的需要调整映射,则需要使用@Property.name来指定数据库中的属性名称。6.1.6.连接节点:@Relationship@Relationship注解可用于所有非简单类型的属性。它适用于用其他类型@Node或其集合和映射注释的属性。type或value属性允许配置关系的类型,允许direction指定方向。SDN中的默认方向是Relationship.Direction#OUTGOING。我们支持动态关系。动态关系表示为Map或Map。在这种情况下,与其他域类的关系类型由映射键给出,不能通过@Relationship映射。关系属性Neo4j不仅支持在节点上定义属性,还支持在关系上定义属性。为了在模型中表达这些属性,SDN提供了应用于简单Java类的@RelationshipProperties。在一个属性类中,必须只有一个标有@TargetNode的字段定义关系指向的实体。或者,在INCOMING关系的上下文中,来自。关系属性类及其用法可能如下所示:清单14.关系属性Roles@RelationshipPropertiespublicclassRoles{@RelationshipIdprivateLongid;privatefinalList角色;@TargetNode私有最终PersonEntity人;publicRoles(PersonEntityperson,Listroles){this.person=person;this.roles=角色;}publicListgetRoles(){返回角色;您必须为生成的内部ID(@RelationshipId)定义一个属性,以便SDN可以在保存期间确定哪些关系可以安全地覆盖而不会丢失属性。如果SDN没有找到存储内部节点ID的字段,它将在启动过程中失败。清单15.为实体定义关系属性@Relationship(type="ACTED_IN",direction=Direction.INCOMING)privateListactorsAndRoles;Relationshipquery备注通常,创建查询的关系/跳数没有限制。SDN从您的建模节点解析整个可达性图。也就是说,当有双向映射关系的想法时,意味着你在实体的两端定义关系,你最终可能会得到比你预期的更多的东西。考虑一个例子,其中电影有演员,你想获取一部电影及其所有演员。如果从电影到演员的关系只是一种方式,这就不是问题。在双向场景中,SDN将根据关系定义获取特定电影、其演员以及为该演员定义的其他电影。在最坏的情况下,这会级联为获取单个实体的整个图。6.1.7.一个完整的例子将所有这些放在一起,我们可以创建一个简单的域。我们使用不同角度的电影和人物:示例3.MovieEntityimportjava.util.ArrayList;importjava.util.List;importorg.springframework.data.neo4j.core.schema.Id;importorg.springframework.data.neo4j。core.schema.Node;导入org.springframework.data.neo4j.core.schema.Property;导入org.springframework.data.neo4j.core.schema.Relationship;导入org.springframework.data.neo4j.core.schema.Relationship.Direction;@Node("Movie")publicclassMovieEntity{@IdprivatefinalStringtitle;@Property("tagline")privatefinal字符串描述;@Relationship(type="ACTED_IN",direction=Direction.INCOMING)privateListactorsAndRoles;@Relationship(type="DIRECTED",direction=Direction.INCOMING)privateListdirectors=newArrayList<>();publicMovieEntity(Stringtitle,Stringdescription){this.title=title;this.description=描述;}//为简洁起见省略了getters}@Node用它还用于配置Neo4j标记以将此类标记为托管实体。如果您只使用plain,则标签默认为类@Node的名称。每个实体都必须有一个id。我们使用电影的名称作为唯一标识符。这表明@Property是一种为字段使用不同于图形属性的名称的方法。这配置了与Person的传入关系。这是您的应用程序代码和SDN使用的构造函数。人们在这里被映射到两个角色,演员和导演。域类是相同的:示例4.PersonEntityimportorg.springframework.data.neo4j.core.schema.Id;importorg.springframework.data.neo4j.core.schema.Node;@Node("Person")publicclassPersonEntity{@Id私有最终字符串名称;私人最终整数出生;publicPersonEntity(Integerborn,Stringname){this.born=born;this.name=名称;}publicIntegergetBorn(){返回出生;}publicStringgetName(){返回名称;}}SpringAuthentication-SpringDataNeo4j教程二我们没有模拟电影和人两个方向的关系。这是为什么?我们将MovieEntity视为聚合根,拥有关系。另一方面,我们希望能够从数据库中提取所有人,而不用选择与他们相关的所有电影。在尝试从各个方向映射数据库中的每个关系之前,请考虑您的应用程序的用例。虽然您可以这样做,但您最终可能会在对象图中重建图形数据库,这不是映射框架的意图。