我知道工作示例总是比理论更好,但我还是想先讲一点理论。 Elasticsearch 的核心是 Lucene。因此,在将文档写入 Lucene 索引之前,他会经历分析阶段。分析阶段可分为3个部分:
-
字符过滤;
-
标记化;
- 令牌过滤
在第一阶段,我们可以丢弃不需要的字符,例如 HTML 标签。有关字符过滤器的更多信息,您可以找到官方网站 https://www.elastic.co/guide/en/elasticsearch/reference/2.4/analysis-charfilters.html。
下一阶段要有趣得多。在这里,我们将输入文本拆分为标记,稍后将用于搜索。几个非常有用的分词器 https://www.elastic.co/guide/en/elasticsearch/reference/2.4/analysis-tokenizers.html:
-
标准分词器。默认情况下使用它。分词器实现 Unicode 文本分段算法。在实践中,您可以使用它将文本拆分为单词并将这些单词用作标记。
-
n-gram 分词器。如果您想按单词的一部分进行搜索,这就是您所需要的。此分词器将文本拆分为 n 个项目的连续序列。例如,文本“for example”将被分割成这个标记序列
"fo", "or", "r ", " e", "ex", "for", "or ex"
n-gram 的长度是可变的,可以通过 min_gram 和 max_gram 参数进行配置。
-
边缘 n-gram 分词器。与 n-gram 分词器的工作方式相同,除了一件事 - 此分词器不会增加偏移量。例如,文本“for example”将被分割成这个标记序列
"fo", "for", "for ", "for e", "for ex", "for exa"
ETC。
您可以在官方网站上找到有关分词器的更多信息。不幸的是,由于声誉较低,我无法发布更多链接。
下一阶段也非常有趣。将文本拆分为标记后,我们可以用它做很多有趣的事情。我再次给出一些非常有用的令牌过滤器示例:
-
小写过滤器。在大多数情况下,我们希望获得不区分大小写的搜索,因此最好将标记变为小写。
-
词干过滤器。当我们处理自然语言时,我们会遇到很多问题。问题之一是一个词可以有多种形式。词干过滤器帮助我们获取单词的词根形式。
-
模糊过滤器。另一个问题是用户经常犯错。此过滤器添加包含可能的拼写错误的标记。
如果您有兴趣查看分析结果,可以使用这个术语“向量端点”
curl [ELASTIC_URL]:9200/[INDEX_NAME]/[TYPE_NAME]/[DOCUMENT_ID]/_termvectors?pretty
现在谈谈查询。查询分为 2 大组。这些组有 2 个显着差异:
- 请求是否会经过分析阶段;
- 该请求是否需要确切的答案(是或否)
示例包括匹配查询和术语查询。第一个将通过分析阶段,第二个则不会。第一个不会给我们具体的答案(但会给我们一个分数),第二个会给出。为文档创建映射时,我们可以为每个字段分别指定分析器和搜索分析器的索引。
现在关于 spring data elasticsearch 的信息。这里讲一下具体的例子是有道理的。假设我们有一个带有标题字段的文档,并且我们想要搜索有关该字段的信息。首先,创建一个包含 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 元语法进行搜索。我们需要这个,因为我们不需要将查询拆分为边缘 n 元;
-
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 });
搜索是一个非常有趣且内容丰富的话题。我试图尽可能简短地回答,因此可能会出现令人困惑的时刻 - 请毫不犹豫地询问。