原标题:Spring认证中国教育管理中心-Spring Data MongoDB教程十四(内容来源:Spring中国教育管理中心)
18.5.6.通配符索引
AWildcardIndex是一个索引,可用于包含所有字段或基于给定(通配符)模式的特定字段。有关详细信息,请参阅MongoDB 文档。
可以使用WildcardIndexvia 以编程方式设置索引IndexOperations。
示例 189. 编程通配符索引设置
代码语言:javascript复制mongoOperations
.indexOps(User.class)
.ensureIndex(new WildcardIndex("userMetadata"));
代码语言:javascript复制db.user.createIndex({ "userMetadata.$**" : 1 }, {})
该@WildcardIndex注释允许可与文档类型或属性或者是用声明性指数设置。
如果放置在根级域实体类型(用 注释的类型@Document)上,索引解析器将为它创建一个通配符索引。
示例 190. 域类型的通配符索引
代码语言:javascript复制@Document
@WildcardIndexed
public class Product {
// …
}
代码语言:javascript复制db.product.createIndex({ "$**" : 1 },{})
该wildcardProjection可被用来指定键输入/排除在索引中。
示例 191. 通配符索引 wildcardProjection
代码语言:javascript复制@Document
@WildcardIndexed(wildcardProjection = "{ 'userMetadata.age' : 0 }")
public class User {
private @Id String id;
private UserMetadata userMetadata;
}
代码语言:javascript复制db.user.createIndex(
{ "$**" : 1 },
{ "wildcardProjection" :
{ "userMetadata.age" : 0 }
}
)
通配符索引也可以通过直接在字段中添加注释来表示。请注意,wildcardProjection不允许在嵌套路径(例如属性)上使用。@WildcardIndexed在索引创建期间省略对带有注释的类型的投影。
示例 192. 属性上的通配符索引
代码语言:javascript复制@Document
public class User {
private @Id String id;
@WildcardIndexed
private UserMetadata userMetadata;
}
代码语言:javascript复制db.user.createIndex({ "userMetadata.$**" : 1 }, {})
18.5.7.文本索引
MongoDB v.2.4 默认禁用文本索引功能。
创建文本索引允许将多个字段累积到可搜索的全文索引中。每个集合只能有一个文本索引,因此所有标记@TextIndexed为的字段都合并到此索引中。可以对属性进行加权以影响排名结果的文档分数。文本索引的默认语言是英语。要更改默认语言,请将language属性设置为您想要的任何语言(例如,@Document(language="spanish"))。使用名为languageor的属性@Language,您可以在每个文档的基础上定义语言覆盖。以下示例显示了如何创建文本索引并将语言设置为西班牙语:
示例 193. 示例文本索引用法
代码语言:javascript复制@Document(language = "spanish")
class SomeEntity {
@TextIndexed String foo;
@Language String lang;
Nested nested;
}
class Nested {
@TextIndexed(weight=5) String bar;
String roo;
}
18.5.8.使用 DBRefs
映射框架不必存储嵌入在文档中的子对象。您也可以单独存储它们并使用 aDBRef来引用该文档。当对象从 MongoDB 加载时,这些引用会被急切地解析,以便您返回一个映射对象,该对象看起来与嵌入在顶级文档中的存储相同。
以下示例使用 DBRef 来引用独立于引用它的对象存在的特定文档(为简洁起见,两个类都显示为内嵌):
代码语言:javascript复制@Document
public class Account {
@Id
private ObjectId id;
private Float total;
}
@Document
public class Person {
@Id
private ObjectId id;
@Indexed
private Integer ssn;
@DBRef
private List<Account> accounts;
}
您不需要使用@OneToMany或类似的机制,因为对象列表告诉映射框架您想要一对多关系。当对象存储在 MongoDB 中时,有一个 DBRef 列表而不是Account对象本身。在加载DBRefs 的集合时,建议将集合类型中保存的引用限制为特定的 MongoDB 集合。这允许批量加载所有引用,而指向不同 MongoDB 集合的引用需要一一解析。
映射框架不处理级联保存。如果更改Account对象引用的Person对象,则必须Account单独保存该对象。调用save上的Person对象不会自动保存Account在对象accounts属性。
DBRefs 也可以懒惰地解决。在这种情况下,在第一次访问属性时解析引用的实际Object或Collection引用。使用的lazy属性@DBRef来指定这一点。也定义为延迟加载DBRef并用作构造函数参数的必需属性也使用延迟加载代理进行修饰,以确保尽可能减少对数据库和网络的压力。
延迟加载的DBRefs 可能很难调试。确保工具不会通过例如调用toString()或某些内联调试渲染调用属性 getter意外触发代理解析。请考虑启用跟踪日志记录 org.springframework.data.mongodb.core.convert.DefaultDbRefResolver以深入了解DBRef解决方案。
延迟加载可能需要类代理,反过来,由于JEP 396: Strongly Encapsulate JDK Internals by Default,从 Java 16 开始,可能需要访问未打开的 jdk 内部。对于这些情况,请考虑回退到接口类型(例如,从ArrayListto切换List)或提供所需的--add-opens参数。
18.5.9.使用文档参考
Using@DocumentReference提供了一种灵活的方式来引用 MongoDB 中的实体。虽然目标与使用DBRefs时相同,但存储表示不同。 DBRef解析为具有固定结构的文档,如MongoDB 参考文档中所述。 文档引用,不遵循特定格式。它们实际上可以是任何东西,单个值,整个文档,基本上可以存储在 MongoDB 中的所有内容。默认情况下,映射层将使用引用的实体id值进行存储和检索,如下面的示例所示。
代码语言:javascript复制@Document
class Account {
@Id
String id;
Float total;
}
@Document
class Person {
@Id
String id;
@DocumentReference
List<Account> accounts;
}
代码语言:javascript复制Account account = …
tempate.insert(account);
template.update(Person.class)
.matching(where("id").is(…))
.apply(new Update().push("accounts").value(account))
.first();
代码语言:javascript复制{
"_id" : …,
"accounts" : [ "6509b9e" … ]
}
标记Account要引用的值的集合。
映射框架不处理级联保存,因此请确保单独保留引用的实体。
添加对现有实体的引用。
引用的Account实体表示为其_id值的数组。
上面的示例使用_id基于 fetch 查询 ( { '_id' : ?#{#target} }) 进行数据检索并急切地解析链接的实体。可以使用以下属性更改分辨率默认值(如下所列)@DocumentReference
延迟加载可能需要类代理,反过来,由于JEP 396: Strongly Encapsulate JDK Internals by Default,从 Java 16 开始,可能需要访问未打开的 jdk 内部。对于这些情况,请考虑回退到接口类型(例如,从ArrayListto切换List)或提供所需的--add-opens参数。
DocumentReference(lookup)允许定义可能与_id字段不同的过滤器查询,因此提供了一种灵活的方式来定义实体之间的引用,如下面的示例所示,其中Publisher书籍的 由其首字母缩略词而不是内部id.
代码语言:javascript复制@Document
class Book {
@Id
ObjectId id;
String title;
List<String> author;
@Field("publisher_ac")
@DocumentReference(lookup = "{ 'acronym' : ?#{#target} }")
Publisher publisher;
}
@Document
class Publisher {
@Id
ObjectId id;
String acronym;
String name;
@DocumentReference(lazy = true)
List<Book> books;
}
Book 文档
代码语言:javascript复制{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisher_ac" : "DR"
}
Publisher 文档
代码语言:javascript复制{
"_id" : 1a23e45,
"acronym" : "DR",
"name" : "Del Rey",
…
}
使用该acronym字段查询Publisher集合中的实体。
延迟加载对Book集合的引用。
上面的代码片段显示了使用自定义引用对象时的阅读方面。写作需要一些额外的设置,因为映射信息没有表达出从何#target而来。映射层需要Converter在目标文档 和 之间注册 a DocumentPointer,如下所示:
代码语言:javascript复制@WritingConverter
class PublisherReferenceConverter implements Converter<Publisher, DocumentPointer<String>> {
@Override
public DocumentPointer<String> convert(Publisher source) {
return () -> source.getAcronym();
}
}
如果没有DocumentPointer提供转换器,则可以根据给定的查找查询计算目标参考文档。在这种情况下,关联目标属性的评估如下面的示例所示。
代码语言:javascript复制@Document
class Book {
@Id
ObjectId id;
String title;
List<String> author;
@DocumentReference(lookup = "{ 'acronym' : ?#{acc} }")
Publisher publisher;
}
@Document
class Publisher {
@Id
ObjectId id;
String acronym;
String name;
// ...
}
代码语言:javascript复制{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisher" : {
"acc" : "DOC"
}
}
使用该acronym字段查询Publisher集合中的实体。
查找查询的字段值占位符(如acc)用于形成参考文档。
它也可以对模型关系式的一对许多使用的组合引用@ReadonlyProperty和@DocumentReference。这种方法允许链接类型不将链接值存储在拥有文档中,而是存储在引用文档中,如下例所示。
代码语言:javascript复制@Document
class Book {
@Id
ObjectId id;
String title;
List<String> author;
ObjectId publisherId;
}
@Document
class Publisher {
@Id
ObjectId id;
String acronym;
String name;
@ReadOnlyProperty
@DocumentReference(lookup="{'publisherId':?#{#self._id} }")
List<Book> books;
}
Book 文档
代码语言:javascript复制{
"_id" : 9a48e32,
"title" : "The Warded Man",
"author" : ["Peter V. Brett"],
"publisherId" : 8cfb002
}
Publisher 文档
代码语言:javascript复制{
"_id" : 8cfb002,
"acronym" : "DR",
"name" : "Del Rey"
}
通过将 存储在文档中Book来设置从(引用)到Publisher(所有者)的链接。Publisher.idBook
将持有引用的属性标记为只读。这可以防止Book在Publisher文档中存储对个人的引用。
使用该#self变量访问Publisher文档中的值,并在此检索中Books使用匹配的publisherId.
有了上述所有内容,就可以对实体之间的所有类型的关联进行建模。查看下面的非详尽示例列表,以了解可能的情况。
示例 194. 使用id字段的简单文档引用
代码语言:javascript复制class Entity {
@DocumentReference
ReferencedObject ref;
}
代码语言:javascript复制// entity
{
"_id" : "8cfb002",
"ref" : "9a48e32"
}
// referenced object
{
"_id" : "9a48e32"
}
MongoDB 简单类型可以直接使用,无需进一步配置。
示例 195. 使用带有显式查找查询的id字段的简单文档引用
代码语言:javascript复制class Entity {
@DocumentReference(lookup = "{ '_id' : '?#{#target}' }")
ReferencedObject ref;
}
代码语言:javascript复制// entity
{
"_id" : "8cfb002",
"ref" : "9a48e32"
}
// referenced object
{
"_id" : "9a48e32"
}
target定义了参考值本身。
示例 196.文档参考提取refKey查找查询的字段
代码语言:javascript复制class Entity {
@DocumentReference(lookup = "{ '_id' : '?#{refKey}' }")
private ReferencedObject ref;
}
代码语言:javascript复制@WritingConverter
class ToDocumentPointerConverter implements Converter<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> convert(ReferencedObject source) {
return () -> new Document("refKey", source.id);
}
}
代码语言:javascript复制// entity
{
"_id" : "8cfb002",
"ref" : {
"refKey" : "9a48e32"
}
}
// referenced object
{
"_id" : "9a48e32"
}
用于获取参考值的密钥必须是写入时使用的密钥。
refKey是 的缩写target.refKey。
示例 197. 具有多个值的文档引用形成查找查询
代码语言:javascript复制class Entity {
@DocumentReference(lookup = "{ 'firstname' : '?#{fn}', 'lastname' : '?#{ln}' }")
ReferencedObject ref;
}
代码语言:javascript复制// entity
{
"_id" : "8cfb002",
"ref" : {
"fn" : "Josh",
"ln" : "Long"
}
}
// referenced object
{
"_id" : "9a48e32",
"firsntame" : "Josh",
"lastname" : "Long",
}
读/ WIRTE键fn和ln自/至基于查找查询的链接文件。
使用非id字段来查找目标文档。
示例 198. 从目标集合中读取文档引用
代码语言:javascript复制class Entity {
@DocumentReference(lookup = "{ '_id' : '?#{id}' }", collection = "?#{collection}")
private ReferencedObject ref;
}
代码语言:javascript复制@WritingConverter
class ToDocumentPointerConverter implements Converter<ReferencedObject, DocumentPointer<Document>> {
public DocumentPointer<Document> convert(ReferencedObject source) {
return () -> new Document("id", source.id)
.append("collection", … );
}
}
代码语言:javascript复制// entity
{
"_id" : "8cfb002",
"ref" : {
"id" : "9a48e32",
"collection" : "…"
}
}
_id从/向参考文档读取/写入密钥以在查找查询中使用它们。
可以使用其键从参考文档中读取集合名称。
我们知道在查找查询中使用各种 MongoDB 查询运算符很诱人,这很好。但是有几个方面需要考虑:
确保有支持您查找的索引。
请注意,解析需要服务器往返导致延迟,请考虑使用惰性策略。
使用$or运算符批量加载文档引用集合。
尽最大努力在内存中恢复原始元素顺序。仅在使用等式表达式时才可以恢复顺序,而在使用 MongoDB 查询运算符时则无法恢复。在这种情况下,结果将在从商店或通过提供的@DocumentReference(sort)属性收到时进行排序。
一些更一般的评论:
你使用循环引用吗?问问你自己是否需要它们。
懒惰的文档引用很难调试。确保工具不会意外触发代理解析,例如调用toString().
不支持使用反应式基础架构阅读文档引用。
18.5.10.映射框架事件
在映射过程的整个生命周期中都会触发事件。这在生命周期事件部分进行了描述。
在 Spring ApplicationContext 中声明这些 bean 会导致在调度事件时调用它们。
18.6.展开类型
解包实体用于在 Java 域模型中设计值对象,其属性被展平到父级的 MongoDB 文档中。
18.6.1.展开类型映射
考虑以下User.name用@Unwrapped. 该@Unwrapped注释信号是所有属性UserName应该被平整出到user拥有该文档name属性。
示例 199.解包对象的示例代码
代码语言:javascript复制class User {
@Id
String userId;
@Unwrapped(onEmpty = USE_NULL)
UserName name;
}
class UserName {
String firstname;
String lastname;
}
代码语言:javascript复制{
"_id" : "1da2ba06-3ba7",
"firstname" : "Emma",
"lastname" : "Frost"
}
当装载name属性其值被设置为null如果两个firstname和lastname要么null或不存在。通过使用onEmpty=USE_EMPTY一个空的UserName,null其属性的潜在价值,将被创建。
对于不太冗长的可嵌入类型声明,请使用@Unwrapped.Nullableand@Unwrapped.Empty代替@Unwrapped(onEmpty = USE_NULL)and @Unwrapped(onEmpty = USE_EMPTY)。这两个注释都使用 JSR-305@javax.annotation.Nonnull进行元注释,以帮助进行可空性检查。
可以在展开的对象中使用复杂类型。但是,那些不能是,也不能包含未包装的字段本身。
18.6.2.解包类型字段名称
通过使用注解的可选prefix属性,一个值对象可以被多次解包@Unwrapped。通过添加,所选的前缀被添加到@Field("…")解包对象中的每个属性或名称之前。请注意,如果多个属性呈现为相同的字段名称,则值将相互覆盖。
示例 200. 带有名称前缀的解包对象的示例代码
代码语言:javascript复制class User {
@Id
String userId;
@Unwrapped.Nullable(prefix = "u_")
UserName name;
@Unwrapped.Nullable(prefix = "a_")
UserName name;
}
class UserName {
String firstname;
String lastname;
}
代码语言:javascript复制{
"_id" : "a6a805bd-f95f",
"u_firstname" : "Jean",
"u_lastname" : "Grey",
"a_firstname" : "Something",
"a_lastname" : "Else"
}
的所有属性UserName都以 为前缀u_。
的所有属性UserName都以 为前缀a_。
虽然将@Field注释与@Unwrapped相同的属性组合在一起没有意义,因此会导致错误。这是用于@Field任何未包装类型属性的完全有效的方法。
示例 201. 使用@Field注释解开对象的示例代码
代码语言:javascript复制public class User {
@Id
private String userId;
@Unwrapped.Nullable(prefix = "u-")
UserName name;
}
public class UserName {
@Field("first-name")
private String firstname;
@Field("last-name")
private String lastname;
}
代码语言:javascript复制{
"_id" : "2647f7b9-89da",
"u-first-name" : "Barbara",
"u-last-name" : "Gordon"
}
的所有属性UserName都以 为前缀u-。
最终字段名称是连接@Unwrapped(prefix)和的结果@Field(name)。
18.6.3.查询解包对象
可以在类型和字段级别上定义对未包装属性的查询,因为所提供的Criteria内容与域类型相匹配。呈现实际查询时将考虑前缀和潜在的自定义字段名称。使用解包对象的属性名称匹配所有包含的字段,如下面的示例所示。
示例 202. 查询解包对象
代码语言:javascript复制UserName userName = new UserName("Carol", "Danvers")
Query findByUserName = query(where("name").is(userName));
User user = template.findOne(findByUserName, User.class);
代码语言:javascript复制db.collection.find({
"firstname" : "Carol",
"lastname" : "Danvers"
})
也可以直接使用其属性名称来寻址解包对象的任何字段,如下面的代码片段所示。
示例 203. 查询未包装对象的字段
代码语言:javascript复制Query findByUserFirstName = query(where("name.firstname").is("Shuri"));
List<User> users = template.findAll(findByUserFirstName, User.class);
代码语言:javascript复制db.collection.find({
"firstname" : "Shuri"
})
按展开的字段排序。
展开对象的字段可用于通过其属性路径进行排序,如下面的示例所示。
示例 204. 在展开的字段上排序
代码语言:javascript复制Query findByUserLastName = query(where("name.lastname").is("Romanoff"));
List<User> user = template.findAll(findByUserName.withSort(Sort.by("name.firstname")), User.class);
代码语言:javascript复制db.collection.find({
"lastname" : "Romanoff"
}).sort({ "firstname" : 1 })
尽管可能,使用解包对象本身作为排序标准包括其所有字段的不可预测顺序,并可能导致排序不准确。
展开物体上的场投影
展开对象的场可以作为整体或通过单个场进行投影,如下面的示例所示。
示例 205. 在展开的对象上投影。
代码语言:javascript复制Query findByUserLastName = query(where("name.firstname").is("Gamora"));
findByUserLastName.fields().include("name");
List<User> user = template.findAll(findByUserName, User.class);
代码语言:javascript复制db.collection.find({
"lastname" : "Gamora"
},
{
"firstname" : 1,
"lastname" : 1
})
展开对象上的场投影包括其所有属性。
示例 206. 在展开的对象的字段上投影。
代码语言:javascript复制Query findByUserLastName = query(where("name.lastname").is("Smoak"));
findByUserLastName.fields().include("name.firstname");
List<User> user = template.findAll(findByUserName, User.class);
代码语言:javascript复制db.collection.find({
"lastname" : "Smoak"
},
{
"firstname" : 1
})
展开对象上的场投影包括其所有属性。
在未包装的对象上按示例查询。
展开的对象可以Example像任何其他类型一样在探测器中使用。请查看按示例查询部分,以了解有关此功能的更多信息。
对解包对象的存储库查询。
该Repository抽象允许导出对未包装对象的字段以及整个对象的查询。
示例 207. 对解包对象的存储库查询。
代码语言:javascript复制interface UserRepository extends CrudRepository<User, String> {
List<User> findByName(UserName username);
List<User> findByNameFirstname(String firstname);
}
匹配解包对象的所有字段。
与firstname.
即使存储库create-query-indexes命名空间属性设置为 ,为解包对象创建索引也会暂停true。
18.6.4.展开对象的更新
展开的对象可以作为域模型的一部分的任何其他对象进行更新。映射层负责将结构展平到其周围环境中。可以更新解包对象的单个属性以及整个值,如下面的示例所示。
示例 208. 更新解包对象的单个字段。
代码语言:javascript复制Update update = new Update().set("name.firstname", "Janet");
template.update(User.class).matching(where("id").is("Wasp"))
.apply(update).first()
代码语言:javascript复制db.collection.update({
"_id" : "Wasp"
},
{
"$set" { "firstname" : "Janet" }
},
{ ... }
)
示例 209. 更新一个展开的对象。
代码语言:javascript复制Update update = new Update().set("name", new Name("Janet", "van Dyne"));
template.update(User.class).matching(where("id").is("Wasp"))
.apply(update).first()
代码语言:javascript复制db.collection.update({
"_id" : "Wasp"
},
{
"$set" {
"firstname" : "Janet",
"lastname" : "van Dyne",
}
},
{ ... }
)
18.6.5.未包装对象上的聚合
该聚合框架会试图映射类型聚集的展开值。在引用其值之一时,请确保使用包括包装器对象的属性路径。除此之外,不需要特殊操作。
18.6.6.展开对象的索引
可以将@Indexed注释附加到解包类型的属性,就像对常规对象所做的那样。不能@Indexed与@Unwrapped拥有属性的注释一起使用。
代码语言:javascript复制public class User {
@Id
private String userId;
@Unwrapped(onEmpty = USE_NULL)
UserName name;
// Invalid -> InvalidDataAccessApiUsageException
@Indexed
@Unwrapped(onEmpty = USE_Empty)
Address address;
}
public class UserName {
private String firstname;
@Indexed
private String lastname;
}
索引创建lastname的users集合。
@Indexed一起使用无效@Unwrapped
18.7.自定义转换 - 覆盖默认映射
影响映射结果的最简单的方法是通过@Field注释指定所需的本机 MongoDB 目标类型 。这允许BigDecimal在域模型中使用非 MongoDB 类型,同时以本机org.bson.types.Decimal128格式持久化值。
示例 210. 显式目标类型映射
代码语言:javascript复制public class Payment {
@Id String id;
@Field(targetType = FieldType.DECIMAL128)
BigDecimal value;
Date date;
}
代码语言:javascript复制{
"_id" : ObjectId("5ca4a34fa264a01503b36af8"),
"value" : NumberDecimal(2.099),
"date" : ISODate("2019-04-03T12:11:01.870Z")
}
表示有效的字符串id值ObjectId会自动转换。有关
详细信息,请参阅如何_id在映射层中处理字段。
所需的目标类型明确定义为Decimal128转换为NumberDecimal. 否则,该
BigDecimal值将被调整为String.
Date值由 MongoDB 驱动程序本身处理并存储为ISODate.
上面的代码片段对于提供简单的类型提示很方便。要对映射过程进行更细粒度的控制,您可以使用MongoConverter实现注册 Spring 转换器,例如MappingMongoConverter.
MappingMongoConverter在尝试映射对象本身之前,检查是否有任何 Spring 转换器可以处理特定的类。要“劫持”的正常映射策略MappingMongoConverter,也许是为了提高性能或其它自定义映射的需求,首先需要创建春天的实现Converter接口,然后用它注册MappingConverter。
有关 Spring 类型转换服务的更多信息,请参阅此处的参考文档。