7.4. 将实体映射到索引结构


7.4.1. 映射实体

索引实体所需的所有元数据信息都通过注释进行描述,因此无需 XML 映射文件。您仍然可以将 Hibernate 映射文件用于基本 Hibernate 配置,但是必须通过注释来表示 Hibernate Search 特定配置。

7.4.1.1. 基本映射

让我们从最常用于映射实体的注释开始。

基于 Lucene 的查询 API 使用以下常见注解来映射实体:

  • @Indexed
  • @Field
  • @NumericField
  • @Id

7.4.1.2. @Indexed

最重要的是,我们必须将持久类声明为可索引。这可以通过使用 @Indexed 为类添加注解(索引流程将忽略未注释 @Indexed 的 所有实体):

@Entity
@Indexed
public class Essay {
...
}
Copy to Clipboard Toggle word wrap

您可以选择指定 @Indexed 注释的 index 属性,以更改索引的默认名称。

7.4.1.3. @Field

对于实体的每个属性(或属性),您可以描述如何对其进行索引。默认(不存在)表示索引过程中会忽略该属性。

注意

在 Hibernate Search 5 之前,只有通过 @NumericField 明确请求时,才会选择数字字段编码。从 Hibernate Search 5 开始,系统将自动为数字类型选择此编码。为避免数字编码,您可以通过 @Field.bridge 或 @Field Bridge 明确指定非数字字段网桥。软件包 org.hibernate.search.bridge.builtin 包含一组网桥,这些网桥编号编码为字符串,如 org.hibernate.search.bridge.builtin.IntegerBridge

@field 确实将属性声明 为索引,并允许通过设置以下一个或多个属性来配置索引过程的多个方面:

  • Name : describe 在哪个名称下,该属性应存储在 Lucene 文档中。默认值为属性名称(遵循 JavaBeans 约定)
  • 存储 :描述属性是否存储在 Lucene 索引中。您可以存储值 Store.YES (在索引中消耗更多空间,但允许 投射,以压缩方式 存储 Store.COMPRESS (这确实消耗更多 CPU),或者避免任何存储 Store.NO (这是默认值)。存储属性时,您可以从 Lucene 文档检索其原始值。这与元素是否索引无关。
  • index :描述属性是否索引。不同的值是 index .NO,这表示它不会被索引,不能被查询和 Index.YES 找到,即元素会被索引并可以搜索。默认值为 Index.YESindex. no 在不需要可搜索属性但应该可用于投射的情形中,将非常有用。

    注意

    index.NO 与 Analyze .YESNorms.YES 结合使用,因为 分析和 强制要求 对该属性进行索引。

  • 分析 :确定属性是否被分析(Analyze.YES)还是不分析(Analyze.NO)。默认值为 Analyze .YES

    注意

    是否要分析属性取决于您是否希望按原样搜索元素,或按其包含的词语搜索。分析文本字段会有意义,但可能不是日期字段。

    注意

    不得分析用于排序的字段

  • 强制 :描述索引时间提升信息应存储(Norms.YES)还是不存储(Norms.NO)。不存储存储会节省大量内存,但没有任何索引时间提高可用信息。默认值为 Norms.YES
  • termVector :描述术语频率对的集合。这个属性允许在索引过程中将术语向量存储在文档中。默认值为 TermVector.NO

    此属性的不同值有:

    Expand
    定义

    TermVector.YES

    存储每个文档的术语向量。这会生成两个同步的数组,一个包含文档术语,另一个包含术语的频率。

    TermVector.NO

    不要存储术语向量。

    TermVector.WITH_OFFSETS

    存储术语向量和令牌偏移信息。这与 TermVector.YES 加上它包含术语的起始和结束偏移位置信息相同。

    TermVector.WITH_POSITIONS

    存储术语向量和令牌位置信息。这与 TermVector.YES 相同,还包含每一次在文档中出现某个术语的规范。

    TermVector.WITH_POSITION_OFFSETS

    存储术语向量、令牌位置和偏移信息。这是 YES、WITH_OFFSETS 和 WITH_POSITIONS 的组合。

  • indexNullAs : Per default null 值将被忽略且未索引。不过,您可以使用 indexNullAs 指定一个字符串,该字符串将被插入为 null 值的令牌。默认情况下,此值设置为 Field。DO_NOT_INDEX_NULL 表示不应索引 null 值。您可以将这个值设置为 Field.DEFAULT_NULL_TOKEN,以指示应使用默认的 令牌。可以使用 hibernate.search.default_null_token 在配置中指定此默认 令牌。如果未设置此属性,并且指定了 Field.DEFAULT_NULL_TOKEN,则字符串 "null" 将用作默认值。

    注意

    使用 indexNullAs 参数时,务必要在搜索查询中使用相同的令牌来搜索 null 值。此外,建议仅在未分析的字段(分析.NO)中使用此功能。

    警告

    在实施自定义 FieldBridge 或 TwoWayFieldBridge 时,开发人员可以处理 null 值的索引(请参阅 JavaDocs LuceneOptions.indexNullAs())。

7.4.1.4. @NumericField

@Field 有一个相应的注释,名为 @NumericField,其范围与 @Field 或 @DocumentId 相同。它可用于 Integer、Long、Float 和 Double 属性。在索引时,将使用 Trie 结构对该值进行索引。当属性索引为数字字段时,它实现了有效的范围查询和排序,与对标准 @Field 属性执行相同的查询相比,它实现了高效的范围查询和排序。@NumericField 注释接受以下参数:

Expand
定义

forField

(可选)指定将索引为数字的相关 @Field 的名称。只有 属性包含超过 @Field 声明时,才强制使用它

precisionStep

(可选)更改 Trie 结构存储在索引中的方式。较小的精度Steps会导致更多磁盘空间使用量、更快的范围和排序查询。较大的值会导致使用空间减少,范围查询性能更接近普通 @Fields 中的范围查询。默认值为 4。

@NumericField 仅支持 Double、Long、Integer 和 Float。其他数字类型无法利用 Lucene 中的类似功能,因此其余类型应使用默认或自定义 TwoWayFieldBridge 进行字符串编码。

假设您可以在类型转换过程中处理近似值,可以使用自定义 NumericFieldBridge:

示例:定义自定义数字FieldBridge

public class BigDecimalNumericFieldBridge extends NumericFieldBridge {
   private static final BigDecimal storeFactor = BigDecimal.valueOf(100);

   @Override
   public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
      if ( value != null ) {
         BigDecimal decimalValue = (BigDecimal) value;
         Long indexedValue = Long.valueOf( decimalValue.multiply( storeFactor ).longValue() );
         luceneOptions.addNumericFieldToDocument( name, indexedValue, document );
      }
   }

    @Override
    public Object get(String name, Document document) {
        String fromLucene = document.get( name );
        BigDecimal storedBigDecimal = new BigDecimal( fromLucene );
        return storedBigDecimal.divide( storeFactor );
    }

}
Copy to Clipboard Toggle word wrap

