系统要求
- Java 运行时
-
要使用 Hibernate Search 5.11 系列(5.11.12.Final)的最新版本,您需要 Java 8 或更高版本。您可以在 此处 下载适用于 Windows/Linux/Solaris 的 Java 运行时。
- Hibernate Search
-
hibernate-search-orm-5.11.12.Final.jar 和所有运行时依赖项。您可以从 Hibernate Search 发行版的 dist/lib 目录获取 jar 文件,也可以从 Maven 中央存储库下载它们。
- Hibernate ORM
-
您将需要
hibernate-core
版本 5.4 及其传递依赖项。有关如何获取这些依赖项的信息,请参阅 此处。 - JPA 2.2
-
即使 Hibernate Search 可以不依赖 JPA 注解使用,以下说明也会使用它们来进行基本的实体配置(@Entity, @Id, @OneToMany,…)。配置的这一部分也可以用 xml 或代码表示。Hibernate Search 有自己的一套注解(@Indexed, @DocumentId, @Field,…),目前还没有基于 XML 的替代方案;如果注解不适合您的项目,更好的选择是 程序化映射 API。
如果您无法升级到 Java 7 以上版本,可以使用 Hibernate Search 5.6.x 版本。使用 Java 6 的用户应查看旧版本的 Hibernate Search 4.x。对于 Java 5,您可以使用 Hibernate Search 3.x。
使用 Maven
Hibernate Search 的构件可以在 Maven 的中央仓库找到,但首先在 JBoss Maven 仓库发布。为了确保您始终能及时获得最新更新,我们建议您将此仓库添加到您的全局 settings.xml 文件中(有关详细信息,请参阅 Maven 入门)。
要开始使用,您只需将以下内容添加到您的 pom.xml 文件中。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<version>{latest_stable}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-elasticsearch</artifactId>
<version>{latest_stable}</version>
</dependency>
您也可以选择将索引存储在 Infinispan 集群中;有关依赖关系以及如何配置它的信息,请参阅 Infinispan 目录提供程序文档。
配置
下一步是在您的 Hibernate 配置文件中添加几个属性。如果您直接使用 Hibernate,这可以在 hibernate.properties 或 hibernate.cfg.xml 中完成;如果您通过 JPA 使用 Hibernate,则可以在 persistence.xml 中添加这些相同的属性。
大多数配置设置都有合理的默认值:要开始使用,您无需添加任何属性。
一个示例配置可能如下所示
...
<property name="hibernate.search.default.directory_provider"
value="filesystem"/>
<property name="hibernate.search.default.indexBase"
value="/var/lucene/indexes"/>
...
首先,您必须告诉 Hibernate Search 使用哪个 DirectoryProvider。可以通过设置 hibernate.search.default.directory_provider 属性来实现。Apache Lucene 有一个存储索引文件的 Directory 概念。Hibernate Search 通过 DirectoryProvider 处理 Lucene Directory 实例的初始化和配置。在本教程中,我们将使用一个将索引存储在文件系统上的目录提供程序。这将使我们能够检查由 Hibernate Search 创建的 Lucene 索引(例如,通过 Luke)。有关如何配置不同的 Directory 实现,请参阅 目录配置。
当使用 filesystem DirectoryProvider 时,您还必须通过属性 hibernate.search.default.indexBase 指定所有索引的默认基本目录。
假设您的应用程序包含 Hibernate 管理的类 example.Book 和 example.Author,您想向您的应用程序添加全文搜索功能,以便搜索数据库中包含的书籍。
package example;
...
@Entity
public class Book {
@Id
@GeneratedValue
private Integer id;
private String title;
private String subtitle;
@ManyToMany
private Set<Author> authors = new HashSet<Author>();
private Date publicationDate;
public Book() {}
// standard getters/setters follow here
...
}
package example;
...
@Entity
public class Author {
@Id
@GeneratedValue
private Integer id;
private String name;
public Author() {}
// standard getters/setters follow here
...
}
在您的实体上启用全文搜索功能
为此,您必须在 Book 和 Author 类中添加一些注解。
选择一个唯一的标识符
Hibernate Search 需要将实体标识符存储在每个实体的索引中。默认情况下,它将为此目的使用用 @Id 标记的字段,但您可以使用 @DocumentId 覆盖它(仅限高级用户)。
选择要索引的内容以及如何索引
接下来,您需要标记要使其可搜索的字段。让我们从标题和副标题开始,并用 @Field 注释它们。
参数 index=Index.YES 将确保文本将被索引,而 analyze=Analyze.YES 确保文本将使用默认的 Lucene 分析器进行分析。
分析器选项是一个重要的概念,我们将在参考文档中更好地解释。为了简单介绍,让我们简化一下,说分析意味着将一个句子分成单独的单词,将其小写,并可能排除像 'a' 或 'the' 这样的常用词。
存储选项和投影
第三个参数 store=Store.NO 确保实际数据不会存储在索引中。此数据是否存储在索引中与是否可以搜索它无关:存储它的好处是能够通过投影检索它(请参阅 投影)。
当不使用投影时,Hibernate Search 将执行一个 Lucene 查询,以找到与查询匹配的实体的数据库标识符,并使用这些标识符从数据库中检索管理对象。如果您使用投影,您可能会避免往返数据库,但这只会返回对象数组,而不是从正常查询中获得的管理对象。
请注意,index=Index.YES、analyze=Analyze.YES 和 store=Store.NO 是这些参数的默认值,可以省略。
某些类型可能需要编码
Lucene 索引主要基于字符串,并提供对数字类型的额外支持。因此,Hibernate Search 必须将索引字段的数据类型转换为字符串,反之亦然。例外是 Integer、Long、Calendar 等属性,这些属性都被索引为 NumericField,这意味着它们将被编码为更适合范围查询的表示形式。
提供了许多预定义的桥接器,例如 BooleanBridge 将将类型为 Boolean 的属性编码为文字 "true" 或 "false";通过这样做,它们可以通过关键字搜索。
在我们的示例中,Book 实体有一个 Date 属性,因此,如果我们想让此属性也可搜索,我们需要用 @Field 和 @DateBridge 注释它。
有关更多详细信息,请参阅 桥接器。
关联实体的索引
@IndexedEmbedded 注解用于索引关联实体,例如通常通过 @ManyToMany、@OneToOne、@ManyToOne、@Embedded 和 @ElementCollection 定义的实体。
但是请注意,关联实体的属性被嵌入到用 @Indexed 标记的实体的同一个索引条目中,本质上是对数据进行反规范化。这是必要的,因为 Lucene 索引文档是一个扁平的数据结构,不适合存储关系信息。
在我们的示例中,为了确保作者的姓名可搜索,您必须确保姓名在书本身中被索引。在 @IndexedEmbedded 之外,您还需要用 @Indexed 标记要包含在索引中的关联实体的所有字段。有关更多详细信息,请参阅 嵌入式和关联对象。
更高级的模型
这些是您需要了解的所有关于快速入门实体映射的注解。有关实体映射的更多详细信息,请参阅 映射实体。
package example;
...
@Entity
@Indexed
public class Book {
@Id
@GeneratedValue
private Integer id;
@Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
private String title;
@Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
private String subtitle;
@Field(index=Index.YES, analyze=Analyze.NO, store=Store.YES)
@DateBridge(resolution=Resolution.DAY)
private Date publicationDate;
@IndexedEmbedded
@ManyToMany
private Set<Author> authors = new HashSet<Author>();
public Book() {
}
// standard getters/setters follow here
...
}
package example;
...
@Entity
public class Author {
@Id
@GeneratedValue
private Integer id;
@Field
private String name;
public Author() {
}
// standard getters/setters follow here
...
}
索引
简短的答案是索引是自动的:Hibernate Search 会在每次通过 Hibernate ORM 持久化、更新或删除实体时透明地索引每个实体。它的任务是保持索引和数据库同步,让您不必担心这个问题。
但是,当在现有应用程序中引入 Hibernate Search 时,您必须为数据库中已有的数据创建一个初始 Lucene 索引。
在您添加了上述属性和注解之后,如果您在数据库中存在现有数据,您需要触发对您的书籍进行初始批量索引。这将重建您的索引,以确保您的索引和您的数据库同步。您可以通过使用以下代码片段之一来实现这一点(另请参阅 重建整个索引)
FullTextSession fullTextSession = Search.getFullTextSession(session);
fullTextSession.createIndexer().startAndWait();
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.createIndexer().startAndWait();
执行完上述代码后,您应该可以在 /var/lucene/indexes/example.Book 下看到一个 Lucene 索引。
存储路径的根目录取决于我们在配置步骤中指定的配置属性 hibernate.search.default.indexBase。
您现在可以使用 Luke 检查此索引。它将帮助您了解 Hibernate Search 的工作原理:Luke 允许您检查索引内容和结构,类似于您使用 SQL 控制台检查 Hibernate ORM 在关系数据库上的工作方式。
搜索
现在,我们将最终执行第一次搜索。一般方法是创建一个 Lucene 查询,可以通过 Lucene API(请参阅 使用 Lucene API 构建 Lucene 查询)或通过 Hibernate Search 查询 DSL(使用 Hibernate Search 查询 DSL 构建 Lucene 查询)来实现,然后将此查询包装到 org.hibernate.Query 中,以获得 Hibernate API 中所有常用的功能。本质上
-
创建一个 Lucene 查询(直接使用 Lucene 代码或通过 Hibernate Search DSL)
-
将 Lucene 查询包装到 Hibernate 查询中(org.apache.lucene.search.Query → org.hibernate.Query)
-
执行 Hibernate 查询
以下代码将针对索引字段准备一个查询,执行它,并返回一个 Book 列表。
EntityManager em = entityManagerFactory.createEntityManager();
FullTextEntityManager fullTextEntityManager =
org.hibernate.search.jpa.Search.getFullTextEntityManager(em);
em.getTransaction().begin();
// create native Lucene query unsing the query DSL
// alternatively you can write the Lucene query using the Lucene query parser
// or the Lucene programmatic API. The Hibernate Search DSL is recommended though
QueryBuilder qb = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder().forEntity(Book.class).get();
org.apache.lucene.search.Query luceneQuery = qb
.keyword()
.onFields("title", "subtitle", "authors.name")
.matching("Java rocks!")
.createQuery();
// wrap Lucene query in a javax.persistence.Query
javax.persistence.Query jpaQuery =
fullTextEntityManager.createFullTextQuery(luceneQuery, Book.class);
// execute search
List result = jpaQuery.getResultList();
em.getTransaction().commit();
em.close();
当 Lucene 查询被包装到 Hibernate 或 JPA 标准查询中时,该接口的所有已知方法都可用。
全文搜索简介
现在让我们让事情变得更有趣一些。假设您的一个索引书籍实体的标题为 "Refactoring: Improving the Design of Existing Code",您想获取所有以下查询的命中结果:"refactor"、"refactors"、"refactored" 和 "refactoring"。在 Lucene 中,这可以通过选择一个在索引和搜索过程中应用词干提取的 Analyzer 类来实现。Hibernate Search 提供了几种配置要使用的分析器的方法(请参阅 默认分析器和按类分析器)
-
在配置文件中设置 hibernate.search.analyzer 属性。指定类将成为默认分析器。
-
在实体级别设置 @Analyzer 注解。
-
在字段级别设置 @Analyzer 注解。
当使用 @Analyzer 注解时,您可以指定要使用的分析器的完全限定类名,或者可以引用由 @AnalyzerDef 注解定义的分析器定义。在后一种情况下,将使用 Solr 分析器框架及其工厂方法。要了解更多关于可用的工厂类信息,您可以浏览 Solr JavaDoc 或阅读 Solr Wiki 上的相应部分。
在以下示例中,使用 StandardTokenizerFactory,后面跟着两个过滤器工厂,LowerCaseFilterFactory 和 SnowballPorterFilterFactory。标准分词器在标点符号和连字符处分割单词,同时保持电子邮件地址和互联网主机名完整。它是一个通用的分词器。小写过滤器将每个词中的字母小写,而 snowball 过滤器最终应用特定语言的词干提取。
通常,在使用 Solr 框架时,您会从一个分词器开始,后面跟着任意数量的过滤器。
@Entity
@Indexed
@AnalyzerDef(name = "customanalyzer",
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = SnowballPorterFilterFactory.class, params = {
@Parameter(name = "language", value = "English")
})
})
public class Book {
@Id
@GeneratedValue
@DocumentId
private Integer id;
@Field
@Analyzer(definition = "customanalyzer")
private String title;
@Field
@Analyzer(definition = "customanalyzer")
private String subtitle;
@IndexedEmbedded
@ManyToMany
private Set<Author> authors = new HashSet<Author>();
@Field(index = Index.YES, analyze = Analyze.NO, store = Store.YES)
@DateBridge(resolution = Resolution.DAY)
private Date publicationDate;
public Book() {
}
// standard getters/setters follow here
...
}
使用 @AnalyzerDef,您可以定义一个分析器,您仍然需要使用 @Analyzer 将其应用于实体或属性。就像上面的例子中,自定义分析器被定义,但没有应用于实体:它只应用于标题和副标题属性。
分析器定义不限于实体,因此您可以在任何实体上定义它,并在其他实体上重用该定义。
下一步
以上段落介绍了 Hibernate Search,但它支持更多功能。
例如,过滤器可以为添加重复限制提供非常方便的 API,或者空间查询可以根据与坐标的距离添加限制。
本教程的下一步是更加熟悉 Hibernate Search 的整体架构 (架构),并更详细地探索基本功能。本教程中只简要介绍了两个主题,分别是分析器配置 (默认分析器和按类分析器) 和字段桥 (桥接)。两者都是进行更细粒度索引所必需的重要功能。更高级的主题涵盖集群 (JMS 主/从后端, Infinispan 目录配置),大型索引处理 (分片索引),空间索引, 分面.