Unwrapping Types

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

Unwrapped Types Mapping

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

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 加载 name 属性时,如果 firstnamelastnamenull 或不存在,则其值将设置为 null。通过使用 onEmpty=USE_EMPTY ,将创建一个空的 UserName,可能会为其属性提供 null 值。

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

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

Unwrapped Types field names

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

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 UserName 的所有属性都以 u_ 为前缀。
2 UserName 的所有属性都以 a_ 为前缀。

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

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 UserName 的所有属性都以 u- 为前缀。
2 最终的字段名是 @Unwrapped(prefix)@Field(name) 连接的结果。

Query on Unwrapped Objects

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

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"
})

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

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.

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

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 })

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

Field projection on unwrapped objects

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

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 对解包装对象的字段投影包含其所有属性。
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 对解包装对象的字段投影包含其所有属性。

Query By Example on unwrapped object.

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

Repository Queries on unwrapped objects.

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

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 匹配解包对象的全部字段。
2 Matches against the firstname.

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

Update on Unwrapped Objects

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

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

Index on Unwrapped Objects

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

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 users 集合中的 lastname 创建索引。
2 @Indexed 用法与 @Unwrapped 无效