7.4.1.5. @Id

最后,实体的 id (识别符)属性是 Hibernate Search 使用的特殊属性,用于确保给定实体的索引唯一性。按照设计,必须存储 id,且不能进行令牌化。要将属性标记为索引标识符,可使用 @DocumentId 注释。如果您使用的是 Jakarta Persistence 并且指定了 @Id,您可以省略 @DocumentId。选定的实体标识符也可用作文档标识符。

Infinispan Query 使用实体的 id 属性来确保索引唯一标识。按照设计,ID 会存储,且不得转换为令牌。要将属性标记为索引 ID,可使用 @DocumentId 注释。

示例:指定索引的属性

@Entity
@Indexed
public class Essay {
    ...
    @Id
    @DocumentId
    public Long getId() { return id; }

    @Field(name="Abstract", store=Store.YES)
    public String getSummary() { return summary; }

    @Lob
    @Field
    public String getText() { return text; }

    @Field @NumericField( precisionStep = 6)
    public float getGrade() { return grade; }
}
Copy to Clipboard Toggle word wrap

上例定义了四个字段的索引: id、Abs tract文本评级。请注意,默认情况下字段名称没有大写,符合 JavaBean 规范。grade 字段标为数字,其精确步骤略大于默认值。

7.4.1.6. 映射属性多次

有时,您需要为每个索引多次映射一个属性,索引策略略有不同。例如,根据字段排序查询需要取消分析字段。要按此属性上的词语搜索,仍需要对它进行索引 - 旦分析过,一旦未分析一次。@Fields 允许您实现此目标。

示例:使用 @Fields 映射多个属性时间

