Unwrapping Types

展开实体用于在 Java 领域模型中设计值对象,其属性会展平到父 MongoDB 文档中。

Unwrapped entities are used to design value objects in your Java domain model whose properties are flattened out into the parent’s MongoDB Document.

Unwrapped Types Mapping

考虑以下领域模型,其中 User.name@Unwrapped 进行注释。@Unwrapped 注释表示 UserName 的所有属性都应该展平到拥有 name 属性的 user 文档中。

Consider the following domain model where User.name is annotated with @Unwrapped. The @Unwrapped annotation signals that all properties of UserName should be flattened out into the user document that owns the name property.

Example 1. Sample Code of unwrapping objects
class User {

    @Id
    String userId;

    @Unwrapped(onEmpty = USE_NULL) 1
    UserName name;
}

class UserName {

    String firstname;

    String lastname;

}
{
  "_id" : "1da2ba06-3ba7",
  "firstname" : "Emma",
  "lastname" : "Frost"
}
1 When loading the name property its value is set to null if both firstname and lastname are either null or not present. By using onEmpty=USE_EMPTY an empty UserName, with potential null value for its properties, will be created.

对于冗长的可嵌入类型声明,请使用 @Unwrapped.Nullable@Unwrapped.Empty 代替 @Unwrapped(onEmpty = USE_NULL)@Unwrapped(onEmpty = USE_EMPTY)。这两种注释都用 JSR-305 @javax.annotation.Nonnull 进行元注释,以帮助进行空值检查。

For less verbose embeddable type declarations use @Unwrapped.Nullable and @Unwrapped.Empty instead @Unwrapped(onEmpty = USE_NULL) and @Unwrapped(onEmpty = USE_EMPTY). Both annotations are meta-annotated with JSR-305 @javax.annotation.Nonnull to aid with nullability inspections.

可以在展开对象中使用复杂类型。但是,这些类型本身不得包含展开字段,也不得包含展开字段。

It is possible to use complex types within an unwrapped object. However, those must not be, nor contain unwrapped fields themselves.

Unwrapped Types field names

可以通过使用 @Unwrapped 注释的可选 prefix 属性多次展开值对象。通过这样做,所选前缀会被前置到展开对象中的每个属性或 @Field("…") 名称。请注意,如果多个属性呈现到同一个字段名称,值将覆盖彼此。

A value object can be unwrapped multiple times by using the optional prefix attribute of the @Unwrapped annotation. By dosing so the chosen prefix is prepended to each property or @Field("…") name in the unwrapped object. Please note that values will overwrite each other if multiple properties render to the same field name.

Example 2. Sample Code of unwrapped object with name prefix
class User {

    @Id
    String userId;

    @Unwrapped.Nullable(prefix = "u_") 1
    UserName name;

    @Unwrapped.Nullable(prefix = "a_") 2
    UserName name;
}

class UserName {

    String firstname;

    String lastname;
}
{
  "_id" : "a6a805bd-f95f",
  "u_firstname" : "Jean",             1
  "u_lastname" : "Grey",
  "a_firstname" : "Something",        2
  "a_lastname" : "Else"
}
1 All properties of UserName are prefixed with u_.
2 All properties of UserName are prefixed with a_.

虽然将 @Field 注释与 @Unwrapped 注释组合在同一属性上是没有意义的,因此会导致错误。对于展开类型属性,使用 @Field 是绝对有效的。

While combining the @Field annotation with @Unwrapped on the very same property does not make sense and therefore leads to an error. It is a totally valid approach to use @Field on any of the unwrapped types properties.

Example 3. Sample Code unwrapping objects with @Field annotation
public class User {

	@Id
    private String userId;

    @Unwrapped.Nullable(prefix = "u-") 1
    UserName name;
}

public class UserName {

	@Field("first-name")              2
    private String firstname;

	@Field("last-name")
    private String lastname;
}
{
  "_id" : "2647f7b9-89da",
  "u-first-name" : "Barbara",         2
  "u-last-name" : "Gordon"
}
1 All properties of UserName are prefixed with u-.
2 Final field names are a result of concatenating @Unwrapped(prefix) and @Field(name).

Query on Unwrapped Objects

展开属性的查询定义在类型级和字段级都是可能的,因为提供的 Criteria 与 domain 类型匹配。呈现实际查询时会考虑前缀和潜在自定义字段名称。使用展开对象的属性名称与所有包含的字段进行匹配,如下面的示例所示。

Defining queries on unwrapped properties is possible on type- as well as field-level as the provided Criteria is matched against the domain type. Prefixes and potential custom field names will be considered when rendering the actual query. Use the property name of the unwrapped object to match against all contained fields as shown in the sample below.

Example 4. Query on unwrapped object
UserName userName = new UserName("Carol", "Danvers")
Query findByUserName = query(where("name").is(userName));
User user = template.findOne(findByUserName, User.class);
db.collection.find({
  "firstname" : "Carol",
  "lastname" : "Danvers"
})

还可以使用其属性名称直接寻址展开对象的任何字段,如下面的代码片段所示。

It is also possible to address any field of the unwrapped object directly using its property name as shown in the snippet below.

