原标题:Spring认证中国教育管理中心-SpringDataMongoDB教程十四(内容来源:Spring中国教育管理中心)
18.5.6.通配符索引
AWildcardIndex是一个索引,可用于包含所有字段或基于给定(通配符)模式的特定字段。有关详细信息,请参阅MongoDB文档。
可以使用WildcardIndexvia以编程方式设置索引IndexOperations。
示例.编程通配符索引设置
mongoOperations.indexOps(User.class).ensureIndex(newWildcardIndex("userMetadata"));
db.user.createIndex({"userMetadata.**":1},{})
该
WildcardIndex注释允许可与文档类型或属性或者是用声明性指数设置。如果放置在根级域实体类型(用注释的类型
Document)上,索引解析器将为它创建一个通配符索引。示例.域类型的通配符索引
Document
WildcardIndexedpublicclassProduct{ //…}db.product.createIndex({"**":1},{})
该wildcardProjection可被用来指定键输入/排除在索引中。
示例.通配符索引wildcardProjection
Document
WildcardIndexed(wildcardProjection="{userMetadata.age:0}")publicclassUser{privateIdStringid;privateUserMetadatauserMetadata;}db.user.createIndex({"**":1},{"wildcardProjection":{"userMetadata.age":0}})
通配符索引也可以通过直接在字段中添加注释来表示。请注意,wildcardProjection不允许在嵌套路径(例如属性)上使用。
WildcardIndexed在索引创建期间省略对带有注释的类型的投影。示例.属性上的通配符索引
DocumentpublicclassUser{private
IdStringid;WildcardIndexedprivateUserMetadatauserMetadata;}db.user.createIndex({"userMetadata.**":1},{})
18.5.7.文本索引
MongoDBv.2.4默认禁用文本索引功能。
创建文本索引允许将多个字段累积到可搜索的全文索引中。每个集合只能有一个文本索引,因此所有标记
TextIndexed为的字段都合并到此索引中。可以对属性进行加权以影响排名结果的文档分数。文本索引的默认语言是英语。要更改默认语言,请将language属性设置为您想要的任何语言(例如,Document(language="spanish"))。使用名为languageor的属性Language,您可以在每个文档的基础上定义语言覆盖。以下示例显示了如何创建文本索引并将语言设置为西班牙语:示例.示例文本索引用法
Document(language="spanish")classSomeEntity{
TextIndexedStringfoo;LanguageStringlang;Nestednested;}classNested{TextIndexed(weight=5)Stringbar;Stringroo;}18.5.8.使用DBRefs
映射框架不必存储嵌入在文档中的子对象。您也可以单独存储它们并使用aDBRef来引用该文档。当对象从MongoDB加载时,这些引用会被急切地解析,以便您返回一个映射对象,该对象看起来与嵌入在顶级文档中的存储相同。
以下示例使用DBRef来引用独立于引用它的对象存在的特定文档(为简洁起见,两个类都显示为内嵌):
DocumentpublicclassAccount{
IdprivateObjectIdid;privateFloattotal;}DocumentpublicclassPerson{IdprivateObjectIdid;IndexedprivateIntegerssn;DBRefprivateListAccountaccounts;}您不需要使用
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:StronglyEncapsulateJDKInternalsbyDefault,从Java16+开始,可能需要访问未打开的jdk内部。对于这些情况,请考虑回退到接口类型(例如,从ArrayListto切换List)或提供所需的--add-opens参数。
18.5.9.使用文档参考
Using
DocumentReference提供了一种灵活的方式来引用MongoDB中的实体。虽然目标与使用DBRefs时相同,但存储表示不同。DBRef解析为具有固定结构的文档,如MongoDB参考文档中所述。文档引用,不遵循特定格式。它们实际上可以是任何东西,单个值,整个文档,基本上可以存储在MongoDB中的所有内容。默认情况下,映射层将使用引用的实体id值进行存储和检索,如下面的示例所示。DocumentclassAccount{
IdStringid;Floattotal;}DocumentclassPerson{IdStringid;DocumentReferenceListAccountaccounts;}Accountaccount=…tempate.insert(account);template.update(Person.class).matching(where("id").is(…)).apply(newUpdate().push("accounts").value(account)).first();
{"_id":…,"accounts":["b9e"…]}
标记Account要引用的值的集合。
映射框架不处理级联保存,因此请确保单独保留引用的实体。
添加对现有实体的引用。
引用的Account实体表示为其_id值的数组。
上面的示例使用_id基于fetch查询({_id:?#{#target}})进行数据检索并急切地解析链接的实体。可以使用以下属性更改分辨率默认值(如下所列)
DocumentReference延迟加载可能需要类代理,反过来,由于JEP:StronglyEncapsulateJDKInternalsbyDefault,从Java16+开始,可能需要访问未打开的jdk内部。对于这些情况,请考虑回退到接口类型(例如,从ArrayListto切换List)或提供所需的--add-opens参数。
DocumentReference(lookup)允许定义可能与_id字段不同的过滤器查询,因此提供了一种灵活的方式来定义实体之间的引用,如下面的示例所示,其中Publisher书籍的由其首字母缩略词而不是内部id.
DocumentclassBook{
IdObjectIdid;Stringtitle;ListStringauthor;Field("publisher_ac")DocumentReference(lookup="{acronym:?#{#target}}")Publisherpublisher;}DocumentclassPublisher{IdObjectIdid;Stringacronym;Stringname;DocumentReference(lazy=true)ListBookbooks;}Book文档
{"_id":9a48e32,"title":"TheWardedMan","author":["PeterV.Brett"],"publisher_ac":"DR"}
Publisher文档
{"_id":1a23e45,"acronym":"DR","name":"DelRey",…}
使用该acronym字段查询Publisher集合中的实体。
延迟加载对Book集合的引用。
上面的代码片段显示了使用自定义引用对象时的阅读方面。写作需要一些额外的设置,因为映射信息没有表达出从何#target而来。映射层需要Converter在目标文档和之间注册aDocumentPointer,如下所示:
WritingConverterclassPublisherReferenceConverterimplementsConverterPublisher,DocumentPointerString{
Override publicDocumentPointerStringconvert(Publishersource){ return()-source.getAcronym(); }}如果没有DocumentPointer提供转换器,则可以根据给定的查找查询计算目标参考文档。在这种情况下,关联目标属性的评估如下面的示例所示。
DocumentclassBook{
IdObjectIdid;Stringtitle;ListStringauthor;DocumentReference(lookup="{acronym:?#{acc}}")Publisherpublisher;}DocumentclassPublisher{IdObjectIdid;Stringacronym;Stringname;//...}{"_id":9a48e32,"title":"TheWardedMan","author":["PeterV.Brett"],"publisher":{"acc":"DOC"}}
使用该acronym字段查询Publisher集合中的实体。
查找查询的字段值占位符(如acc)用于形成参考文档。
它也可以对模型关系式的一对许多使用的组合引用
ReadonlyProperty和DocumentReference。这种方法允许链接类型不将链接值存储在拥有文档中,而是存储在引用文档中,如下例所示。DocumentclassBook{
IdObjectIdid;Stringtitle;ListStringauthor;ObjectIdpublisherId;}DocumentclassPublisher{IdObjectIdid;Stringacronym;Stringname;ReadOnlyPropertyDocumentReference(lookup="{publisherId:?#{#self._id}}")ListBookbooks;}Book文档
{"_id":9a48e32,"title":"TheWardedMan","author":["PeterV.Brett"],"publisherId":8cfb}
Publisher文档
{"_id":8cfb,"acronym":"DR","name":"DelRey"}
通过将存储在文档中Book来设置从(引用)到Publisher(所有者)的链接。Publisher.idBook
将持有引用的属性标记为只读。这可以防止Book在Publisher文档中存储对个人的引用。
使用该#self变量访问Publisher文档中的值,并在此检索中Books使用匹配的publisherId.
有了上述所有内容,就可以对实体之间的所有类型的关联进行建模。查看下面的非详尽示例列表,以了解可能的情况。
示例.使用id字段的简单文档引用
classEntity{
DocumentReferenceReferencedObjectref;}//entity{"_id":"8cfb","ref":"9a48e32"}//referencedobject{"_id":"9a48e32"}
MongoDB简单类型可以直接使用,无需进一步配置。
示例.使用带有显式查找查询的id字段的简单文档引用
classEntity{
DocumentReference(lookup="{_id:?#{#target}}")ReferencedObjectref;}//entity{"_id":"8cfb","ref":"9a48e32"}//referencedobject{"_id":"9a48e32"}
target定义了参考值本身。
示例.文档参考提取refKey查找查询的字段
classEntity{
DocumentReference(lookup="{_id:?#{refKey}}")privateReferencedObjectref;}WritingConverterclassToDocumentPointerConverterimplementsConverterReferencedObject,DocumentPointerDocument{ publicDocumentPointerDocumentconvert(ReferencedObjectsource){ return()-newDocument("refKey",source.id); }}
//entity{"_id":"8cfb","ref":{"refKey":"9a48e32"}}//referencedobject{"_id":"9a48e32"}
用于获取参考值的密钥必须是写入时使用的密钥。
refKey是的缩写target.refKey。
示例.具有多个值的文档引用形成查找查询
classEntity{
DocumentReference(lookup="{firstname:?#{fn},lastname:?#{ln}}")ReferencedObjectref;}//entity{"_id":"8cfb","ref":{"fn":"Josh","ln":"Long"}}//referencedobject{"_id":"9a48e32","firsntame":"Josh","lastname":"Long",}
读/WIRTE键fn和ln自/至基于查找查询的链接文件。
使用非id字段来查找目标文档。
示例.从目标集合中读取文档引用
classEntity{
DocumentReference(lookup="{_id:?#{id}}",collection="?#{collection}")privateReferencedObjectref;}WritingConverterclassToDocumentPointerConverterimplementsConverterReferencedObject,DocumentPointerDocument{ publicDocumentPointerDocumentconvert(ReferencedObjectsource){ return()-newDocument("id",source.id).append("collection",…); }}
//entity{"_id":"8cfb","ref":{"id":"9a48e32","collection":"…"}}
_id从/向参考文档读取/写入密钥以在查找查询中使用它们。
可以使用其键从参考文档中读取集合名称。
我们知道在查找查询中使用各种MongoDB查询运算符很诱人,这很好。但是有几个方面需要考虑:
确保有支持您查找的索引。
请注意,解析需要服务器往返导致延迟,请考虑使用惰性策略。
使用or运算符批量加载文档引用集合。
尽最大努力在内存中恢复原始元素顺序。仅在使用等式表达式时才可以恢复顺序,而在使用MongoDB查询运算符时则无法恢复。在这种情况下,结果将在从商店或通过提供的
DocumentReference(sort)属性收到时进行排序。一些更一般的评论:
你使用循环引用吗?问问你自己是否需要它们。
懒惰的文档引用很难调试。确保工具不会意外触发代理解析,例如调用toString().
不支持使用反应式基础架构阅读文档引用。
18.5.10.映射框架事件
在映射过程的整个生命周期中都会触发事件。这在生命周期事件部分进行了描述。
在SpringApplicationContext中声明这些bean会导致在调度事件时调用它们。
18.6.展开类型
解包实体用于在Java域模型中设计值对象,其属性被展平到父级的MongoDB文档中。
18.6.1.展开类型映射
考虑以下User.name用
Unwrapped.该Unwrapped注释信号是所有属性UserName应该被平整出到user拥有该文档name属性。示例.解包对象的示例代码
classUser{
IdStringuserId;Unwrapped(onEmpty=USE_NULL)UserNamename;}classUserName{Stringfirstname;Stringlastname;}{"_id":"1da2ba06-3ba7","firstname":"Emma","lastname":"Frost"}
当装载name属性其值被设置为null如果两个firstname和lastname要么null或不存在。通过使用onEmpty=USE_EMPTY一个空的UserName,null其属性的潜在价值,将被创建。
对于不太冗长的可嵌入类型声明,请使用
Unwrapped.NullableandUnwrapped.Empty代替Unwrapped(onEmpty=USE_NULL)andUnwrapped(onEmpty=USE_EMPTY)。这两个注释都使用JSR-javax.annotation.Nonnull进行元注释,以帮助进行可空性检查。可以在展开的对象中使用复杂类型。但是,那些不能是,也不能包含未包装的字段本身。
18.6.2.解包类型字段名称
通过使用注解的可选prefix属性,一个值对象可以被多次解包
Unwrapped。通过添加,所选的前缀被添加到Field("…")解包对象中的每个属性或名称之前。请注意,如果多个属性呈现为相同的字段名称,则值将相互覆盖。示例.带有名称前缀的解包对象的示例代码
classUser{
IdStringuserId;Unwrapped.Nullable(prefix="u_")UserNamename;Unwrapped.Nullable(prefix="a_")UserNamename;}classUserName{Stringfirstname;Stringlastname;}{"_id":"a6abd-f95f","u_firstname":"Jean","u_lastname":"Grey","a_firstname":"Something","a_lastname":"Else"}
的所有属性UserName都以为前缀u_。
的所有属性UserName都以为前缀a_。
虽然将
Field注释与Unwrapped相同的属性组合在一起没有意义,因此会导致错误。这是用于Field任何未包装类型属性的完全有效的方法。示例.使用
Field注释解开对象的示例代码publicclassUser{
IdprivateStringuserId;Unwrapped.Nullable(prefix="u-")UserNamename;}publicclassUserName{ Field("first-name")privateStringfirstname; Field("last-name")privateStringlastname;}{"_id":"f7b9-89da","u-first-name":"Barbara","u-last-name":"Gordon"}
的所有属性UserName都以为前缀u-。
最终字段名称是连接
Unwrapped(prefix)和的结果Field(name)。18.6.3.查询解包对象
可以在类型和字段级别上定义对未包装属性的查询,因为所提供的Criteria内容与域类型相匹配。呈现实际查询时将考虑前缀和潜在的自定义字段名称。使用解包对象的属性名称匹配所有包含的字段,如下面的示例所示。
示例.查询解包对象
UserNameuserName=newUserName("Carol","Danvers")QueryfindByUserName=query(where("name").is(userName));Useruser=template.findOne(findByUserName,User.class);
db.collection.find({"firstname":"Carol","lastname":"Danvers"})
也可以直接使用其属性名称来寻址解包对象的任何字段,如下面的代码片段所示。
示例.查询未包装对象的字段
QueryfindByUserFirstName=query(where("name.firstname").is("Shuri"));ListUserusers=template.findAll(findByUserFirstName,User.class);
db.collection.find({"firstname":"Shuri"})
按展开的字段排序。
展开对象的字段可用于通过其属性路径进行排序,如下面的示例所示。
示例.在展开的字段上排序
QueryfindByUserLastName=query(where("name.lastname").is("Romanoff"));ListUseruser=template.findAll(findByUserName.withSort(Sort.by("name.firstname")),User.class);
db.collection.find({"lastname":"Romanoff"}).sort({"firstname":1})
尽管可能,使用解包对象本身作为排序标准包括其所有字段的不可预测顺序,并可能导致排序不准确。
展开物体上的场投影
展开对象的场可以作为整体或通过单个场进行投影,如下面的示例所示。
示例.在展开的对象上投影。
QueryfindByUserLastName=query(where("name.firstname").is("Gamora"));findByUserLastName.fields().include("name");ListUseruser=template.findAll(findByUserName,User.class);
db.collection.find({"lastname":"Gamora"},{"firstname":1,"lastname":1})
展开对象上的场投影包括其所有属性。
示例.在展开的对象的字段上投影。
QueryfindByUserLastName=query(where("name.lastname").is("Smoak"));findByUserLastName.fields().include("name.firstname");ListUseruser=template.findAll(findByUserName,User.class);
db.collection.find({"lastname":"Smoak"},{"firstname":1})
展开对象上的场投影包括其所有属性。
在未包装的对象上按示例查询。
展开的对象可以Example像任何其他类型一样在探测器中使用。请查看按示例查询部分,以了解有关此功能的更多信息。
对解包对象的存储库查询。
该Repository抽象允许导出对未包装对象的字段以及整个对象的查询。
示例.对解包对象的存储库查询。
interfaceUserRepositoryextendsCrudRepositoryUser,String{ ListUserfindByName(UserNameusername); ListUserfindByNameFirstname(Stringfirstname);}
匹配解包对象的所有字段。
与firstname.
即使存储库create-query-indexes命名空间属性设置为,为解包对象创建索引也会暂停true。
18.6.4.展开对象的更新
展开的对象可以作为域模型的一部分的任何其他对象进行更新。映射层负责将结构展平到其周围环境中。可以更新解包对象的单个属性以及整个值,如下面的示例所示。
示例.更新解包对象的单个字段。
Updateupdate=newUpdate().set("name.firstname","Janet");template.update(User.class).matching(where("id").is("Wasp")).apply(update).first()
db.collection.update({"_id":"Wasp"},{"set"{"firstname":"Janet"}},{...})
示例.更新一个展开的对象。
Updateupdate=newUpdate().set("name",newName("Janet","vanDyne"));template.update(User.class).matching(where("id").is("Wasp")).apply(update).first()
db.collection.update({"_id":"Wasp"},{"set"{"firstname":"Janet","lastname":"vanDyne",}},{...})
18.6.5.未包装对象上的聚合
该聚合框架会试图映射类型聚集的展开值。在引用其值之一时,请确保使用包括包装器对象的属性路径。除此之外,不需要特殊操作。
18.6.6.展开对象的索引
可以将
Indexed注释附加到解包类型的属性,就像对常规对象所做的那样。不能Indexed与Unwrapped拥有属性的注释一起使用。publicclassUser{
IdprivateStringuserId;Unwrapped(onEmpty=USE_NULL)UserNamename;//Invalid-InvalidDataAccessApiUsageExceptionIndexedUnwrapped(onEmpty=USE_Empty)Addressaddress;}publicclassUserName{privateStringfirstname;IndexedprivateStringlastname;}索引创建lastname的users集合。
Indexed一起使用无效
Unwrapped18.7.自定义转换-覆盖默认映射
影响映射结果的最简单的方法是通过
Field注释指定所需的本机MongoDB目标类型。这允许BigDecimal在域模型中使用非MongoDB类型,同时以本机org.bson.types.Decimal格式持久化值。示例.显式目标类型映射
publicclassPayment{
IdStringid;Field(targetType=FieldType.DECIMAL)BigDecimalvalue;Datedate;}{"_id":ObjectId("5ca4a34faab36af8"),"value":NumberDecimal(2.),"date":ISODate("9-04-03T12:11:01.Z")}
表示有效的字符串id值ObjectId会自动转换。有关
详细信息,请参阅如何_id在映射层中处理字段。
所需的目标类型明确定义为Decimal转换为NumberDecimal.否则,该
BigDecimal值将被调整为String.
Date值由MongoDB驱动程序本身处理并存储为ISODate.
上面的代码片段对于提供简单的类型提示很方便。要对映射过程进行更细粒度的控制,您可以使用MongoConverter实现注册Spring转换器,例如MappingMongoConverter.
MappingMongoConverter在尝试映射对象本身之前,检查是否有任何Spring转换器可以处理特定的类。要“劫持”的正常映射策略MappingMongoConverter,也许是为了提高性能或其它自定义映射的需求,首先需要创建春天的实现Converter接口,然后用它注册MappingConverter。
有关Spring类型转换服务的更多信息,请参阅此处的参考文档。
#开发#