@Entity
@Indexed(index = "Book" )
public class Book {
    @Fields( {
            @Field,
            @Field(name = "summary_forSort", analyze = Analyze.NO, store = Store.YES)
            } )
    public String getSummary() {
        return summary;
    }
    ...
}
Copy to Clipboard Toggle word wrap

在本例中,字段 概述 被索引两次,一次以令牌化的方式作为 概述,一次是以未令牌的方式作为 summary_forSort 索引。

7.4.1.7. 嵌入式和关联对象

可以将关联的对象和嵌入式对象作为根实体索引的一部分进行索引。如果您希望根据相关对象的属性搜索给定实体,这很有用。目标是返回相关城市为 Atlanta 的位置(在 Lucene 查询解析器语言中,语言将转换为 address.city:Atlanta)。位置字段将在位置索引中 索引Place 索引文档还将包含您可以 查询的 address.id、address .street 和 address.city 字段。

示例:索引关联

@Entity
@Indexed
public class Place {
    @Id
    @GeneratedValue
    @DocumentId
    private Long id;

    @Field
    private String name;

    @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } )
    @IndexedEmbedded
    private Address address;
    ....
}

@Entity
public class Address {
    @Id
    @GeneratedValue
    private Long id;

    @Field
    private String street;

    @Field
    private String city;

    @ContainedIn
    @OneToMany(mappedBy="address")
    private Set<Place> places;
    ...
}
Copy to Clipboard Toggle word wrap

由于在使用 @IndexedEmbedded 技术时,Lucene 索引中数据已被非常规化,因此 Hibernate Search 必须了解 Place 对象中的任何变化,以及 Address 对象中的任何更改,以便索引保持最新。为确保在地址发生更改时 Lucene 文档得到更新,请使用 @ContainedIn 标记双向关系的另一侧。

注意

@ContainedIn 对于指向实体的关联和嵌入式(收集)对象的关联都很有用。

若要对此展开,以下示例演示了嵌套 @IndexedEmbedded

示例:@IndexedEmbedded 和 @ContainedIn 的嵌套使用

@Entity
@Indexed
public class Place {
    @Id
    @GeneratedValue
    @DocumentId
    private Long id;

    @Field
    private String name;

    @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } )
    @IndexedEmbedded
    private Address address;
    ....
}

@Entity
public class Address {
    @Id
    @GeneratedValue
    private Long id;

    @Field
    private String street;

    @Field
    private String city;

    @IndexedEmbedded(depth = 1, prefix = "ownedBy_")
    private Owner ownedBy;

    @ContainedIn
    @OneToMany(mappedBy="address")
    private Set<Place> places;
    ...
}

@Embeddable
public class Owner {
    @Field
    private String name;
   ...
}
Copy to Clipboard Toggle word wrap

任何 @*ToMany@*ToOne@Embedded 属性都可以标上 @IndexedEmbedded。然后,关联的类的属性将添加到主实体索引中。索引将包含以下字段:

  • id
  • name
  • address.street
  • address.city
  • address.ownedBy_name

默认前缀是 propertyName.,遵循传统的对象导航约定。您可以使用 prefix 属性覆盖它,如 ownedBy 属性中所示。

注意

前缀不能设置为空字符串。

当对象图包含类(而非实例)的循环依赖项时,需要 深度 属性。例如,如果所有者指向位置:Hibernate 搜索将在到达预期深度(或到达对象图形边界)后停止包含索引的嵌入式属性。具有自引用的类是 cyclic 依赖项示例。在我们的示例中,由于 深度 设置为 1,Owner 中的任何 @IndexedEmbedded 属性都将被忽略。

使用 @IndexedEmbedded 作为对象关联可让您表达查询(使用 Lucene 的查询语法),例如:

  • 返回名称包含 JBoss 以及地址城市为 Atlanta 的位置。在 Lucene 查询中,这是:

    +name:jboss +address.city:atlanta
    Copy to Clipboard Toggle word wrap
  • 返回名称包含 JBoss 的位置,以及所有者名称包含 Joe 的位置。在 Lucene 查询中,该选项为

    +name:jboss +address.ownedBy_name:joe
    Copy to Clipboard Toggle word wrap

此行为以更有效的方式(降低数据重复的成本)模仿关系连接操作。请记住,开箱即用时 Lucene 索引没有任何关联概念,连接操作也不存在。这有助于保持关系模式规范化,同时从完整的文本索引速度和特性丰富的中受益。

注意

关联的对象本身(但不必)为 @Indexed

