Unwrapping Types
展开实体用于在 Java 领域模型中设计值对象,其属性会展平到父 MongoDB 文档中。
Unwrapped Types Mapping
考虑以下领域模型,其中 User.name
用 @Unwrapped
进行注释。@Unwrapped
注释表示 UserName
的所有属性都应该展平到拥有 name
属性的 user
文档中。
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 属性时,如果 firstname 和 lastname 是 null 或不存在,则其值将设置为 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("…")
名称。请注意,如果多个属性呈现到同一个字段名称,值将覆盖彼此。
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
是绝对有效的。
@Field
annotationpublic 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 类型匹配。呈现实际查询时会考虑前缀和潜在自定义字段名称。使用展开对象的属性名称与所有包含的字段进行匹配,如下面的示例所示。
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"
})
还可以使用其属性名称直接寻址展开对象的任何字段,如下面的代码片段所示。
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.
可以通过其属性路径对展开对象的字段进行排序,如下面的示例所示。
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
展开对象的字段可以整体或通过单个字段成为投影的对象,如下面的示例所示。
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 | 对解包装对象的字段投影包含其所有属性。 |
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
抽象允许派生未包装对象及其整个对象的字段查询。
interface UserRepository extends CrudRepository<User, String> {
List<User> findByName(UserName username); 1
List<User> findByNameFirstname(String firstname); 2
}
1 | 匹配解包对象的全部字段。 |
2 | Matches against the firstname . |
即使存储库 |
Update on Unwrapped Objects
未包装对象可与其他域模型中的对象一样进行更新。映射层负责将结构扁平化到其周围环境中。可以更新未包装对象的单个属性,也可以更新整个值,如以下示例所示。
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" }
},
{ ... }
)
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 无效 |