Example 5. Query on field of unwrapped object
Query findByUserFirstName = query(where("name.firstname").is("Shuri"));
List<User> users = template.findAll(findByUserFirstName, User.class);
db.collection.find({
  "firstname" : "Shuri"
})

Sort by unwrapped field.

可以通过其属性路径对展开对象的字段进行排序,如下面的示例所示。

Fields of unwrapped objects can be used for sorting via their property path as shown in the sample below.

Example 6. Sort on unwrapped field
Query findByUserLastName = query(where("name.lastname").is("Romanoff"));
List<User> user = template.findAll(findByUserName.withSort(Sort.by("name.firstname")), User.class);
db.collection.find({
  "lastname" : "Romanoff"
}).sort({ "firstname" : 1 })

尽管可以这样做,但使用展开对象本身作为排序条件,会以不可预测的顺序包含其所有字段,并可能导致排序不准确。

Though possible, using the unwrapped object itself as sort criteria includes all of its fields in unpredictable order and may result in inaccurate ordering.

Field projection on unwrapped objects

展开对象的字段可以整体或通过单个字段成为投影的对象,如下面的示例所示。

Fields of unwrapped objects can be subject for projection either as a whole or via single fields as shown in the samples below.

Example 7. Project on unwrapped object.
Query findByUserLastName = query(where("name.firstname").is("Gamora"));
findByUserLastName.fields().include("name");                             1
List<User> user = template.findAll(findByUserName, User.class);
db.collection.find({
  "lastname" : "Gamora"
},
{
  "firstname" : 1,
  "lastname" : 1
})
1 A field projection on an unwrapped object includes all of its properties.
Example 8. Project on a field of an unwrapped object.
Query findByUserLastName = query(where("name.lastname").is("Smoak"));
findByUserLastName.fields().include("name.firstname");                   1
List<User> user = template.findAll(findByUserName, User.class);
db.collection.find({
  "lastname" : "Smoak"
},
{
  "firstname" : 1
})
1 A field projection on an unwrapped object includes all of its properties.

Query By Example on unwrapped object.

复合对象可以在 `Example`探针内用作任何其他类型。请查看 Query By Example部分,以了解有关此功能的更多信息。

Unwrapped objects can be used within an Example probe just as any other type. Please review the Query By Example section, to learn more about this feature.

Repository Queries on unwrapped objects.

Repository 抽象允许派生未包装对象及其整个对象的字段查询。

The Repository abstraction allows deriving queries on fields of unwrapped objects as well as the entire object.

Example 9. Repository queries on unwrapped objects.
interface UserRepository extends CrudRepository<User, String> {

	List<User> findByName(UserName username);         1

	List<User> findByNameFirstname(String firstname); 2
}
1 Matches against all fields of the unwrapped object.
2 Matches against the firstname.

即使存储库 create-query-indexes 命名空间属性设置为 true,也会暂停未包装对象的索引创建。

Index creation for unwrapped objects is suspended even if the repository create-query-indexes namespace attribute is set to true.

Update on Unwrapped Objects

未包装对象可与其他域模型中的对象一样进行更新。映射层负责将结构扁平化到其周围环境中。可以更新未包装对象的单个属性,也可以更新整个值,如以下示例所示。

Unwrapped objects can be updated as any other object that is part of the domain model. The mapping layer takes care of flattening structures into their surroundings. It is possible to update single attributes of the unwrapped object as well as the entire value as shown in the examples below.

Example 10. Update a single field of an unwrapped object.
Update update = new Update().set("name.firstname", "Janet");
template.update(User.class).matching(where("id").is("Wasp"))
   .apply(update).first()
db.collection.update({
  "_id" : "Wasp"
},
{
  "$set" { "firstname" : "Janet" }
},
{ ... }
)
Example 11. Update an unwrapped object.
Update update = new Update().set("name", new Name("Janet", "van Dyne"));
template.update(User.class).matching(where("id").is("Wasp"))
   .apply(update).first()
db.collection.update({
  "_id" : "Wasp"
},
{
  "$set" {
    "firstname" : "Janet",
    "lastname" : "van Dyne",
  }
},
{ ... }
)

Aggregations on Unwrapped Objects

Aggregation Framework 将尝试映射类型聚合的展开值。在引用其某个值时,请务必使用包括包装对象的属性路径。除此之外,无需执行任何特殊操作。

The Aggregation Framework will attempt to map unwrapped values of typed aggregations. Please make sure to work with the property path including the wrapper object when referencing one of its values. Other than that no special action is required.

Index on Unwrapped Objects

可以像处理常规对象一样,将 @Indexed 注释附加到未包装类型的属性。无法在所有者属性上将 @Indexed@Unwrapped 注释一起使用。

It is possible to attach the @Indexed annotation to properties of an unwrapped type just as it is done with regular objects. It is not possible to use @Indexed along with the @Unwrapped annotation on the owning property.

public class User {

	@Id
    private String userId;

    @Unwrapped(onEmpty = USE_NULL)
    UserName name;                    1

    // Invalid -> InvalidDataAccessApiUsageException
    @Indexed                          2
    @Unwrapped(onEmpty = USE_Empty)
    Address address;
}

public class UserName {

    private String firstname;

    @Indexed
    private String lastname;           1
}
1 Index created for lastname in users collection.
2 Invalid @Indexed usage along with @Unwrapped