我知道实际的例子总是比理论更好,但是,我还是想先讲一点理论。 Elasticsearch 的核心是 Lucene。因此,在将文档写入 Lucene 索引之前,他要经过分析阶段。分析阶段可分为3个部分:
-
字符过滤;
-
标记化;
- 令牌过滤
在第一阶段,我们可以丢弃不需要的字符,例如 HTML 标签。有关字符过滤器的更多信息,您可以在official site 上找到。
下一阶段要有趣得多。这里我们将输入文本拆分为标记,稍后将用于搜索。几个很有用的tokenizers:
-
标准分词器。默认情况下使用它。标记器实现了 Unicode 文本分割算法。在实践中,您可以使用它将文本拆分为单词并将这些单词用作标记。
-
n-gram 分词器。如果您想按单词的一部分进行搜索,这就是您所需要的。此分词器将文本拆分为 n 项的连续序列。例如,文本“例如”将被拆分为这个标记序列
"fo", "or", "r ", " e", "ex", "for", "or ex" 等。n-gram 的长度是可变的,可以通过 min_gram 和 max_gram 参数进行配置。
-
边缘 n-gram 分词器。除了一件事之外,它与 n-gram 分词器的工作方式相同 - 此分词器不会增加偏移量。例如,文本“例如”将被拆分为这个标记序列
"fo", "for", "for ", "for e", "for ex", "for exa" 等。
您可以在官方网站上找到有关标记器的更多信息。很遗憾,由于声誉低,我无法发布更多链接。
下一阶段也非常有趣。在我们将文本拆分为标记之后,我们可以用它做很多有趣的事情。我再次给出一些非常有用的令牌过滤器示例:
-
小写过滤器。在大多数情况下,我们希望进行不区分大小写的搜索,因此最好将标记转为小写。
-
词干过滤器。当我们与自然语言打交道时,我们会遇到很多问题。问题之一是一个词可以有多种形式。词干过滤器帮助我们获得词的词根形式。
-
模糊过滤器。另一个问题是用户经常打错字。此过滤器添加包含可能拼写错误的标记。
如果你有兴趣查看分析器的结果,可以使用这个 _termvectors 端点
curl [ELASTIC_URL]:9200/[INDEX_NAME]/[TYPE_NAME]/[DOCUMENT_ID]/_termvectors?pretty
现在谈谈查询。查询分为 2 个大组。这些组有 2 个显着差异:
- 请求是否经过分析阶段;
- 请求是否需要准确的答案(是或否)
示例是匹配查询和术语查询。第一个将通过分析阶段,第二个不会。第一个不会给我们一个具体的答案(但给我们一个分数),第二个会。在为文档创建映射时,我们可以为每个字段分别指定分析器的索引和搜索分析器。
现在有关弹簧数据弹性搜索的信息。在这里谈论具体的例子是有意义的。假设我们有一个带有标题字段的文档,并且我们想要搜索该字段的信息。首先,创建一个包含 elasticsearch 设置的文件。
{
"analysis": {
"analyzer": {
"ngram_analyzer": {
"tokenizer": "ngram_tokenizer",
"filter": [
"lowercase"
]
},
"edge_ngram_analyzer": {
"tokenizer": "edge_ngram_tokenizer",
"filter": [
"lowercase"
]
},
"english_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"english_stop",
"unique",
"english_possessive_stemmer",
"english_stemmer"
]
"keyword_analyzer": {
"tokenizer": "keyword",
"filter": ["lowercase"]
}
},
"tokenizer": {
"ngram_tokenizer": {
"type": "ngram",
"min_gram": 2,
"max_gram": 20
},
"edge_ngram_tokenizer": {
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 20
}
},
"filter": {
"english_stop": {
"type": "stop",
"stopwords": "_english_"
},
"english_stemmer": {
"type": "stemmer",
"language": "english"
},
"english_possessive_stemmer": {
"type": "stemmer",
"language": "possessive_english"
}
}
}
您可以将此设置保存到您的资源文件夹。现在让我们看看我们的文档类
@Document(indexName = "document", type = "document")
@Setting(settingPath = "document_index_setting.json")
public class Document {
@Id
private String id;
@MultiField(
mainField = @Field(type = FieldType.String,
index = not_analyzed),
otherFields = {
@InnerField(suffix = "edge_ngram",
type = FieldType.String,
indexAnalyzer = "edge_ngram_analyzer",
searchAnalyzer = "keyword_analyzer"),
@InnerField(suffix = "ngram",
type = FieldType.String,
indexAnalyzer = "ngram_analyzer"),
searchAnalyzer = "keyword_analyzer"),
@InnerField(suffix = "english",
type = FieldType.String,
indexAnalyzer = "english_analyzer")
}
)
private String title;
// getters and setters omitted
}
所以这里的字段标题带有三个内部字段:
-
title.edge_ngram 用于使用关键字搜索分析器按边 n-gram 进行搜索。我们需要这个,因为我们不需要将查询拆分为边 n-gram;
-
title.ngram 用于 n-gram 搜索;
-
title.english 用于搜索自然语言的细微差别
和主要字段标题。我们不对此进行分析,因为有时我们想按此字段进行排序。
让我们使用简单的多重匹配查询来搜索所有这些字段:
String searchQuery = "blablabla";
MultiMatchQueryBuilder queryBuilder = multiMatchQuery(searchQuery)
.field("title.edge_ngram", 2)
.field("title.ngram")
.field("title.english");
NativeSearchQueryBuilder searchBuilder = new NativeSearchQueryBuilder()
.withIndices("document")
.withTypes("document")
.withQuery(queryBuilder)
.withPageable(new PageRequest(page, pageSize));
elasticsearchTemplate.queryForPage(searchBuilder.build,
Document.class,
new SearchResultMapper() {
//realisation omitted });
搜索是一个非常有趣且内容丰富的话题。我试图尽可能简短地回答,因此可能会出现令人困惑的时刻 - 请不要犹豫。