解包类型
解包实体用于在您的 Java 域模型中设计值对象,其属性被扁平化到父 MongoDB 文档中。
解包类型映射
考虑以下域模型,其中 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
注解的可选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
注解解包对象的示例代码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) 连接起来的结果。 |
对解包对象进行查询
可以在类型级别和字段级别上对解包属性定义查询,因为提供的Criteria
与域类型匹配。在渲染实际查询时,将考虑前缀和潜在的自定义字段名称。使用解包对象的属性名称来匹配所有包含的字段,如下面的示例所示。
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"
})
按解包字段排序。
可以使用解包对象的字段通过其属性路径进行排序,如下面的示例所示。
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 })
虽然可以使用未包装的对象本身作为排序条件,但这会将所有字段包含在内,并且顺序不可预测,可能会导致排序不准确。 |
未包装对象的字段投影
未包装对象的字段可以作为整体或通过单个字段进行投影,如下面的示例所示。
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 | 对未包装对象的字段投影包括其所有属性。 |
对未包装对象进行示例查询。
未包装对象可以在 Example
探测器中使用,就像任何其他类型一样。请查看 示例查询 部分,以了解有关此功能的更多信息。
对未包装对象进行存储库查询。
Repository
抽象允许对未包装对象的字段以及整个对象进行查询。
interface UserRepository extends CrudRepository<User, String> {
List<User> findByName(UserName username); (1)
List<User> findByNameFirstname(String firstname); (2)
}
1 | 与未包装对象的全部字段匹配。 |
2 | 与 firstname 匹配。 |
即使存储库 |
更新未包装对象
未包装对象可以像域模型中任何其他对象一样进行更新。映射层负责将结构扁平化为其周围环境。可以更新未包装对象的单个属性,也可以更新整个值,如下面的示例所示。
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",
}
},
{ ... }
)
解包对象的索引
可以将@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 一起使用时的无效用法 |