Metadata-based Mapping
为了充分利用 SDN 中的对象映射功能,你应该使用 @Node
注释注释你的映射对象。虽然映射框架不必具有此注释(即使没有任何注释,你的 POJO 也能正确映射),但它可以让类路径扫描器查找和预处理你的域对象,以提取必要的元数据。如果不使用此注释,则第一次存储域对象时,你的应用程序的性能会略有下降,因为映射框架需要建立其内部元数据模型,以便它了解你的域对象的属性以及如何持久保存它们。
To take full advantage of the object mapping functionality inside SDN, you should annotate your mapped objects with the @Node
annotation.
Although it is not necessary for the mapping framework to have this annotation (your POJOs are mapped correctly, even without any annotations), it lets the classpath scanner find and pre-process your domain objects to extract the necessary metadata.
If you do not use this annotation, your application takes a slight performance hit the first time you store a domain object, because the mapping framework needs to build up its internal metadata model so that it knows about the properties of your domain object and how to persist them.
Mapping Annotation Overview
From SDN
-
@Node
: Applied at the class level to indicate this class is a candidate for mapping to the database. -
@Id
: Applied at the field level to mark the field used for identity purpose. -
@GeneratedValue
: Applied at the field level together with@Id
to specify how unique identifiers should be generated. -
@Property
: Applied at the field level to modify the mapping from attributes to properties. -
@CompositeProperty
: Applied at the field level on attributes of type Map that shall be read back as a composite. See Composite properties. -
@Relationship
: Applied at the field level to specify the details of a relationship. -
@DynamicLabels
: Applied at the field level to specify the source of dynamic labels. -
@RelationshipProperties
: Applied at the class level to indicate this class as the target for properties of a relationship. -
@TargetNode
: Applied on a field of a class annotated with@RelationshipProperties
to mark the target of that relationship from the perspective of the other end.
以下注释用于指定转换并确保与 OGM 向后兼容。
The following annotations are used to specify conversions and ensure backwards compatibility with OGM.
-
@DateLong
-
@DateString
-
@ConvertWith
有关该主题的更多信息,请参见 Conversions。
See Conversions for more information on that.
From Spring Data commons
-
@org.springframework.data.annotation.Id
same as@Id
from SDN, in fact,@Id
is annotated with Spring Data Common’s Id-annotation. -
@CreatedBy
: Applied at the field level to indicate the creator of a node. -
@CreatedDate
: Applied at the field level to indicate the creation date of a node. -
@LastModifiedBy
: Applied at the field level to indicate the author of the last change to a node. -
@LastModifiedDate
: Applied at the field level to indicate the last modification date of a node. -
@PersistenceCreator
: Applied at one constructor to mark it as the preferred constructor when reading entities. -
@Persistent
: Applied at the class level to indicate this class is a candidate for mapping to the database. -
@Version
: Applied at field level it is used for optimistic locking and checked for modification on save operations. The initial value is zero which is bumped automatically on every update. -
@ReadOnlyProperty
: Applied at field level to mark a property as read only. The property will be hydrated during database reads, but not be subject to writes. When used on relationships be aware that no related entity in that collection will be persisted if not related otherwise.
查看 Auditing 以了解有关审核支持的所有注释。
Have a look at Auditing for all annotations regarding auditing support.
The basic building block: @Node
@Node
批注用于将一个类标记为受映射上下文类路径扫描约束的受管理领域类。
The @Node
annotation is used to mark a class as a managed domain class, subject to the classpath scanning by the mapping context.
要在图中将 Object 映射到节点,反之亦然,我们需要一个标签来标识要映射至和映射自的类。
To map an Object to nodes in the graph and vice versa, we need a label to identify the class to map to and from.
@Node
具有一个 labels
属性,用于配置用于在读取和写入批注类的实例时使用的多个标签。value
属性是 labels
的别名。如果您未指定标签,则将简单类名用作主标签。如果您想要提供多个标签,您可以:
@Node
has an attribute labels
that allows you to configure one or more labels to be used when reading and writing instances of the annotated class.
The value
attribute is an alias for labels
.
If you don’t specify a label, then the simple class name will be used as the primary label.
In case you want to provide multiple labels, you could either:
-
Supply an array to the
labels
property. The first element in the array will be considered as the primary label. -
Supply a value for
primaryLabel
and put the additional labels inlabels
.
主标签始终应该是反映您的领域类最具体的标签。
The primary label should always be the most concrete label that reflects your domain class.
对于通过存储库或 Neo4j 模板编写的批注类的每个实例,将写入至少带有主键标签的一个节点。反之,所有带有主键标签的节点都将映射到批注类的实例。
For each instance of an annotated class that is written through a repository or through the Neo4j template, one node in the graph with at least the primary label will be written. Vice versa, all nodes with the primary label will be mapped to the instances of the annotated class.
A note on class hierarchies
@Node
批注不会从超类型和接口继承。但是,您可以在每个继承级别单独注释您的领域类。这允许进行多态查询:您可以传入基础类或中间类,并为您的节点检索正确的具体实例。这仅受用 @Node
批注的抽象基础支持。此类上定义的标签将与具体实现的标签一起用作附加标签。
The @Node
annotation is not inherited from super-types and interfaces.
You can however annotate your domain classes individually at every inheritance level.
This allows polymorphic queries: You can pass in base or intermediate classes and retrieve the correct, concrete instance for your nodes.
This is only supported for abstract bases annotated with @Node
.
The labels defined on such a class will be used as additional labels together with the labels of the concrete implementations.
我们还支持一些场景中的领域类层次结构中的接口:
We also support interfaces in domain-class-hierarchies for some scenarios:
Unresolved include directive in modules/ROOT/pages/object-mapping/metadata-based-mapping.adoc - include::example$integration/shared/common/Inheritance.java[]
1 | Just the plain interface name, as you would name your domain |
2 | As we need to synchronize the primary labels, we put @Node on the implementing class, which
is probably in another module. Note that the value is exactly the same as the name of the interface
implemented. Renaming is not possible. |
也可以使用不同的主标签,而不是接口名称:
Using a different primary label instead of the interface name is possible, too:
Unresolved include directive in modules/ROOT/pages/object-mapping/metadata-based-mapping.adoc - include::example$integration/shared/common/Inheritance.java[]
1 | Put the @Node annotation on the interface |
还可以使用接口的不同实现,并具有一个多态的领域模型。在这样做的过程中,至少需要两个标签:一个确定接口的标签和一个确定具体类的标签:
It’s also possible to use different implementations of an interface and have a polymorph domain model. When doing so, at least two labels are required: A label determining the interface and one determining the concrete class:
Unresolved include directive in modules/ROOT/pages/object-mapping/metadata-based-mapping.adoc - include::example$integration/shared/common/Inheritance.java[]
1 | Explicitly specifying the label that identifies the interface is required in this scenario |
2 | Which applies for the first… |
3 | and second implementation as well |
4 | This is a client or parent model, using SomeInterface3 transparently for two relationships |
5 | No concrete type is specified |
所需的数据结构将显示在以下测试中。OGM 将编写相同的内容:
The data structure needed is shown in the following test. The same would be written by the OGM:
Unresolved include directive in modules/ROOT/pages/object-mapping/metadata-based-mapping.adoc - include::example$integration/imperative/InheritanceMappingIT.java[]
接口无法定义标识符字段。因此,它们不是存储库的有效实体类型。 |
Interfaces cannot define an identifier field. As a consequence they are not a valid entity type for repositories. |
Dynamic or "runtime" managed labels
通过简单类名隐式定义或通过 @Node
批注显式定义的所有标签都是静态的。它们无法在运行时更改。如果您需要可以在运行时操作的其他标签,则可以使用 @DynamicLabels
。@DynamicLabels
是一种字段级批注,它将一个 java.util.Collection<String>
(例如 List
或 Set
)类型的属性标记为动态标签的来源。
All labels implicitly defined through the simple class name or explicitly via the @Node
annotation are static.
They cannot be changed during runtime.
If you need additional labels that can be manipulated during runtime, you can use @DynamicLabels
.
@DynamicLabels
is an annotation on field level and marks an attribute of type java.util.Collection<String>
(a List
or Set
) for example) as source of dynamic labels.
如果存在此批注,则在加载期间将节点上存在的所有标签(而非通过 @Node
和类名进行静态映射)收集到该集合中。在写入期间,节点的所有标签将替换为静态定义的标签加上集合的内容。
If this annotation is present, all labels present on a node and not statically mapped via @Node
and the class names, will be collected into that collection during load.
During writes, all labels of the node will be replaced with the statically defined labels plus the contents of the collection.
如果您有其他应用程序向节点添加其他标签,请不要使用 @DynamicLabels
。如果 @DynamicLabels
存在于受管理的实体上,则生成的标签集将是写入数据库的“真实内容”。
If you have other applications add additional labels to nodes, don’t use @DynamicLabels
.
If @DynamicLabels
is present on a managed entity, the resulting set of labels will be "the truth" written to the database.
Identifying instances: @Id
虽然 @Node
创建一个在类和具有特定标签的节点之间的映射,但我们还需要在该类的各个实例(对象)和节点实例之间创建连接。
While @Node
creates a mapping between a class and nodes having a specific label, we also need to make the connection between individual instances of that class (objects) and instances of the node.
这是 @Id
发挥作用的地方。@Id
将类的属性标记为对象的唯一标识符。在理想情况下,该唯一标识符是一组唯一的业务键,或者换句话说,一个自然键。@Id
可以用于带有受支持简单类型的所有属性。
This is where @Id
comes into play.
@Id
marks an attribute of the class to be the unique identifier of the object.
That unique identifier is in an optimal world a unique business key or in other words, a natural key.
@Id
can be used on all attributes with a supported simple type.
然而,自然键很难找到。例如,人的名字很少是唯一的,会随着时间的推移而改变,或更糟的是,并非每个人都有姓和名。
Natural keys are however pretty hard to find. Peoples names for example are seldom unique, change over time or worse, not everyone has a first and last name.
因此,我们支持两种不同类型的“代理键”。
We therefore support two different kind of surrogate keys.
在 String
、long
或 Long
类型属性中,@Id
可与 @GeneratedValue
一起使用。Long
和 long
映射到 Neo4j 内部 ID。String
映射到自 Neo4j 5 以来可用的 elementId。这两个都不是节点或关系上的属性,通常不可见,并且该属性允许 SDN 检索类的单个实例。
On an attribute of type String
, long
or Long
, @Id
can be used with @GeneratedValue
.
Long
and long
maps to the Neo4j internal id.
String
maps to the elementId that is available since Neo4j 5.
Both are not a property on a node or relationship and usually not visible, to the attribute and allows SDN to retrieve individual instances of the class.
@GeneratedValue
提供属性 generatorClass
。generatorClass
可用于指定一个实现 IdGenerator
的类。IdGenerator
是一个函数接口,其 generateId
获取主标签和实例以生成一个 ID。我们支持 UUIDStringGenerator
作为开箱即用的实现之一。
@GeneratedValue
provides the attribute generatorClass
.
generatorClass
can be used to specify a class implementing IdGenerator
.
An IdGenerator
is a functional interface and its generateId
takes the primary label and the instance to generate an Id for.
We support UUIDStringGenerator
as one implementation out of the box.
可以通过 generatorRef
在 @GeneratedValue
上指定应用程序上下文中一个 Spring Bean。该 bean 也需要实现 IdGenerator
,但可以利用上下文中的一切,包括 Neo4j 客户端或模板与数据库进行交互。
You can also specify a Spring Bean from the application context on @GeneratedValue
via generatorRef
.
That bean also needs to implement IdGenerator
, but can make use of everything in the context, including the Neo4j client or template to interact with the database.
不要跳过 Handling and provisioning of unique IDs 中有关 ID 处理的重要说明 |
Don’t skip the important notes about ID handling in Handling and provisioning of unique IDs |
Optimistic locking: @Version
Spring Data Neo4j 通过在 Long
类型字段上使用 @Version
注解来支持乐观锁定。此属性会在更新期间自动加 1,并且不得手动修改。
Spring Data Neo4j supports optimistic locking by using the @Version
annotation on a Long
typed field.
This attribute will get incremented automatically during updates and must not be manually modified.
例如,如果不同线程中的两个事务想要修改具有版本 x
的同一对象,则第一个操作将成功持久化到数据库中。此时,版本字段将加 1,变为 x+1
。第二个操作将失败,显示 OptimisticLockingFailureException
,因为它想要修改具有版本 x
的对象,而该版本在数据库中已不存在。在这种情况下,需要重试该操作,从通过数据库获取具有当前版本的最新对象开始。
If, e.g., two transactions in different threads want to modify the same object with version x
, the first operation will get successfully persisted to the database.
At this moment, the version field will get incremented, so it is x+1
.
The second operation will fail with a OptimisticLockingFailureException
because it wants to modify the object with the version x
that does not exist anymore in the database.
In such cases the operation needs to get retried, beginning with a fresh fetch of the object with the current version from the database.
如果使用 business ids,那么 `@Version`属性也是强制性的。Spring Data Neo4j 将检查此字段以确定实体是新建的还是已持久化过的。
The @Version
attribute is also mandatory if business ids are used.
Spring Data Neo4j will check this field to determine if the entity is new or has already been persisted before.
Mapping properties: @Property
带有 @Node
注解的所有类属性都将持久化成 Neo4j 节点和关系的属性。如果不进行进一步配置,Java 或 Kotlin 类中属性的名称将用作 Neo4j 属性。
All attributes of a @Node
-annotated class will be persisted as properties of Neo4j nodes and relationships.
Without further configuration, the name of the attribute in the Java or Kotlin class will be used as Neo4j property.
如果你使用现有的 Neo4j 架构或只是想将映射调整成适合你的需求,则需要使用 @Property
。name
用于指定数据库中属性的名称。
If you are working with an existing Neo4j schema or just like to adapt the mapping to your needs, you will need to use @Property
.
The name
is used to specify the name of the property inside the database.
Connecting nodes: @Relationship
@Relationship
注解可用于所有非简单类型的属性。它适用于带有 @Node
或集合和映射注解的其他类型属性。
The @Relationship
annotation can be used on all attributes that are not a simple type.
It is applicable on attributes of other types annotated with @Node
or collections and maps thereof.
type
或 value
属性允许配置关系类型,direction
允许指定方向。SDN 中的默认方向是 Relationship.Direction#OUTGOING
。
The type
or the value
attribute allow configuration of the relationship’s type, direction
allows specifying the direction.
The default direction in SDN is Relationship.Direction#OUTGOING
.
我们支持动态关系。动态关系表示为 Map<String, AnnotatedDomainClass>
或 Map<Enum, AnnotatedDomainClass>
。在这种情况下,与其他域类的关系类型由映射键给出,不可通过 @Relationship
进行配置。
We support dynamic relationships.
Dynamic relationships are represented as a Map<String, AnnotatedDomainClass>
or Map<Enum, AnnotatedDomainClass>
.
In such a case, the type of the relationship to the other domain class is given by the maps key and must not be configured through the @Relationship
.
Map relationship properties
Neo4j 支持不仅在节点上定义属性,还可以在关系上定义属性。为了在模型中表示那些属性,SDN 提供 @RelationshipProperties
应用于一个简单的 Java 类。在属性类中必须有一个字段标记为 @TargetNode
,以定义关系指向的实体。或者,在 INCOMING
关系上下文中,是从中来的。
Neo4j supports defining properties not only on nodes but also on relationships.
To express those properties in the model SDN provides @RelationshipProperties
to be applied on a simple Java class.
Within the properties class there have to be exactly one field marked as @TargetNode
to define the entity the relationship points towards.
Or, in an INCOMING
relationship context, is coming from.
关系属性类及其用法可能如下所示:
A relationship property class and its usage may look like this:
Roles
Unresolved include directive in modules/ROOT/pages/object-mapping/metadata-based-mapping.adoc - include::example$documentation/domain/Roles.java[]
对于生成的内部 ID,你必须定义一个属性 (@RelationshipId
),以便 SDN 在保存期间确定哪些关系可以在不丢失属性的情况下安全地覆盖。如果 SDN 找不到用于存储内部节点 ID 的字段,它将在启动期间失败。
You must define a property for the generated, internal ID (@RelationshipId
) so that SDN can determine during save which relationships
can be safely overwritten without losing properties.
If SDN does not find a field for storing the internal node id, it will fail during startup.
Unresolved include directive in modules/ROOT/pages/object-mapping/metadata-based-mapping.adoc - include::example$documentation/domain/MovieEntity.java[]
Relationship query remarks
一般来说,创建查询没有关系/跳跃限制。SDN 从已建模的节点解析整个可到达图。
In general there is no limitation of relationships / hops for creating the queries. SDN parses the whole reachable graph from your modelled nodes.
话虽如此,当有将关系映射为双向的想法时,表示你在实体的两个端点上定义了该关系,你可能会得到超出你的预期的结果。
This said, when there is the idea of mapping a relationship bidirectional, meaning you define the relationship on both ends of your entity, you might get more than what you are expecting.
考虑一个 电影 有 演员 的示例,并且你希望获取包含所有演员的特定电影。如果从 电影 到 演员 的关系只是单向的,则这不会有问题。在双向场景中,SDN 将获取特定 电影、其 演员,但根据关系的定义,还将获取为该 演员 定义的其他电影。在最糟糕的情况下,这将级联到获取单个实体的整个图。
Consider an example where a movie has actors, and you want to fetch a certain movie with all its actors. This won’t be problematical if the relationship from movie to actor were just unidirectional. In a bidirectional scenario SDN would fetch the particular movie, its actors but also the other movies defined for this actor per definition of the relationship. In the worst case, this will cascade to fetching the whole graph for a single entity.
A complete example
将所有这些放在一起,我们可以创建一个简单的域。我们使用具有不同角色的电影和人物:
Putting all those together, we can create a simple domain. We use movies and people with different roles:
MovieEntity
Unresolved include directive in modules/ROOT/pages/object-mapping/metadata-based-mapping.adoc - include::example$documentation/domain/MovieEntity.java[]
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:
PersonEntity
Unresolved include directive in modules/ROOT/pages/object-mapping/metadata-based-mapping.adoc - include::example$documentation/domain/PersonEntity.java[]
我们没有在两个方向上对电影和人之间的关系进行建模。这是为什么?我们认为 |
We haven’t modelled the relationship between movies and people in both direction.
Why is that?
We see the |