7.5. 使用 Hibernate Search 执行 Lucene 查询
流程
Hibernate Search 可以执行 Lucene 查询并检索由 InfinispanHibernate 会话管理的域对象。搜索提供了 Lucene 的强大功能,无需离开 Hibernate 范式,为 Hibernate 典型搜索机制(HQL、标准查询、本地 SQL 查询)提供了另一个维度。
准备并执行查询由以下四个步骤组成:
- 创建 FullTextSession
- 使用 Hibernate QueryHibernate Search 查询 DSL(推荐)或使用 Lucene 查询 API 创建 Lucene 查询 API
- 使用 org.hibernate.Query 包装 Lucene 查询
- 通过调用 example list()或 scroll()执行搜索.
若要访问查询功能,请使用 FullTextSession。此搜索特定会话打包了一个常规 org.hibernate.Session,以提供查询和索引功能。
示例:创建 FullTextSession
Session session = sessionFactory.openSession(); ... FullTextSession fullTextSession = Search.getFullTextSession(session);
Session session = sessionFactory.openSession();
...
FullTextSession fullTextSession = Search.getFullTextSession(session);
使用 FullTextSession 使用 Hibernate Search 查询 DSL 或原生 Lucene 查询构建全文本查询。
在使用 Hibernate Search 查询 DSL 时,请使用以下代码:
另外,也可使用 Lucene 查询解析器或 Lucene 编程 API 编写 Lucene 查询。
示例:使用 QueryParser 创建 Lucene Query
基于 Lucene 查询构建的 Hibernate 查询是 org.hibernate.Query。此查询保留与其他 Hibernate 查询工具相同,如 HQL(Hibernate 查询语言)、Native 和 Criteria。使用 list()、uniqueResult()、heerate()和 scroll()等方法以及查询。
Hibernate Jakarta Persistence 也提供相同的扩展:
示例:使用 Jakarta Persistence 创建搜索查询
这些示例中使用了 Hibernate API。通过调整 FullTextQuery 的检索方式,也可使用 Jakarta Persistence 编写相同的示例。
7.5.1. 构建队列 复制链接链接已复制到粘贴板!
Hibernate 搜索查询基于 Lucene 查询构建,允许用户使用任何 Lucene 查询类型。构建查询时,Hibernate Search 使用 org.hibernate.Query 作为查询操作 API,以进行进一步查询处理。
7.5.1.1. 使用 Lucene API 构建 Lucene 查询 复制链接链接已复制到粘贴板!
使用 Lucene API 时,可以使用查询解析器(简单查询)或 Lucene 编程 API(复杂查询)。构建 Lucene 查询超出了 Hibernate 搜索文档的范围。详情请查看在线 Lucene 文档或 Lucene in Action 或 Hibernate Search in Action 的副本。
7.5.1.2. 构建 Lucene Query 复制链接链接已复制到粘贴板!
Lucene 编程 API 启用全文本查询。但是,在使用 Lucene 编程 API 时,参数必须转换为字符串等效,并且还必须将正确的分析器应用到正确的字段。例如,ngram 分析器使用多个 ngrams 作为给定词的令牌,应进行此类搜索。建议将 QueryBuilder 用于此任务。
Hibernate Search 查询 API 流畅,具有以下关键特征:
- 方法名称为英文。因此,API 操作可以被阅读和理解为一系列英语短语和指令。
- 它使用 IDE 自动完成功能,可帮助当前输入前缀的补全并允许用户选择正确的选项。
- 它通常使用连锁方法模式。
- 易于使用和阅读 API 操作。
要使用 API,首先创建一个附加到给定 索引类型的 查询构建器。此 QueryBuilder 知道要使用的分析器以及要应用的字段桥接。可以创建几个 QueryBuilders(涉及查询根目录的每个实体类型一个)。QueryBuilder 派生自 SearchFactory。
QueryBuilder mythQB = searchFactory.buildQueryBuilder().forEntity( Myth.class ).get();
QueryBuilder mythQB = searchFactory.buildQueryBuilder().forEntity( Myth.class ).get();
用于给定字段或字段的分析器也可以被覆盖。
QueryBuilder mythQB = searchFactory.buildQueryBuilder()
.forEntity( Myth.class )
.overridesForField("history","stem_analyzer_definition")
.get();
QueryBuilder mythQB = searchFactory.buildQueryBuilder()
.forEntity( Myth.class )
.overridesForField("history","stem_analyzer_definition")
.get();
查询构建器现在用于构建 Lucene 查询。使用 Lucene 的查询解析器或 Query 对象通过 Lucene 编程 API 生成自定义查询与 Hibernate Search DSL 一起使用。
7.5.1.3. 关键字查询 复制链接链接已复制到粘贴板!
以下示例演示了如何搜索特定词语:
Query luceneQuery = mythQB.keyword().onField("history").matching("storm").createQuery();
Query luceneQuery = mythQB.keyword().onField("history").matching("storm").createQuery();
| 参数 | 描述 |
|---|---|
| keyword() | 使用此参数查找特定词语。 |
| onField() | 使用此参数指定要在哪个 lucene 字段中搜索单词。 |
| matching() | 使用此参数指定搜索字符串的匹配项 |
| createQuery() | 创建 Lucene 查询对象。 |
-
值"storm"通过
historyFieldBridge 传递。这在涉及数字或日期时很有用。 -
然后,字段桥接值传递到用于索引字段
历史记录的分析器。这样可确保查询使用与索引相同的术语转换(小写、ngram、调整等)。如果分析过程为给定词生成了多个术语,则布尔值查询与SHOULD逻辑(大约是一个OR逻辑)一起使用。
要搜索不是字符串类型的属性:
在普通 Lucene 中,Lucene 必须转换为其字符串表示法,本例中为一年。
这种转换适用于任何对象,只要 FieldBridge 具有 objectToString 方法(以及所有内置的 FieldBridge 实施)。
下一个示例搜索使用 ngram 分析器的字段。ngram分析器单词 ngrams 的索引顺序,这有助于避免用户拼写错误。例如,hibernate 的 3 小时是 hib, ibe, ber, ern, rna, nat, ate, ate。
匹配单词"Sisiphus"将小写,然后分为 3 型图:sis、iso、sip、iph、phu、hus。这些 ngram 各自将成为查询的一部分。然后,用户可以找到 Sysiphus 神话(使用 y)。所有操作都为用户透明地完成。
如果用户不希望特定字段使用字段网桥或分析器,则可以调用 ignoreAnalyzer()或 ignoreFieldBridge()函数。
要在同一个字段中搜索多个可能的词语,请在匹配的 子句中添加它们。
//search document with storm or lightning in their history
Query luceneQuery =
mythQB.keyword().onField("history").matching("storm lightning").createQuery();
//search document with storm or lightning in their history
Query luceneQuery =
mythQB.keyword().onField("history").matching("storm lightning").createQuery();
要在多个字段中搜索相同的词语,请使用 onFields 方法。
Query luceneQuery = mythQB
.keyword()
.onFields("history","description","name")
.matching("storm")
.createQuery();
Query luceneQuery = mythQB
.keyword()
.onFields("history","description","name")
.matching("storm")
.createQuery();
有时,即使搜索同一术语,也要与另一个字段不同对待一个字段,为此使用 和Field()方法。
在上例中,只有字段名称增加到 5。
7.5.1.4. Fuzzy Queries 复制链接链接已复制到粘贴板!
要执行 fuzzy 查询(基于 Levenshtein 距离算法),以 关键字 查询开头并添加 fuzzy 标志。
阈值 是两个术语正在考虑匹配的限制。它是 0 到 1 之间的小数,默认值为 0.5。prefixLength 是"fuzzyness"忽略的前缀长度。虽然默认值为 0,但建议对包含大量不同术语的索引使用非零值。
7.5.1.5. 通配符查询 复制链接链接已复制到粘贴板!
通配符查询在只知道部分词语的情况下很有用。? 表示单个字符,* 代表多个字符。请注意,出于性能考虑,建议查询不以?或 * 开头。
通配符查询不会对匹配的术语应用分析器。* 或 ? 被盗的风险太大。
7.5.1.6. 密码队列 复制链接链接已复制到粘贴板!
到目前为止,我们一直在寻找词语或词组,用户还可以搜索准确或大概的句子。使用 phrase()执行此操作。
Query luceneQuery = mythQB
.phrase()
.onField("history")
.sentence("Thou shalt not kill")
.createQuery();
Query luceneQuery = mythQB
.phrase()
.onField("history")
.sentence("Thou shalt not kill")
.createQuery();
可以通过添加滑动因子来搜索大致句子。滑动因子表示句子中允许的换句话数:这类似于操作符内或附近的运算符。
7.5.1.7. 范围查询 复制链接链接已复制到粘贴板!
范围查询搜索给定边界(包含与否)或给定边界以下或以上值之间的值。
7.5.1.8. 组合查询 复制链接链接已复制到粘贴板!
可以组合查询来创建更复杂的查询。可用的聚合操作器如下:
-
SHOULD:查询应包含该子队列的匹配元素。 -
MUST:查询必须包含子队列的匹配元素。 -
MUST:查询不得包含子队列的匹配元素。
子查询可以是任何 Lucene 查询,包括布尔值查询本身。
示例:SHOULD 查询
示例:MUST 查询
示例: MUST not Query
7.5.1.9. 查询选项 复制链接链接已复制到粘贴板!
Hibernate Search 查询 DSL 是一种易于使用的查询 API。在接受和生成 Lucene 查询时,您可以纳入 DSL 不支持的查询类型。
以下是查询类型和字段的查询选项概述:
- boostedTo (在查询类型和字段上)可将整个查询或特定字段扩展至给定因素。
- withConstantScore (在查询时)返回与查询匹配的所有结果,其恒定分数等于增长值。
- 使用 Filter 实例 过滤edBy(Filter )(查询时)过滤查询结果。
- 处理此字段时,ignoreAnalyzer (字段)会忽略分析器。
- 处理此字段时,忽略FieldBridge (on 字段)忽略字段桥接。
示例:Query 选项的组合
7.5.1.10. 构建 Hibernate Search Query 复制链接链接已复制到粘贴板!
7.5.1.10.1. 常规性 复制链接链接已复制到粘贴板!
构建 Lucene 查询后,将其包装到 Hibernate 查询中。查询会搜索所有索引化实体并返回所有索引化类类型,除非明确配置为不这样做。
示例:在 Hibernate 查询中抓取 Lucene 查询
FullTextSession fullTextSession = Search.getFullTextSession( session ); org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery( luceneQuery );
FullTextSession fullTextSession = Search.getFullTextSession( session );
org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery( luceneQuery );
要提高性能,请限制返回的类型,如下所示:
示例:按实体类型过滤搜索结果
第二个示例中的第一部分仅返回匹配的客户。同一示例的第二部分返回匹配的操作程序和项目。类型限制是多态限制。因此,如果基本类 Person 返回的两个子类 Salesman 和客户,请指定 Person.class 以根据结果类型进行过滤。
7.5.1.10.2. 分页 复制链接链接已复制到粘贴板!
为避免性能下降,建议限制每个查询返回的对象数量。用户从一个页面导航至另一页面是一个非常常见的用例。定义分页的方式类似于在普通 HQL 或标准查询中定义分页。
示例:定义搜索查询的粘贴
org.hibernate.Query fullTextQuery =
fullTextSession.createFullTextQuery( luceneQuery, Customer.class );
fullTextQuery.setFirstResult(15); //start from the 15th element
fullTextQuery.setMaxResults(10); //return 10 elements
org.hibernate.Query fullTextQuery =
fullTextSession.createFullTextQuery( luceneQuery, Customer.class );
fullTextQuery.setFirstResult(15); //start from the 15th element
fullTextQuery.setMaxResults(10); //return 10 elements
无论通过 fulltextQuery.getResultSize()分页,仍然可以获得匹配元素的总数。
7.5.1.10.3. 排序 复制链接链接已复制到粘贴板!
Apache Lucene 包含灵活而强大的结果排序机制。默认是相关的排序,适用于各种用例。排序机制可以更改为由其他属性排序,使用 Lucene sortrt 对象应用 Lucene 排序策略。
示例:指定Lucene排序
org.hibernate.search.FullTextQuery query = s.createFullTextQuery( query, Book.class );
org.apache.lucene.search.Sort sort = new Sort(
new SortField("title", SortField.STRING));
List results = query.list();
org.hibernate.search.FullTextQuery query = s.createFullTextQuery( query, Book.class );
org.apache.lucene.search.Sort sort = new Sort(
new SortField("title", SortField.STRING));
List results = query.list();
用于排序的字段不能被令牌化。有关令牌大小的更多信息,请参阅 @Field。
7.5.1.10.4. 获取策略 复制链接链接已复制到粘贴板!
Hibernate 搜索是否将返回类型限制为一个类,使用单个查询来加载对象。Hibernate Search 受到域模型中定义的静态获取策略的限制。可根据特定用例优化获取策略,如下所示:
示例:在查询中指定 FetchMode
Criteria criteria =
s.createCriteria( Book.class ).setFetchMode( "authors", FetchMode.JOIN );
s.createFullTextQuery( luceneQuery ).setCriteriaQuery( criteria );
Criteria criteria =
s.createCriteria( Book.class ).setFetchMode( "authors", FetchMode.JOIN );
s.createFullTextQuery( luceneQuery ).setCriteriaQuery( criteria );
在本例中,查询将返回与 LuceneQuery 匹配的所有 Book。使用 SQL 外部连接从同一查询加载作者集合。
在标准查询定义中,会根据提供的标准查询来猜测类型。因此,不需要限制返回实体类型。
fetch 模式是唯一可调整的属性。不要在标准查询上使用限制(一个位置的 子句),因为 getResultSize()如果与有限制的标准结合使用,则抛出 SearchException。
如果预期有多个实体,请不要使用 setCriteriaQuery。
7.5.1.10.5. 投射 复制链接链接已复制到粘贴板!
在某些情况下,只需要一小部分属性。使用 Hibernate Search 返回属性子集,如下所示:
Hibernate Search 从 Lucene 索引中提取属性,并将它们转换为其对象表示法并返回对象[] 列表。预测可防止数据库往返用时。但是,它们有以下限制:
-
生成的属性必须存储在索引(@
Field(store=Store.YES))中,这会增加索引大小。 生成的属性必须使用 field
Bridge实施 org.hibernate.search.bridge.TwoWayFieldBridge 或org.hibernate.search.bridge.TwoWayStringBridge,后者是简单的版本。注意所有 Hibernate 搜索内置类型都是双向的。
- 只有索引实体或其嵌入式关联的简单属性才能被投射。因此无法预测整个嵌入式实体。
- 投射不适用于通过 @IndexedEmbedded 索引的集合或映射。
Lucene 提供有关查询结果的元数据信息。使用投射常量来检索元数据。
示例:使用 Projection Retrieve Metadata
字段可以与以下投射常量混合:
- FullTextQuery.THIS: 返回初始化的和管理实体(作为非投射查询完成)。
- FullTextQuery.DOCUMENT: 返回与投射对象相关的 Lucene Document。
- FullTextQuery.OBJECT_CLASS: 返回索引实体的类。
- FullTextQuery.SCORE: 返回查询中的文档分数。分数可以轻松地将一个结果与给定查询的另一个结果进行比较,但在比较不同查询的结果时却毫无用处。
- FullTextQuery.ID: 投射对象的 ID 属性值。
- FullTextQuery.DOCUMENT_ID: Lucene 文档 ID。谨慎使用这个值作为 Lucene 文档 ID 可能会在两个不同的 IndexReader 打开之间随时间变化。
- FullTextQuery.EXPLANATION: 返回给定查询中匹配对象/文档的 Lucene Explanation 对象。这不适用于检索大量数据。运行解释通常与每个匹配元素运行整个 Lucene 查询的成本相同。因此,建议预测。
7.5.1.10.6. 自定义对象初始化策略 复制链接链接已复制到粘贴板!
默认情况下,Hibernate Search 使用最合适的策略来初始化与完整文本查询匹配的实体。它将执行一个或多个查询,以检索所需的实体。这种方法可最大程度减少数据库行程,其中仅有些检索到的实体存在于持久性上下文(会话)或第二级缓存中。
如果第二级缓存中存在实体,请强制 Hibernate Search 在检索数据库对象之前查看缓存。
示例:使用查询检查第二个缓存前的
FullTextQuery query = session.createFullTextQuery(luceneQuery, User.class);
query.initializeObjectWith(
ObjectLookupMethod.SECOND_LEVEL_CACHE,
DatabaseRetrievalMethod.QUERY
);
FullTextQuery query = session.createFullTextQuery(luceneQuery, User.class);
query.initializeObjectWith(
ObjectLookupMethod.SECOND_LEVEL_CACHE,
DatabaseRetrievalMethod.QUERY
);
ObjectLookupMethod 定义用于检查对象是否可轻松访问的策略(不从数据库获取)。其他选项有:
-
ObjectLookupMethod.PERSISTENCE_CONTEXT如果很多匹配实体已加载到持久上下文(加载在 Session 或 EntityManager 中),则会使用。 -
ObjectLookupMethod.SECOND_LEVEL_CACHE检查持久上下文,然后检查第二级缓存。
将以下内容设置为在二级缓存中搜索:
- 正确配置和激活第二级缓存。
- 为相关实体启用第二级缓存。这通过 @Cacheable 等注释来完成。
-
为 Session、实体管理器或查询启用二级缓存读取访问权限。在 Hibernate 原生 API 或 Jakarta Persistence 中的
CacheRetrieveMode.USE 中使用 CacheMode.NORMAL。
除非第二级缓存实施为 Infinispan,否则不使用 ObjectLookupMethod.SECOND_LEVEL_CACHE。其他第二级缓存提供商无法有效地实施此操作。
使用 DatabaseRetrievalMethod 自定义从数据库加载对象的方式,如下所示:
- QUERY (默认)使用一组查询来加载每个批处理中的多个对象。建议采用这种方法。
-
FIND_BY_ID 使用
Session.get或EntityManager.find语义一次加载一个对象。如果为实体设置了批处理大小,则建议这样做,这将允许 Hibernate Core 批量加载实体。
7.5.1.10.7. 限制查询的时间 复制链接链接已复制到粘贴板!
按照如下所述,限制 Hibernate 指南中查询花费的时间:
- 在达到极限时引发异常。
- 限制为在引发时间限制时检索的结果数。
7.5.1.10.8. 引发时间限制例外 复制链接链接已复制到粘贴板!
如果查询使用的时间超过定义的时间,则会引发 QueryTimeoutException(org.hibernate.QueryTimeoutException 或 javax.persistence.QueryTimeoutException,具体取决于编程 API)。
要使用原生 Hibernate API 时定义限制,请使用以下方法之一:
示例:在查询执行中定义超时
getResultSize()、heerate()和 scroll()遵循超时,直到方法调用的末尾。因此,Sterable 或 ScrollableResults 会忽略超时。另外,explain()也不满足这个超时期限。此方法用于调试,并检查查询性能较慢的原因。
以下是使用 Jakarta Persistence 限制执行时间的标准方法:
示例:在查询执行中定义超时
示例代码不能保证查询以指定的结果数量停止。
7.5.2. 检索结果 复制链接链接已复制到粘贴板!
构建 Hibernate 查询后,它将像 HQL 或标准查询一样执行。同样的范式和对象语义适用于 Lucene Query 查询,以及 list()、unique Result()、iterate () 和 scroll () 等常见操作。
7.5.2.1. 性能注意事项 复制链接链接已复制到粘贴板!
如果您期望合理数量的结果(例如使用分页)并且希望对所有结果进行处理,则建议使用 )。如果实体 list() 或唯一Result(批处理大小设置正确,则 效果最佳。请注意,在使用 list( )list()、 在分 唯一结果()和iterate()时,Hibernate Search 必须 处理所有 Lucene Hits 元素(页中 )。
如果要最大程度减少 Lucene 文档加载,滚动() 更为合适。完成后,不要忘记关闭 ScrollableResults 对象,因为它会保留 Lucene 资源。如果您计划使用滚动,但希望批量加载对象,您可以使用 query。setFetchSize( )。当对象被访问后,如果还没有加载,Hibernate Search 将通过一次加载下一个 fetchSize 对象。
分页优先于滚动。
7.5.2.2. 结果大小 复制链接链接已复制到粘贴板!
有时,了解匹配文档总数很有用:
- 提供由 Google 搜索提供的总搜索结果功能。例如,"在大约 888,000,000 结果中,1-10"
- 实施快速分页导航
- 实施多步骤搜索引擎,以便在受限查询返回零或没有足够的结果时添加约法
当然,检索所有匹配文件的成本过高。Hibernate Search 允许您检索匹配文档的总数,而不考虑分页参数。更有趣的是,您可以检索匹配元素的数量,而无需触发单个对象负载。
示例:确定查询的结果大小
与 Google 一样,如果索引未完全与数据库保持最新(例如,异步集群),则结果数量是近似的。
7.5.2.3. ResultTransformer 复制链接链接已复制到粘贴板!
投射结果返回为对象数组。如果对象使用的数据结构与应用的要求不匹配,请应用 ResultTransformer。ResultTransformer 在执行查询后构建必要的数据结构。
示例:在 Projections 中使用 ResultTransformer
ResultTransformer 实施示例可在 Hibernate 核心代码库中找到。
7.5.2.4. 了解结果 复制链接链接已复制到粘贴板!
如果查询的结果不是您预期的,luke 工具 对了解结果很有用。但是,Hibernate Search 还允许您访问给定结果(给定查询中)的 Lucene Explanation 对象。此类对于 Lucene 用户而言比较先进,但可以很好地理解对象的评分。您可以通过两种方式访问给定结果的说明对象:
-
使用
fullTextQuery.explain(int)方法 - 使用投射
第一种方法将文档 ID 用作参数并返回说明对象。文档 ID 可使用投影和 FullTextQuery.DOCUMENT_ID 常量来检索。
文档 ID 与实体 ID 无关。注意不要混淆这些概念。
在第二种方法中,您使用 FullTextQuery.EXPLANATION 常数来预测说明对象。
示例:使用 Projection 检索 Lucene 解释对象
只有在需要时才使用说明对象,因为它与再次运行 Lucene 查询的成本大致相同。
7.5.2.5. 过滤器 复制链接链接已复制到粘贴板!
Apache Lucene 具有强大的功能,允许您根据自定义过滤流程过滤查询结果。这是应用其他数据限制的一种非常强大的方式,特别是可以缓存和重复利用过滤器。使用案例包括:
- 安全
- 时序数据(例如,仅查看上个月的数据)
- 填充过滤器(例如,搜索仅限于给定类别)
Hibernate 搜索通过引入透明缓存的可参数名称过滤器的概念来进一步推动概念。对于熟悉 Hibernate 内核过滤器概念的用户,API 非常相似:
示例:为查询启用 Fulltext Filters
fullTextQuery = s.createFullTextQuery( query, Driver.class );
fullTextQuery.enableFullTextFilter("bestDriver");
fullTextQuery.enableFullTextFilter("security").setParameter( "login", "andre" );
fullTextQuery.list(); //returns only best drivers where andre has credentials
fullTextQuery = s.createFullTextQuery( query, Driver.class );
fullTextQuery.enableFullTextFilter("bestDriver");
fullTextQuery.enableFullTextFilter("security").setParameter( "login", "andre" );
fullTextQuery.list(); //returns only best drivers where andre has credentials
在本例中,我们在查询之上启用了两个过滤器:您可以根据需要启用或禁用任意数量的过滤器。
声明过滤器是通过 @FullTextFilterDef 注释来完成的。此注释可以位于任何 @Indexed 实体上,无论过滤器稍后要应用到哪一个查询。这意味着过滤定义是全局的,并且它们的名称必须唯一。如果定义了两个名称相同的注释,则会抛出 SearchException,以防有两个不同的 @FullTextFilterDef 注释。每个命名过滤器都必须指定其实际的过滤器实施。
示例:定义和实施过滤器
@FullTextFilterDefs( {
@FullTextFilterDef(name = "bestDriver", impl = BestDriversFilter.class),
@FullTextFilterDef(name = "security", impl = SecurityFilterFactory.class)
})
public class Driver { ... }
@FullTextFilterDefs( {
@FullTextFilterDef(name = "bestDriver", impl = BestDriversFilter.class),
@FullTextFilterDef(name = "security", impl = SecurityFilterFactory.class)
})
public class Driver { ... }
BestDriversFilter 是一个简单的 Lucene 过滤器的例子,它可将结果集减少到分数为 5 的驱动程序。在本例中,指定的过滤器直接实施 org.apache.lucene.search.Filter,并且包含 no-arg 构造器。
如果您的 Filter 创建需要额外的步骤,或者您想要使用的过滤器没有 no-arg 构造器,您可以使用工厂模式:
示例:使用 Factory Pattern 创建过滤器
Hibernate Search 将查找带注释的 @Factory 方法,并使用它来构建过滤器实例。工厂必须具有 no-arg 构造器。
Infinispan Query 使用 @Factory 注释的方法来构建过滤器实例。该工厂必须具有无参数构造器。
使用命名过滤器时,参数必须传递到过滤器。例如,安全过滤器可能想要了解您要应用哪个安全级别:
示例:将参数传递给定义的过滤器
fullTextQuery = s.createFullTextQuery( query, Driver.class );
fullTextQuery.enableFullTextFilter("security").setParameter( "level", 5 );
fullTextQuery = s.createFullTextQuery( query, Driver.class );
fullTextQuery.enableFullTextFilter("security").setParameter( "level", 5 );
每一参数名称应对过滤器或过滤目标命名过滤器定义的工厂关联集合器。
示例:在实际过滤器实施中使用参数
注意注释为 @Key 的方法返回 FilterKey 对象。返回的对象具有特殊合同:密钥对象必须实现等效的()/ hashCode(),这样,只有在给定 Filter 类型相同并且参数集相同时,两个键才相等。换句话说,两个过滤器键是相等的,只有在可以交换从中生成键的过滤器时才适用。密钥对象用作缓存机制中的密钥。
只有在以下情况下才需要 @key 方法:
- 过滤器缓存系统被启用(默认启用)
- 过滤器有参数
在大多数情况下,使用 StandardFilterKey 实现会足够好。它将等号()/ hashCode()实现委派到每个参数等于和 hashcode 方法。
如前文所述,定义的过滤器按默认设置进行缓存,缓存使用硬链接和软引用组合来允许在需要时处理内存。硬引用缓存跟踪最近使用的过滤器,并在需要时转换最不用于 SoftReferences 的过滤器。达到硬引用缓存的限制后,其他过滤器将缓存为 SoftReferences。要调整硬引用缓存的大小,请使用 hibernate.search.filter.cache_strategy.size (默认为 128)。如需高级使用过滤器缓存,请实现您自己的 FilterCachingStrategy。classname 由 hibernate.search.filter.cache_strategy 定义。
此过滤器缓存机制不应与缓存实际过滤器结果混淆。在 Lucene 中,通常的做法是使用 IndexReader 围绕 CachingWrapperFilter 包装过滤器。打包程序将缓存 getDocIdSet(IndexReader reader) 方法返回的 DocIdSet,以避免昂贵的重新输入。务必要提及,计算的 DocIdSet 仅适用于同一 IndexReader 实例,因为读取器在打开时有效地代表索引的状态。打开的 IndexReader 中无法更改文档列表。但是,不同的/新的 IndexReader 实例可能会在另一组文档中正常工作(可以是来自不同的索引,还是仅仅因为索引已更改),因此必须重新计算缓存的 DocIdSet。
Hibernate 搜索也有助于实现缓存的这一方面。默认情况下,@FullTextFilterDef 的 缓存 标志被设置为 FilterCacheModeType.INSTANCE_AND_DOCIDSETRESULTS,它将自动缓存过滤器实例,并将指定的过滤器围绕 CachingWrapperFilter 的 Hibernate 实施包装。与此类 SoftReferences 的 Lucene 版本和硬参考计数一起使用(请参阅关于过滤缓存)。可以使用 hibernate.search.filter.cache_docidresults.size (默认为 5)来调整硬引用计数。可以使用 @FullTextFilterDef.cache 参数控制嵌套行为。这个参数有三个不同的值:
| 值 | 定义 |
|---|---|
| FilterCacheModeType.NONE | 没有过滤实例,Hibernate Search 也不会缓存任何结果。对于每个过滤器调用,都会创建一个新的过滤器实例。此设置对于快速更改数据集或大量内存受限环境可能有用。 |
| FilterCacheModeType.INSTANCE_ONLY | 过滤器实例在并发 Filter.getDocIdSet()调用之间缓存和重复利用。DocIdSet 结果不会被缓存。当过滤器使用自己的特定缓存机制或过滤器结果因为应用程序特定的事件在这两种情况下都不需要造成 DocIdSet 缓存而动态更改时,此设置很有用。 |
| FilterCacheModeType.INSTANCE_AND_DOCIDSETRESULTS | 过滤器实例和 DocIdSet 结果都会被缓存。这是默认值。 |
在以下情况下,应缓存过滤器:
- 系统不会经常更新目标实体索引(换句话说,IndexReader 被大量重用)
- Filter 的 DocIdSet 计算成本高昂(与执行查询的时间相对相对)
7.5.2.6. 在共享环境中使用过滤器 复制链接链接已复制到粘贴板!
在分片环境中,可以对可用分片的子集执行查询。这可以通过两个步骤完成:
查询索引共享的子集
- 创建一个分片策略,它将根据过滤器配置选择 IndexManagers 的子集。
- 在查询时激活过滤器。
示例:查询索引共享的子集
在本例中,如果激活了 客户 过滤器,查询会针对特定的客户分片运行。
在本例中,如果存在名为 customer 的过滤器,则只会查询专用于此客户的分片,否则将返回所有分片。给定分片策略可以响应一个或多个过滤器,并依赖于其参数。
第二步是在查询时激活过滤器。尽管过滤器可以是常规过滤器(如 中定义的),它也在查询后过滤 Lucene 结果,但您可以使用仅传递给分片策略(否则将被忽略)的特殊过滤器。
要使用此功能,请在声明过滤器时指定 ShardSensitiveOnlyFilter 类。
请注意,通过使用 ShardSensitiveOnlyFilter,您不必实施任何 Lucene 过滤器。建议使用过滤器和分片策略来加快分片环境中的查询。
7.5.3. Faceting 复制链接链接已复制到粘贴板!
分面搜索是一种将查询结果划分为多个类别的技术。这种分类包括每个类别的点击数计算,以及根据这些方面进一步限制搜索结果的能力(说明)。以下示例显示了一个现实示例。搜索结果以 15 次命中显示在页面主部分中。但是,左侧的导航栏显示了 计算机和互联网 类别及其子类别 编程、计算机科学、数据库、软件、Web 开发、 网络 和家庭 计算。对于每个子类别,显示图书数量与主要搜索标准相符,并属于相应的子类别。这种 "计算机和互联网" 类别划分是一个具体的搜索面。另一个是平均客户评论。
分面搜索将查询的结果划分为不同的类别。分类包括每个类别的点击数计算,并根据这些方面(类别)进一步限制搜索结果。以下示例在主页面中以 15 个命中显示单面搜索结果。
左侧导航栏显示类别和子类别。对于每个子类别,图书的数量都与主要搜索标准匹配,并属于相应的子类别。这种"计算机和互联网"类别划分是一个具体的搜索面。另一个例子是平均客户评论。
示例:在 Amazon 上搜索 Hibernate Search
在 Hibernate Search 中,类 QueryBuilder 和 FullTextQuery 是面临 API 的入口点。前者会创建各种请求,后者将访问 FacetManager。FacetManager 对查询应用各种请求,并选择添加到现有查询中的侧面,以优化搜索结果。示例使用实体 Cd,如下例所示:
示例:实体 Cd
在 Hibernate Search 5.2 之前,无需显式使用 @Facet 注释。在 Hibernate Search 5.2 中,为了使用 Lucene 的原生面 API,有必要使用 Lucene。
7.5.3.1. 创建 Faceting 请求 复制链接链接已复制到粘贴板!
进行分面搜索的第一步是创建 FacetingRequest。目前支持两种类型的相互竞争请求。第一种类型称为 离散面, 第二个类型 范围则面临请求。如果是离散的面貌请求,您可以指定您要在哪个索引字段上查看(分类)以及要应用哪些选项。以下示例中显示了离散侧面请求的示例:
示例:创建离散 Faceting 请求
在执行此竞争请求时,将为索引化字段 标签 的每个离散值创建 Facet 实例。Facet 实例将记录实际字段值,包括此特定字段值在原始查询结果中发生的频率。orderBy、includeZeroCounts 和 maxFacetCount 是可选参数,可在任何面临请求时应用。OrderBy 允许指定返回所创建面的顺序。默认值为 FacetSortOrder.COUNT_DESC,但您也可以根据字段值或指定范围的顺序排序。includeZeroCount 决定结果中是否包括数为 0 的面数(默认为 ),而 maxFacetCount 允许限制返回的最大面数。
目前,索引化字段必须满足几个前提条件才能对其应用。索引化属性必须是 String、Date 或 Number 和 null 值的子类型。此外,该属性必须使用 Analyze .NO 进行索引,如果是数值属性 @NumericField,则需要指定。
范围侧面请求的创建非常相似,除非我们必须为所面对的字段值指定范围。下面列出了指定了三个不同价格范围的范围。以下 及以上 只能指定一次,但您可以根据需要 从 - 指定为 范围。对于每个范围边界,您还可以通过 excludeLimit 来指定它是否包含在范围中。
示例:创建一个范围 Faceting 请求
7.5.3.2. 应用查找请求 复制链接链接已复制到粘贴板!
侧面请求通过 FacetManager 类应用到查询,该类可通过 FullTextQuery 类检索。
您可以像您一样启用任意数量的请求,并在随后通过 getFacets()指定对象请求名称来检索它们。还有一个 disableFaceting()方法,允许您通过指定它的名称来禁用遇到请求。
可以利用 FacetManager 对查询应用竞争请求,该请求可通过 FullTextQuery 检索。
示例:应用查找请求
可以使用 getFacets() 并指定 faceting 请求名称来检索多个侧面请求。
disableFaceting() 方法通过指定名称来禁用争用请求。
7.5.3.3. 限制查询结果 复制链接链接已复制到粘贴板!
最后,您可以将任何返回的 Facets 应用为原始查询的额外标准,以实现"中断"功能。为此,可使用 FacetSelection。FacetSelections 可以通过 FacetManager 使用,并允许您将一个面t选为查询标准(selectFacets)、删除面限制(deselectFacets)、移除所有面限制(clearSelectedFacets),并检索所有当前选定的侧面(getSelectedFacets)。以下代码片段演示了一个示例:
7.5.4. 优化查询过程 复制链接链接已复制到粘贴板!
查询性能取决于几个条件:
- Lucene 查询。
- 加载的对象数量:使用分页(始终)或索引投射(如果需要)。
- Hibernate 搜索与 Lucene 读取器交互的方式:定义相应的读取器策略。
- 从索引中缓存经常提取的值。如需更多信息,请参阅 缓存索引值:fieldCache。
7.5.4.1. 缓存索引值:fieldCache 复制链接链接已复制到粘贴板!
Lucene 索引的主要功能是识别与您查询的匹配项。在执行查询后,必须分析结果,以提取有用的信息。Hibernate 搜索通常需要提取类类型和主密钥。
从索引中提取所需的值具有性能成本,在某些情况下这可能非常低且不容易,但在某些情况下,这可能是缓存的良好候选者。
要求取决于使用的 Projection s 的类型,因为在某些情况下不需要类类型,因为它可以从查询上下文或其他方法推断出来。
使用 @CacheFromIndex 注释,您可以试验 Hibernate Search 所需的各种主元数据字段:
可以使用此注解缓存类类型和 ID:
CLASS:Hibernate Search 将使用 Lucene FieldCache 来提高从索引中提取类类型的性能。此值默认为启用,如果您不指定 @CacheFromIndex 注释,则应用 Hibernate Search。
-
ID:提取主标识符将使用缓存。这有可能提供最佳性能查询,但会消耗更多的内存,进而可能会降低性能。
测量静止后性能和内存消耗的影响(执行某些查询)。通过启用字段缓存可以提高性能,但情况并非始终如此。
使用 FieldCache 有两个缺点需要考虑:
- 内存使用情况:这些缓存可能会相当耗尽内存。通常 CLASS 缓存的要求低于 ID 缓存。
- 索引温:使用字段缓存时,新索引或分段上的第一个查询将比未启用缓存时慢。
对于某些查询,根本不需要类类型,在这种情况下,即使您启用了 CLASS 字段缓存,也可能不会使用它;例如,如果您的目标为一个类,很明显,所有返回的值都将是该类型(每次查询执行中都会评估)。
若要使用 ID FieldCache,目标实体的 ID 必须使用 TwoWayFieldBridge(作为所有构建的网桥),并且特定查询中加载的所有类型都必须将 fieldname 用于 id,并且具有相同类型的 ID(每次执行查询时都会进行评估)。