@IndexedEmbedded 指向某一实体时,其关联必须是方向的,并且必须给另一方添加注释 @ContainedIn (如上例中所示)。如果没有,Hibernate Search 将无法在相关实体更新时更新根索引(在本示例中,相关地址实例更新时必须更新 Place 索引文档)。

有时,由 @IndexedEmbedded 标注的对象类型并非 Hibernate 和 Hibernate Search 所针对的对象类型。当接口用于代替其实施时,尤其会出现这种情况。因此,您可以使用 targetElement 参数覆盖 Hibernate Search 所针对的对象类型。

示例:使用 @IndexedEmbedded targetElement 属性

@Entity
@Indexed
public class Address {
    @Id
    @GeneratedValue
    @DocumentId
    private Long id;

    @Field
    private String street;

    @IndexedEmbedded(depth = 1, prefix = "ownedBy_", )
    @Target(Owner.class)
    private Person ownedBy;
    ...
}

@Embeddable
public class Owner implements Person { ... }
Copy to Clipboard Toggle word wrap

7.4.1.8. 将对象嵌入式限制为特定路径

@IndexedEmbedded 注释也提供 includePaths 属性,可用于作为深度的替代方案或与其组合。

仅使用深度时,会在同一深度以递归方式添加嵌入式类型的所有索引字段。这样更加难以仅选择特定路径而不添加所有其他字段,而可能不需要这些字段。

为避免不必要的加载和索引实体,您可以精确指定所需的路径。典型的应用程序可能需要不同路径的不同深度,或者换句话说可能需要明确指定路径,如下例所示:

示例:使用 @IndexedEmbedded 的 includePaths 属性

@Entity
@Indexed
public class Person {

   @Id
   public int getId() {
      return id;
   }

   @Field
   public String getName() {
      return name;
   }

   @Field
   public String getSurname() {
      return surname;
   }

   @OneToMany
   @IndexedEmbedded(includePaths = { "name" })
   public Set<Person> getParents() {
      return parents;
   }

   @ContainedIn
   @ManyToOne
   public Human getChild() {
      return child;
   }

    ...//other fields omitted
Copy to Clipboard Toggle word wrap

使用上例中所示的映射,您可以按 名称和 /或 姓氏 搜索 Person,以及/或父 名称。它不会索引父名称 的姓氏,因此无法对父名进行搜索,但会加快索引、节省空间并提高整体性能。

@IndexedEmbeddedincludePaths 除常规地为深度指定有限值的索引外,还包括指定的路径。使用 includePaths 并保留深度未定义时,行为等同于设置 depth=0:只有包含的路径才会被索引。

示例:使用 @IndexedEmbedded 的 includePaths 属性

@Entity
@Indexed
public class Human {

   @Id
   public int getId() {
      return id;
   }

   @Field
   public String getName() {
      return name;
   }

   @Field
   public String getSurname() {
      return surname;
   }

   @OneToMany
   @IndexedEmbedded(depth = 2, includePaths = { "parents.parents.name" })
   public Set<Human> getParents() {
      return parents;
   }

   @ContainedIn
   @ManyToOne
   public Human getChild() {
      return child;
   }

    ...//other fields omitted
Copy to Clipboard Toggle word wrap

在上面的示例中,每个人都将拥有其名称和姓氏属性索引。父项的名称和姓氏也将进行索引,其中最多可递归为第二行,因为存在depth 属性。可以按姓名或姓氏直接搜索该人员、其父项或父项。除第二个级别外,我们还会再为一个级别编制索引,而仅索引名称,而非姓氏。

这会在索引中生成以下字段:

  • ID :作为主密钥
  • _hibernate_class :存储实体类型
  • 名称 :作为直接字段
  • Sur name:作为直接字段
  • parent.name :作为嵌入式字段(深度 1)
  • parent.surname :作为嵌入式字段,在深度 1 中
  • parent.parents.name :作为嵌入式字段,位于深度 2
  • parent.parents.surname :作为嵌入式字段,显示深度 2
  • parent.parents.parents.name :作为 includePaths 指定的额外路径。第一个 父级. 从字段名称中推断,剩余的路径是 includePaths 的属性。

如果您要首先定义所需的查询(如此时您准确知道需要哪些字段)以及不需要哪些其他字段来实施您的用例,那么明确控制索引路径可能会更加简单。

返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2025 Red Hat