Spring认证中国教育管理中心-Spring Data MongoDB教程十四

2021-11-29 17:43:59 浏览数 (1)

原标题: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 类型转换服务的更多信息,请参阅此处的参考文档。

0 人点赞