Using DBRefs
映射框架不必存储嵌入在文档中的子对象。您也可以将它们单独存储,并使用 DBRef
来引用该文档。当对象从 MongoDB 被加载时,这些引用会被急切解析,这样您会得到一个映射对象,看上去就像它被存储在顶级文档中一样。
The mapping framework does not have to store child objects embedded within the document.
You can also store them separately and use a DBRef
to refer to that document.
When the object is loaded from MongoDB, those references are eagerly resolved so that you get back a mapped object that looks the same as if it had been stored embedded within your top-level document.
下面的示例使用一个 DBRef
来引用一个特定文档,该文档独立于在其中被引用的对象(为了简洁起见,两个类被内联显示):
The following example uses a DBRef to refer to a specific document that exists independently of the object in which it is referenced (both classes are shown in-line for brevity’s sake):
@Document
public class Account {
@Id
private ObjectId id;
private Float total;
}
@Document
public class Person {
@Id
private ObjectId id;
@Indexed
private Integer ssn;
@DBRef
private List<Account> accounts;
}
您不必使用 @OneToMany
或类似的机制,因为对象列表告诉映射框架您想要一个多对一关系。当对象被存储在 MongoDB 中时,会有一个 DBRef
列表,而不是 Account
对象本身。当需要加载 DBRef
集合时,建议将集合类型中持有的引用限制在一个特定的 MongoDB 集合。这允许批量加载所有引用,而指向不同 MongoDB 集合的引用需要逐个解析。
You need not use @OneToMany
or similar mechanisms because the List of objects tells the mapping framework that you want a one-to-many relationship.
When the object is stored in MongoDB, there is a list of DBRefs rather than the Account
objects themselves.
When it comes to loading collections of `DBRef`s it is advisable to restrict references held in collection types to a specific MongoDB collection.
This allows bulk loading of all references, whereas references pointing to different MongoDB collections need to be resolved one by one.
映射框架无法处理级联保存。如果你更改了 Person
对象引用的 Account
对象,则你必须单独保存 Account
对象。对 Person
对象调用 save
不会自动保存 accounts
属性中的 Account
对象。
The mapping framework does not handle cascading saves.
If you change an Account
object that is referenced by a Person
object, you must save the Account
object separately.
Calling save
on the Person
object does not automatically save the Account
objects in the accounts
property.
DBRef
也能以懒惰方式解析。在这种情况下,实际的 Object
或引用 Collection
会在首次访问该属性时被解析。使用 @DBRef
的 lazy
属性来指定这一点。定义为懒惰加载 DBRef
并且用作构造函数参数的必需属性也用懒惰加载代理进行修饰,以确保尽可能减少对数据库和网络的压力。
DBRef`s can also be resolved lazily.
In this case the actual `Object
or Collection
of references is resolved on first access of the property.
Use the lazy
attribute of @DBRef
to specify this.
Required properties that are also defined as lazy loading DBRef
and used as constructor arguments are also decorated with the lazy loading proxy making sure to put as little pressure on the database and network as possible.
延迟加载 |
Lazily loaded |
延迟加载可能需要类代理,而类代理反过来又可能需要访问从 Java 16+ 开始由于 JEP 396: Strongly Encapsulate JDK Internals by Default 而不再开放的 jdk 内部信息。对于这些情况,请考虑回退到一个接口类型(例如,从`ArrayList` 切换到`List`)或提供所需的`--add-opens` 参数。
Lazy loading may require class proxies, that in turn, might need access to jdk internals, that are not open, starting with Java 16+, due to JEP 396: Strongly Encapsulate JDK Internals by Default.
For those cases please consider falling back to an interface type (eg. switch from ArrayList
to List
) or provide the required --add-opens
argument.
Using Document References
使用 @DocumentReference
提供了一种灵活的方法来引用 MongoDB 中的实体。虽然目标与使用 DBRefs 相同,但存储表示是不同的。`DBRef`解析为具有固定结构的文档,如 MongoDB Reference documentation 中所述。文档引用不遵循特定的格式。它们可以是任何东西,单个值、整个文档,基本上可以存储在 MongoDB 中的任何东西。默认情况下,映射层将使用引用的实体 id 值进行存储和检索,如下面的示例所示。
Using @DocumentReference
offers a flexible way of referencing entities in MongoDB.
While the goal is the same as when using DBRefs, the store representation is different.
DBRef
resolves to a document with a fixed structure as outlined in the MongoDB Reference documentation.
Document references, do not follow a specific format.
They can be literally anything, a single value, an entire document, basically everything that can be stored in MongoDB.
By default, the mapping layer will use the referenced entities id value for storage and retrieval, like in the sample below.
@Document
class Account {
@Id
String id;
Float total;
}
@Document
class Person {
@Id
String id;
@DocumentReference 1
List<Account> accounts;
}
Account account = …
template.insert(account); 2
template.update(Person.class)
.matching(where("id").is(…))
.apply(new Update().push("accounts").value(account)) 3
.first();
{
"_id" : …,
"accounts" : [ "6509b9e" … ] 4
}
1 | Mark the collection of Account values to be referenced. |
2 | The mapping framework does not handle cascading saves, so make sure to persist the referenced entity individually. |
3 | Add the reference to the existing entity. |
4 | Referenced Account entities are represented as an array of their _id values. |
上述示例使用基于 _id
的获取查询 ({ '_id' : ?#{#target} }
) 检索数据并立即解析链接的实体。可以通过 @DocumentReference
的属性来更改解析默认值(如下所列)。
The sample above uses an _id
-based fetch query ({ '_id' : ?#{#target} }
) for data retrieval and resolves linked entities eagerly.
It is possible to alter resolution defaults (listed below) using the attributes of @DocumentReference
Attribute | Description | Default |
---|---|---|
|
The target database name for collection lookup. |
|
|
The target collection name. |
The annotated property’s domain type, respectively the value type in case of |
|
The single document lookup query evaluating placeholders via SpEL expressions using |
An |
|
Used for sorting result documents on server side. |
None by default.
Result order of |
|
If set to |
Resolves properties eagerly by default. |
延迟加载可能需要类代理,而类代理反过来又可能需要访问从 Java 16+ 开始由于 JEP 396: Strongly Encapsulate JDK Internals by Default 而不再开放的 jdk 内部信息。对于这些情况,请考虑回退到一个接口类型(例如,从`ArrayList` 切换到`List`)或提供所需的`--add-opens` 参数。
Lazy loading may require class proxies, that in turn, might need access to jdk internals, that are not open, starting with Java 16+, due to JEP 396: Strongly Encapsulate JDK Internals by Default.
For those cases please consider falling back to an interface type (eg. switch from ArrayList
to List
) or provide the required --add-opens
argument.
@DocumentReference(lookup)
允许定义过滤查询,该查询可以不同于 _id
字段,因此提供了一种灵活的方式来定义实体之间的引用,如下面的示例所示,其中一本书的 Publisher
由其缩写引用,而不是内部 id
。
@DocumentReference(lookup)
allows defining filter queries that can be different from the _id
field and therefore offer a flexible way of defining references between entities as demonstrated in the sample below, where the Publisher
of a book is referenced by its acronym instead of the internal id
.
@Document
class Book {
@Id
ObjectId id;
String title;
List<String> author;
@Field("publisher_ac")
@DocumentReference(lookup = "{ 'acronym' : ?#{#target} }") 1
Publisher publisher;
}
@Document
class Publisher {
@Id
ObjectId id;
String acronym; 1
String name;
@DocumentReference(lazy = true) 2
List<Book> books;
}
Book
document{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisher_ac" : "DR"
}
Publisher
document{
"_id" : 1a23e45,
"acronym" : "DR",
"name" : "Del Rey",
…
}
1 | Use the acronym field to query for entities in the Publisher collection. |
2 | Lazy load back references to the Book collection. |
上述代码段显示了使用自定义引用对象时读取方面的操作。由于映射信息并未表示 #target
的来源,因此编写需要一些额外的设置。映射层需要在目标文档和 DocumentPointer
之间注册一个 Converter
,如下所示:
The above snippet shows the reading side of things when working with custom referenced objects.
Writing requires a bit of additional setup as the mapping information do not express where #target
stems from.
The mapping layer requires registration of a Converter
between the target document and DocumentPointer
, like the one below:
@WritingConverter
class PublisherReferenceConverter implements Converter<Publisher, DocumentPointer<String>> {
@Override
public DocumentPointer<String> convert(Publisher source) {
return () -> source.getAcronym();
}
}
如果没有提供 DocumentPointer
转换器,则可以根据给定的查找查询来计算目标引用文档。在这种情况下,将评估关联的目标属性,如下面的示例所示。
If no DocumentPointer
converter is provided the target reference document can be computed based on the given lookup query.
In this case the association target properties are evaluated as shown in the following sample.
@Document
class Book {
@Id
ObjectId id;
String title;
List<String> author;
@DocumentReference(lookup = "{ 'acronym' : ?#{acc} }") 1 2
Publisher publisher;
}
@Document
class Publisher {
@Id
ObjectId id;
String acronym; 1
String name;
// ...
}
{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisher" : {
"acc" : "DOC"
}
}
1 | Use the acronym field to query for entities in the Publisher collection. |
2 | The field value placeholders of the lookup query (like acc ) is used to form the reference document. |
还可以将 @ReadonlyProperty
和 @DocumentReference
的组合用于建模关系型 一对多 引用。此方法允许链接类型,而不会将链接值存储在所有文档中,而是存储在引用文档中,如下面的示例所示。
It is also possible to model relational style One-To-Many references using a combination of @ReadonlyProperty
and @DocumentReference
.
This approach allows link types without storing the linking values within the owning document but rather on the referencing document as shown in the example below.
@Document
class Book {
@Id
ObjectId id;
String title;
List<String> author;
ObjectId publisherId; 1
}
@Document
class Publisher {
@Id
ObjectId id;
String acronym;
String name;
@ReadOnlyProperty 2
@DocumentReference(lookup="{'publisherId':?#{#self._id} }") 3
List<Book> books;
}
Book
document{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisherId" : 8cfb002
}
Publisher
document{
"_id" : 8cfb002,
"acronym" : "DR",
"name" : "Del Rey"
}
1 | Set up the link from Book (reference) to Publisher (owner) by storing the Publisher.id within the Book document. |
2 | Mark the property holding the references to be readonly.
This prevents storing references to individual Book`s with the `Publisher document. |
3 | Use the #self variable to access values within the Publisher document and in this retrieve Books with matching publisherId . |
有了上述所有内容,就可以对实体之间的所有关联进行建模。参阅下面这个不详尽的示例列表,以了解其可能性。
With all the above in place it is possible to model all kind of associations between entities. Have a look at the non-exhaustive list of samples below to get feeling for what is possible.
class Entity {
@DocumentReference
ReferencedObject ref;
}
// entity
{
"_id" : "8cfb002",
"ref" : "9a48e32" 1
}
// referenced object
{
"_id" : "9a48e32" 1
}
1 | MongoDB simple type can be directly used without further configuration. |
class Entity {
@DocumentReference(lookup = "{ '_id' : '?#{#target}' }") 1
ReferencedObject ref;
}
// entity
{
"_id" : "8cfb002",
"ref" : "9a48e32" 1
}
// referenced object
{
"_id" : "9a48e32"
}
1 | target defines the reference value itself. |
refKey
field for the lookup queryclass Entity {
@DocumentReference(lookup = "{ '_id' : '?#{refKey}' }") 1 2
private ReferencedObject ref;
}
@WritingConverter
class ToDocumentPointerConverter implements Converter<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> convert(ReferencedObject source) {
return () -> new Document("refKey", source.id); 1
}
}
// entity
{
"_id" : "8cfb002",
"ref" : {
"refKey" : "9a48e32" 1
}
}
// referenced object
{
"_id" : "9a48e32"
}
1 | The key used for obtaining the reference value must be the one used during write. |
2 | refKey is short for target.refKey . |
class Entity {
@DocumentReference(lookup = "{ 'firstname' : '?#{fn}', 'lastname' : '?#{ln}' }") 1 2
ReferencedObject ref;
}
// entity
{
"_id" : "8cfb002",
"ref" : {
"fn" : "Josh", 1
"ln" : "Long" 1
}
}
// referenced object
{
"_id" : "9a48e32",
"firstname" : "Josh", 2
"lastname" : "Long", 2
}
1 | Read/write the keys fn & ln from/to the linkage document based on the lookup query. |
2 | Use non id fields for the lookup of the target documents. |
class Entity {
@DocumentReference(lookup = "{ '_id' : '?#{id}' }", collection = "?#{collection}") 2
private ReferencedObject ref;
}
@WritingConverter
class ToDocumentPointerConverter implements Converter<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> convert(ReferencedObject source) {
return () -> new Document("id", source.id) 1
.append("collection", … ); 2
}
}
// entity
{
"_id" : "8cfb002",
"ref" : {
"id" : "9a48e32", 1
"collection" : "…" 2
}
}
1 | Read/write the keys _id from/to the reference document to use them in the lookup query. |
2 | The collection name can be read from the reference document using its key. |
我们知道在查找查询中使用各种 MongoDB 查询运算符很有用,这样做也没问题。但要注意几个方面:
We know it is tempting to use all kinds of MongoDB query operators in the lookup query and this is fine. But there a few aspects to consider:
-
Make sure to have indexes in place that support your lookup.
-
Mind that resolution requires a server rountrip inducing latency, consider a lazy strategy.
-
A collection of document references is bulk loaded using the
$or
operator. The original element order is restored in memory on a best-effort basis. Restoring the order is only possible when using equality expressions and cannot be done when using MongoDB query operators. In this case results will be ordered as they are received from the store or via the provided@DocumentReference(sort)
attribute.
再提出一些更一般的说明:
A few more general remarks:
-
Do you use cyclic references? Ask your self if you need them.
-
Lazy document references are hard to debug. Make sure tooling does not accidentally trigger proxy resolution by e.g. calling
toString()
. -
There is no support for reading document references using reactive infrastructure.