最新的稳定版请使用 Spring Data MongoDB 4.3.1Spring中文文档

最新的稳定版请使用 Spring Data MongoDB 4.3.1Spring中文文档

解封实体用于在 Java 域模型中设计值对象,其属性被平展到父级的 MongoDB 文档中。Spring中文文档

解包类型映射

请考虑以下域模型,其中用 . 批注指示 的所有属性都应展平到拥有该属性的文档中。User.name@Unwrapped@UnwrappedUserNameusernameSpring中文文档

例 1.解包对象示例代码
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 加载属性时,其值设置为如果两者都存在,则为“不存在”或“不存在”。 通过使用一个空的 ,将创建其属性的潜在值。namenullfirstnamelastnamenullonEmpty=USE_EMPTYUserNamenull

对于不太详细的可嵌入类型声明,请改用 和 和 。 这两个注释都使用 JSR-305 进行元注释,以帮助进行可空性检查。@Unwrapped.Nullable@Unwrapped.Empty@Unwrapped(onEmpty = USE_NULL)@Unwrapped(onEmpty = USE_EMPTY)@javax.annotation.NonnullSpring中文文档

可以在未包装的对象中使用复杂类型。 但是,这些字段本身不得包含未包装的字段。Spring中文文档

1 加载属性时,其值设置为如果两者都存在,则为“不存在”或“不存在”。 通过使用一个空的 ,将创建其属性的潜在值。namenullfirstnamelastnamenullonEmpty=USE_EMPTYUserNamenull

可以在未包装的对象中使用复杂类型。 但是,这些字段本身不得包含未包装的字段。Spring中文文档

“解包类型”字段名称

可以使用批注的可选属性多次解包值对象。 通过给药,所选前缀将附加到未包装对象中的每个属性或名称之前。 请注意,如果多个属性呈现为相同的字段名称,则值将相互覆盖。prefix@Unwrapped@Field("…")Spring中文文档

例 2.带有名称前缀的解包对象的示例代码
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 的所有属性都以 .UserNameu_
2 的所有属性都以 .UserNamea_

虽然将注释与相同的属性组合在一起是没有意义的,因此会导致错误。 这是用于任何未包装类型属性的完全有效的方法。@Field@Unwrapped@FieldSpring中文文档

例 3.使用注释解包对象的示例代码@Field
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 的所有属性都以 .UserNameu-
2 最终字段名称是连接 和 的结果。@Unwrapped(prefix)@Field(name)
1 的所有属性都以 .UserNameu_
2 的所有属性都以 .UserNamea_
1 的所有属性都以 .UserNameu-
2 最终字段名称是连接 和 的结果。@Unwrapped(prefix)@Field(name)

查询未包装的对象

在类型和字段级别上都可以定义对未包装属性的查询,因为提供的属性与域类型匹配。 在呈现实际查询时,将考虑前缀和可能的自定义字段名称。 使用解包装对象的属性名称与所有包含的字段进行匹配,如下面的示例所示。CriteriaSpring中文文档

例 4.查询未包装的对象
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"
})

也可以直接使用其属性名称对未包装对象的任何字段进行寻址,如下面的代码片段所示。Spring中文文档

例 5.查询未包装对象的字段
Query findByUserFirstName = query(where("name.firstname").is("Shuri"));
List<User> users = template.findAll(findByUserFirstName, User.class);
db.collection.find({
  "firstname" : "Shuri"
})

按展开的字段排序。

未包装对象的字段可用于通过其属性路径进行排序,如下面的示例所示。Spring中文文档

例 6.对未展开的字段进行排序
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 })

尽管可能,但使用未包装对象本身作为排序条件会以不可预测的顺序包含其所有字段,并可能导致排序不准确。Spring中文文档

未包装对象上的场投影

未展开对象的字段可以作为一个整体进行投影,也可以通过单个字段进行投影,如下面的示例所示。Spring中文文档

例 7.投影在未包装的对象上。
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 未包装对象上的字段投影包括其所有属性。
例 8.投影到未包装对象的字段上。
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 未包装对象上的字段投影包括其所有属性。

在未包装的对象上按示例查询。

展开的对象可以在探针中使用,就像任何其他类型一样。 请查看“按示例查询”部分,了解有关此功能的更多信息。ExampleSpring中文文档

对未包装对象的存储库查询。

该抽象允许对未包装对象的字段以及整个对象进行派生查询。RepositorySpring中文文档

例 9.对未包装对象的存储库查询。
interface UserRepository extends CrudRepository<User, String> {

	List<User> findByName(UserName username);         (1)

	List<User> findByNameFirstname(String firstname); (2)
}
1 与未包装对象的所有字段匹配。
2 与 .firstname

即使存储库命名空间属性设置为 ,也暂停为未包装对象创建索引。create-query-indexestrueSpring中文文档

尽管可能,但使用未包装对象本身作为排序条件会以不可预测的顺序包含其所有字段,并可能导致排序不准确。Spring中文文档

1 未包装对象上的字段投影包括其所有属性。
1 未包装对象上的字段投影包括其所有属性。
1 与未包装对象的所有字段匹配。
2 与 .firstname

即使存储库命名空间属性设置为 ,也暂停为未包装对象创建索引。create-query-indexestrueSpring中文文档

更新未包装的对象

解包的对象可以更新为域模型中的任何其他对象。 映射图层负责将结构展平到其周围环境中。 可以更新未包装对象的单个属性以及整个值,如以下示例所示。Spring中文文档

例 10.更新未包装对象的单个字段。
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" }
},
{ ... }
)
例 11.更新未包装的对象。
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",
  }
},
{ ... }
)

对未包装对象的聚合

聚合框架将尝试映射类型化聚合的未包装值。 请确保在引用其值之一时使用包含包装对象的属性路径。 除此之外,不需要采取任何特殊措施。Spring中文文档

未包装对象的索引

可以将注释附加到未包装类型的属性,就像对常规对象所做的那样。 不能与拥有财产的注释一起使用。@Indexed@Indexed@UnwrappedSpring中文文档

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 为集合中创建的索引。lastnameusers
2 无效用法以及@Indexed@Unwrapped
1 为集合中创建的索引。lastnameusers
2 无效用法以及@Indexed@Unwrapped