如何同时支持标记化和非标记化搜索
How to support tokenized and untokenized search at the same time
我尝试使休眠搜索同时支持标记化和未标记化搜索(如果我在这里使用了错误的术语,请原谅我)。举例如下。
我有以下类型的实体列表。
@Entity
@Indexed
@NormalizerDef(name = "lowercase",
filters = {
@TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
@TokenFilterDef(factory = LowerCaseFilterFactory.class)
}
)
public class Deal {
//other fields omitted for brevity purposes
@Field(store = Store.YES)
@Field(name = "name_Sort", store = Store.YES, normalizer= @Normalizer(definition="lowercase"))
@SortableField(forField = "name_Sort")
@Column(name = "NAME")
private String name = "New Deal";
//Getters/Setters omitted here
}
我还使用了关键字方法来构建查询生成器,如下所示。 getSearchableFields 方法 returns 可搜索字段的列表。在此示例中,"name" 将在此返回的列表中,因为 Deal 中的字段名称是可搜索的。
protected Query inputFilterBuilder() {
return queryBuilder.keyword()
.wildcard().onFields(getSearchableFields())
.matching("*" + searchRequest.getQuery().toLowerCase() + "*").createQuery();
}
当我只使用整个单词进行搜索时,此设置工作正常。例如,如果我有两个 Deal 实体,一个的名称是 "Practical Concrete Hat",另一个的名称是 "Practical Cotton Cheese"。当按 "Practical" 搜索时,我得到了这两个实体。但是当按 "Practical Co" 搜索时,我得到 0 个实体。原因是因为字段名称被标记化并且 "Practical Co" 不是关键字。
我的问题是如何同时支持这两种搜索,以便在按 "Practical" 或 "Practical Co" 搜索时返回这两个实体。
我通读了官方的休眠搜索文档,我的直觉是我应该再添加一个用于未标记化搜索的字段。也许我构建查询生成器的方式也需要更新?
更新
使用 SimpleQueryString 的解决方案无效。
根据提供的答案,我编写了以下查询构建器逻辑。但是,它不起作用。
protected Query inputFilterBuilder() {
String[] searchableFields = getSearchableFields();
if(searchableFields.length == 0) {
return queryBuilder.simpleQueryString().onField("").matching("").createQuery();
}
SimpleQueryStringMatchingContext simpleQueryStringMatchingContext = queryBuilder.simpleQueryString().onField(searchableFields[0]);
for(int i = 1; i < searchableFields.length; i++) {
simpleQueryStringMatchingContext = simpleQueryStringMatchingContext.andField(searchableFields[i]);
}
return simpleQueryStringMatchingContext
.matching("\"" + searchRequest.getQuery() + "\"").createQuery();
}
使用单独的分析器进行查询和短语查询的工作解决方案。
我从官方文档中发现,我们可以使用词组查询来搜索多个词。所以我写了下面的查询生成器方法。
protected Query inputFilterBuilder() {
String[] searchableFields = getSearchableFields();
if(searchableFields.length == 0) {
return queryBuilder.phrase().onField("").sentence("").createQuery();
}
PhraseMatchingContext phraseMatchingContext = queryBuilder.phrase().onField(searchableFields[0]);
for(int i = 1; i < searchableFields.length; i++) {
phraseMatchingContext = phraseMatchingContext.andField(searchableFields[i]);
}
return phraseMatchingContext.sentence(searchRequest.getQuery()).createQuery();
}
这不适用于使用多个单词且中间带有 space 的搜索。然后我按照建议添加了单独的分析器用于索引和查询,突然之间,它起作用了。
分析器定义:
@AnalyzerDef(name = "edgeNgram", tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = EdgeNGramFilterFactory.class,
params = {
@Parameter(name = "minGramSize", value = "1"),
@Parameter(name = "maxGramSize", value = "10")
})
})
@AnalyzerDef(name = "edgeNGram_query", tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
@TokenFilterDef(factory = LowerCaseFilterFactory.class)
})
交易名称字段注释:
@Field(store = Store.YES, analyzer = @Analyzer(definition = "edgeNgram"))
@Field(name = "edgeNGram_query", store = Store.YES, analyzer = @Analyzer(definition = "edgeNGram_query"))
@Field(name = "name_Sort", store = Store.YES, normalizer= @Normalizer(definition="lowercase"))
@SortableField(forField = "name_Sort")
@Column(name = "NAME")
private String name = "New Deal";
覆盖名称字段分析器以使用查询分析器的代码
String[] searchableFields = getSearchableFields();
if(searchableFields.length > 0) {
EntityContext entityContext = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder().forEntity(this.getClass().getAnnotation(SearchType.class).clazz()).overridesForField(searchableFields[0], "edgeNGram_query");
for(int i = 1; i < searchableFields.length; i++) {
entityContext.overridesForField(searchableFields[i], "edgeNGram_query");
}
queryBuilder = entityContext.get();
}
跟进问题
为什么上述调整实际上有效?
您的问题是通配符查询。通配符查询不支持标记化:它们仅适用于单个标记。事实上,它们甚至不支持规范化,这就是为什么你必须自己将用户输入小写...
解决方案不是混合标记化和非标记化搜索(这是可能的,但不会真正解决您的问题)。解决方案是完全忘记通配符查询并在分析器中使用 edgengram 过滤器。
有关详细说明,请参阅 。
如果您使用 ELasticsearch 集成,则必须依靠 hack 才能使 "query-only" 分析器正常工作。参见 here。
我尝试使休眠搜索同时支持标记化和未标记化搜索(如果我在这里使用了错误的术语,请原谅我)。举例如下。
我有以下类型的实体列表。
@Entity
@Indexed
@NormalizerDef(name = "lowercase",
filters = {
@TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
@TokenFilterDef(factory = LowerCaseFilterFactory.class)
}
)
public class Deal {
//other fields omitted for brevity purposes
@Field(store = Store.YES)
@Field(name = "name_Sort", store = Store.YES, normalizer= @Normalizer(definition="lowercase"))
@SortableField(forField = "name_Sort")
@Column(name = "NAME")
private String name = "New Deal";
//Getters/Setters omitted here
}
我还使用了关键字方法来构建查询生成器,如下所示。 getSearchableFields 方法 returns 可搜索字段的列表。在此示例中,"name" 将在此返回的列表中,因为 Deal 中的字段名称是可搜索的。
protected Query inputFilterBuilder() {
return queryBuilder.keyword()
.wildcard().onFields(getSearchableFields())
.matching("*" + searchRequest.getQuery().toLowerCase() + "*").createQuery();
}
当我只使用整个单词进行搜索时,此设置工作正常。例如,如果我有两个 Deal 实体,一个的名称是 "Practical Concrete Hat",另一个的名称是 "Practical Cotton Cheese"。当按 "Practical" 搜索时,我得到了这两个实体。但是当按 "Practical Co" 搜索时,我得到 0 个实体。原因是因为字段名称被标记化并且 "Practical Co" 不是关键字。
我的问题是如何同时支持这两种搜索,以便在按 "Practical" 或 "Practical Co" 搜索时返回这两个实体。
我通读了官方的休眠搜索文档,我的直觉是我应该再添加一个用于未标记化搜索的字段。也许我构建查询生成器的方式也需要更新?
更新
使用 SimpleQueryString 的解决方案无效。
根据提供的答案,我编写了以下查询构建器逻辑。但是,它不起作用。
protected Query inputFilterBuilder() {
String[] searchableFields = getSearchableFields();
if(searchableFields.length == 0) {
return queryBuilder.simpleQueryString().onField("").matching("").createQuery();
}
SimpleQueryStringMatchingContext simpleQueryStringMatchingContext = queryBuilder.simpleQueryString().onField(searchableFields[0]);
for(int i = 1; i < searchableFields.length; i++) {
simpleQueryStringMatchingContext = simpleQueryStringMatchingContext.andField(searchableFields[i]);
}
return simpleQueryStringMatchingContext
.matching("\"" + searchRequest.getQuery() + "\"").createQuery();
}
使用单独的分析器进行查询和短语查询的工作解决方案。
我从官方文档中发现,我们可以使用词组查询来搜索多个词。所以我写了下面的查询生成器方法。
protected Query inputFilterBuilder() {
String[] searchableFields = getSearchableFields();
if(searchableFields.length == 0) {
return queryBuilder.phrase().onField("").sentence("").createQuery();
}
PhraseMatchingContext phraseMatchingContext = queryBuilder.phrase().onField(searchableFields[0]);
for(int i = 1; i < searchableFields.length; i++) {
phraseMatchingContext = phraseMatchingContext.andField(searchableFields[i]);
}
return phraseMatchingContext.sentence(searchRequest.getQuery()).createQuery();
}
这不适用于使用多个单词且中间带有 space 的搜索。然后我按照建议添加了单独的分析器用于索引和查询,突然之间,它起作用了。
分析器定义:
@AnalyzerDef(name = "edgeNgram", tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
@TokenFilterDef(factory = EdgeNGramFilterFactory.class,
params = {
@Parameter(name = "minGramSize", value = "1"),
@Parameter(name = "maxGramSize", value = "10")
})
})
@AnalyzerDef(name = "edgeNGram_query", tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
@TokenFilterDef(factory = LowerCaseFilterFactory.class)
})
交易名称字段注释:
@Field(store = Store.YES, analyzer = @Analyzer(definition = "edgeNgram"))
@Field(name = "edgeNGram_query", store = Store.YES, analyzer = @Analyzer(definition = "edgeNGram_query"))
@Field(name = "name_Sort", store = Store.YES, normalizer= @Normalizer(definition="lowercase"))
@SortableField(forField = "name_Sort")
@Column(name = "NAME")
private String name = "New Deal";
覆盖名称字段分析器以使用查询分析器的代码
String[] searchableFields = getSearchableFields();
if(searchableFields.length > 0) {
EntityContext entityContext = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder().forEntity(this.getClass().getAnnotation(SearchType.class).clazz()).overridesForField(searchableFields[0], "edgeNGram_query");
for(int i = 1; i < searchableFields.length; i++) {
entityContext.overridesForField(searchableFields[i], "edgeNGram_query");
}
queryBuilder = entityContext.get();
}
跟进问题 为什么上述调整实际上有效?
您的问题是通配符查询。通配符查询不支持标记化:它们仅适用于单个标记。事实上,它们甚至不支持规范化,这就是为什么你必须自己将用户输入小写...
解决方案不是混合标记化和非标记化搜索(这是可能的,但不会真正解决您的问题)。解决方案是完全忘记通配符查询并在分析器中使用 edgengram 过滤器。
有关详细说明,请参阅
如果您使用 ELasticsearch 集成,则必须依靠 hack 才能使 "query-only" 分析器正常工作。参见 here。