ElasticSearch
整合SpringBoot
ES官方提供了各种不同语言的客户端。用来操作ES。这些客户端的本质就是组装DSL语句,通过HTTP请求发送给ES。
设计索引库
跟据数据库的表结构进行ES索引库的创建时。如果字段需要进行倒排索引的时候请为它指定分词器。如果该字段不是检索时所需要使用的字段时请将index设置为false。在同时检索多个字段的时候,往往会降低检索的效率,但是可以使用copy_to这个参数来指定相关字段将其放入到同一个字段里面,这样既保证了检索的效率问题又简化了多字段查询的语法,使语法更加简洁。我们称之为字段拷贝,但是在使用查询语句时我们是看不见这个给字段信息的,检索是可以进行使用的。
初始化工程
引入es的RestHighLevelClient依赖
<!-- 引入 RestHighLevelClient -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本
<!-- 覆盖 Spring Boot 默认的 ES 版本 -->
<properties>
<elasticsearch.version>7.17.3</elasticsearch.version> <!-- 改为你需要的版本 -->
</properties>
初始化RestHighLevelClient
@Configuration
public class ElasticsearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
// 配置 ES 地址(支持多个节点)
return new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http") // 修改为你的 ES 地址
// new HttpHost("another-host", 9200, "http") // 可添加多个节点
)
);
}
}
索引操纵
基本步骤:
初始化RestHighLevelClient
创建XxxIndexRequest。Xxx是CREATE、Get、Delete
准备DSL语句
发送请求。调用RestHighLevelClient.indices().xxx()方法。xxx是create、exist、delete
创建索引库
主要分成三个具体步骤
创建索引库的操作请求对象
给请求对象中放入指定的DSL执行语句
发起请求完成操作
代码示例
@Component
public class IndexCreator {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 创建索引库
* @param indexName 索引名称
* @return 是否创建成功
*/
public boolean createIndex(String indexName) throws IOException {
// 1. 创建索引请求
CreateIndexRequest request = new CreateIndexRequest(indexName);
// 2. 设置索引参数(可选)
request.settings(Settings.builder()
.put("index.number_of_shards", 3) // 分片数
.put("index.number_of_replicas", 1) // 副本数
);
// 3. 定义映射(Mapping,数据结构)
String mapping = """
{
"mappings": {
"properties": {
"id": { "type": "keyword" },
"title": { "type": "text", "analyzer": "ik_max_word" },
"price": { "type": "double" },
"createTime": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||epoch_millis" }
}
}
}
""";
request.mapping(mapping, XContentType.JSON);
// 4. 执行创建操作
CreateIndexResponse response = restHighLevelClient
.indices()//拿到所有的索引操作
.create(request, RequestOptions.DEFAULT);
return response.isAcknowledged();
}
}
注意:在示例代码里面我们使用的是request.mapping(...)
这个API,这是因为索引的setting参数已经在前面进行了设置。如果需要设置setting参数且之前没有设置的情况下,可以使用request.source(...)
这个API来进行声明。
方法 | 作用 | 参数格式要求 |
---|---|---|
request.mapping(...) |
仅设置索引的映射(Mapping) | 接收 纯 Mapping 的 JSON 片段(仅包含 "mappings": { ... } 部分) |
request.source(...) |
设置完整的索引定义(Settings + Mapping) | 接收 完整的索引 JSON 定义(需包含 "settings": { ... }, "mappings": { ... } ) |
删除索引库、判断索引库是否存在
public class IndexOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 判断索引是否存在
* @param indexName 索引名称
* @return 是否存在
*/
public boolean indexExists(String indexName) throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
return restHighLevelClient
.indices()
.delete(request, RequestOptions.DEFAULT);
}
}
public class IndexOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 判断索引是否存在
* @param indexName 索引名称
* @return 是否存在
*/
public boolean indexExists(String indexName) throws IOException {
GetIndexRequest request = new GetIndexRequest(indexName);
return restHighLevelClient
.indices()
.exists(request, RequestOptions.DEFAULT);
}
}
新增、查询、更新、删除文档
在必要的时候需要创建索引库对应的实体对象(一般来说索引库的字段类型结构要不同于数据库的字段类型结构)
基本步骤:
初始化RestHighLevelClient
创建XxxRequest请求。Xxx是Index、Get、Update、Delete
准备参数(Index和Update时需要)
发送请求。调用RestHighLevelClient.Xxx()方法;Xxx是index、get、update、delete
解析结果(Get时需要使用)
新增文档
public class DocumentOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
public void DocAddOperator(){
//1.创建Request对象
IndexRequest request = new IndexRequest("index_name");
//2.准备JSON数据(可以自己准备。或者从数据库里面查询再转换成JSON数据,切记要注意字段的数据类型变化)
String jsonString = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2025-05-19\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
//3.发送请求
restHighLevelClient.index(request,RequestOptions.DEFAULT);
}
}
查询文档
public class DocumentOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
public void DocGetOperator(){
//1.创建Request对象
GetRequest request = new GetRequest("index_name","1");
//2.发送请求
GetResponse response=restHighLevelClient.get(request, RequestOptions.DEFAULT);
//3.解析响应的结果
String json=response。getSourceAsString();
System.out.println(json);
}
}
修改文档
public class DocumentOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
public void DocUpdateOperator(){
//1.创建Request对象
UpdateRequest request = new UpdateRequest("index_name","1");
//2.准备请求参数
request.doc(
"price","952",
"starName","四钻"
);
//3.发送请求
restHighLevelClient.Update(request,RequestOptions.DEFAULT);
}
}
删除文档
public class DocumentOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
public void DocDeleteOperator(){
//1.创建Request对象
UpdateRequest request = new DeleteRequest("index_name","1");
//2.发送请求
restHighLevelClient.delete(request,RequestOptions.DEFAULT);
}
}
批量导入文档
public class DocumentOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
public void DocBulkOperator(){
//1.创建Request对象
UpdateRequest request = new BulkRequest("index_name","1");
//2.添加需要批量提交的请求
request.add(
new IndexRequest("hotel")
.id("101").source("json source",XContentType.JSON));
new IndexRequest("hotel")
.id("102").source("json source",XContentType.JSON));
//3.发送请求
restHighLevelClient.bulk(request,RequestOptions.DEFAULT);
}
}
DSL复杂查询
基本步骤:
创建SearchRequest对象
准备Request.source()、也就是DSL
QueryBuilders来构建查询条件
传入Request.source()的query()方法
发送请求,得到结果
解析结果(参考Json结果,从内到外,逐层进行解析)
快速入门
public class DocumentOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
void DSLOperator() throws IOException {
// 1. 准备Request对象(指定目标索引)
SearchRequest request = new SearchRequest("hotel"); // 指定查询的索引名为"hotel"
// 2. 组织DSL参数(构建查询条件)
request.source() // 获取SearchSourceBuilder
.query(QueryBuilders.matchAllQuery()); // 设置match_all查询(匹配所有文档)
// 3. 发送请求,获取响应结果
SearchResponse response = restHighLevelClient.search( // 通过Elasticsearch客户端发送请求
request,
RequestOptions.DEFAULT // 使用默认请求配置(此处可能存在拼写错误,正确应为DEFAULT)
);
// ...解析响应结果(参考前一个示例的解析逻辑)
// 4 解析查询结果
SearchHits searchHits = response.getHits();
// 4.1 获取总匹配文档数
long total = searchHits.getTotalHits().value; // 对应JSON中的 "total.value": 2
// 4.2 获取命中的文档数组
SearchHit[] hits = searchHits.getHits(); // 对应JSON中的 "hits"数组
// 遍历所有命中文档
for (SearchHit hit : hits) {
// 4.3 获取文档原始JSON数据(_source字段)
String json = hit.getSourceAsString(); // 对应JSON中的 "_source"对象
// 4.4 打印文档内容
System.out.println(json);
}
}
}
全文检索查询
全文检索的语法的结构与前面给的示例代码几乎一摸一样。只需要对其中的一些参数进行一些修改就可以进行使用,下面是代码与DSL语句的对比演示图。可以看到在使用Java代码操作ES时的细节,便于更好的理解代码逻辑,上手完成相关功能。
public class DocumentOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
void DSLOperator() throws IOException {
// 1. 准备Request对象(指定目标索引)
SearchRequest request = new SearchRequest("hotel"); // 指定查询的索引名为"hotel"
// 2. 组织DSL参数(构建查询条件)
request.source() // 获取SearchSourceBuilder
.query(QueryBuilders.matchAllQuery()); // 设置match_all查询(匹配所有文档)
/*
request.source().query(QueryBuilders.matchQuery("all","如家"));//全文检索match查询
request.source().query(QueryBuilders.multiMatchQuery("如家","name","brand"));//全文检索muili_match查询
request.source().query(QueryBuilders.termQuery("city","杭州"));//精确查询term查询
request.source().query(QueryBuilders.rangeQuery("price").gt(100).lt(150);//精确查询range查询
=========================================================================
bool查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//创建bool查询
boolQuery.must(QueryBuilders.termQuery("city","杭州"));//添加term查询
boolQuery.filter(QueryBuilders.rangeQuery("price").gt(100).lt(150));//添加range查询
*/
// 3. 发送请求,获取响应结果
SearchResponse response = restHighLevelClient.search( // 通过Elasticsearch客户端发送请求
request,
RequestOptions.DEFAULT // 使用默认请求配置(此处可能存在拼写错误,正确应为DEFAULT)
);
// ...解析响应结果(参考前一个示例的解析逻辑)
// 4 解析查询结果
SearchHits searchHits = response.getHits();
// 4.1 获取总匹配文档数
long total = searchHits.getTotalHits().value; // 对应JSON中的 "total.value": 2
// 4.2 获取命中的文档数组
SearchHit[] hits = searchHits.getHits(); // 对应JSON中的 "hits"数组
// 遍历所有命中文档
for (SearchHit hit : hits) {
// 4.3 获取文档原始JSON数据(_source字段)
String json = hit.getSourceAsString(); // 对应JSON中的 "_source"对象
// 4.4 打印文档内容
System.out.println(json);
}
}
}
分页和排序
public class AdviceOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
void PageAndOrderOperator() throws IOException {
// 1. 准备Request对象(指定目标索引)
SearchRequest request = new SearchRequest("hotel"); // 指定查询的索引名为"hotel"
// 2. 组织DSL参数(构建查询条件)
request.source() // 获取SearchSourceBuilder
.query(QueryBuilders.matchAllQuery()) // 设置match_all查询(匹配所有文档)
.sort("price",SortOrder.ASC)//设置排序的参数
.from(0).size(5);//设置分页的参数
//注意:可以使用链式编程的语法进行编写或者分开来对同一个请求进行参数设置。跟据个人喜好进行书写就可以。
// 3. 发送请求
restHighLevelClient.search( // 通过Elasticsearch客户端发送请求
request,
RequestOptions.DEFAULT // 使用默认请求配置(此处可能存在拼写错误,正确应为DEFAULT)
);
}
}
高亮展示
public class AdviceOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
void HighLighterOperator() throws IOException {
// 1. 准备Request对象(指定目标索引)
SearchRequest request = new SearchRequest("hotel"); // 指定查询的索引名为"hotel"
// 2. 组织DSL参数(构建查询条件)
request.source() // 获取SearchSourceBuilder
.query(QueryBuilders.matchQuery("all","如家")) // 设置match_all查询(匹配所有文档)
.highlighter(new HighlightBuilders().field("name").requireFieldMatch(false))
//注意:可以使用链式编程的语法进行编写或者分开来对同一个请求进行参数设置。跟据个人喜好进行书写就可以。还可以设置前缀标签与后置标签,增强其可扩展性,这里就直接使用默认的em标签就可以了。
// 3. 发送请求
restHighLevelClient.search( // 通过Elasticsearch客户端发送请求
request,
RequestOptions.DEFAULT // 使用默认请求配置(此处可能存在拼写错误,正确应为DEFAULT)
);
}
}
高亮的结果解析是比较复杂的,在这里你可以参考上述的图片中的代码进行理解,因为使用的比较少你可以自行参考,我就不再过多的赘述与编写了。你只需要记住在解析或者编写ES相关的查询语句时对照着原生的DSL语句进行一层一层的剖析,编写就行了。
聚合
public class AggregationsOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
void AggOperator() throws IOException {
// 1. 准备Request对象(指定目标索引)
SearchRequest request = new SearchRequest("hotel"); // 指定查询的索引名为"hotel"
// 2. 组织DSL参数(构建查询条件)
request.source() // 获取SearchSourceBuilder
.size(0) //设置分页
.aggregation(new AggregationBuilders().term("brandAgg").field("brand").size(10));
//注意:可以使用链式编程的语法进行编写或者分开来对同一个请求进行参数设置。跟据个人喜好进行书写就可以。
// 3. 发送请求,获取响应结果
SearchResponse response = restHighLevelClient.search( // 通过Elasticsearch客户端发送请求
request,
RequestOptions.DEFAULT // 使用默认请求配置(此处可能存在拼写错误,正确应为DEFAULT)
);
// 4. 解析聚合结果
Aggregations aggregations = response.getAggregations(); // 获取聚合结果根对象
// 4.1 根据聚合名称获取指定类型的聚合结果(假设为Terms聚合)
Terms brandTerms = aggregations.get("brandAgg");
// 4.2 获取所有分桶(Bucket)
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
// 4.3 遍历每个分桶
for (Terms.Bucket bucket : buckets) {
// 4.4 获取当前分桶的键(即聚合的字段值)
String key = bucket.getKeyAsString();
System.out.println("品牌名称: " + key);
}
}
}
自动补全
在我们使用一些购物网站时,通常搜索栏都有这样的功能,在你输入一些关键词时就会自动有商品信息自动补全出来供你选择。我们称之为自动补全功能。相关例子如下所示:
拼音分词器
拼音分词器(Pinyin Tokenizer)是一种用于处理中文文本的工具,主要功能是将中文汉字转换为对应的拼音(汉语拼音),并在转换过程中对拼音进行分词处理。它在搜索引擎、自然语言处理(NLP)、语音识别和输入法等场景中广泛应用,尤其在需要处理拼音输入或拼音与汉字混合输入时非常有用。
核心功能
汉字转拼音 将中文文本逐字或逐词转换为拼音形式。例如:
输入:
我爱北京
输出:
wo ai bei jing
(可配置是否带声调,如wǒ ài běi jīng
)。
拼音分词 将连续的拼音字符串按语义切分成有意义的词语。例如:
输入拼音:
xihuanbeijing
分词结果:
xi huan bei jing
(喜欢北京)或xi huan bei jing
(需结合上下文优化)。
多音字处理 根据上下文自动选择正确的拼音。例如:
输入:
重庆
可能输出:
chong qing
(默认)或zhong qing
(需根据语境判断)。
支持模糊匹配 允许拼音缩写或容错输入。例如:
输入:
zhrmbghg
解析为:
zhong hua ren min gong he guo
(中华人民共和国)。
由于原生的拼音分析器不能满足我们现有的业务要求,面临分词单一、结果弱的现状。我们需要配合现有的手段对分析进行一定的加强来满足符合业务的功能需求
自定义分词器可以在创建索引的时候进行定义与设置,相关参数说明在GitHub的插件下载中有详细的说明与解析。下面是一些常用的分词器设置参数,供参考。需要注意的是自定义的分词器仅仅只在当前索引库生效,其它库是无法使用自定义的分析器的。
在构建完自定义的拼音分词器之后,使用搜索功能时你会发现会有同义信息(拼音相同的信息)被检索出来,不符合业务的规范。为了解决这样的问题,我们需要对搜索使用的分词器进行指定,使其区别于倒排索引时使用的分词器。避免这样的业务问题出现
elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回,为了提高补全查询的效率,对于文档中字段的类型还有一定的约束。
查询语法如下:这一块只需要会对相关代码进行修改与再次使用即可,不用完全熟练掌握。
RestAPI实现
public class CompletionOperator {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
void ComOperator() throws IOException {
// 1.准备请求
SearchRequest request = new SearchRequest("hotel");
// 2.构建请求参数
request.source()
.suggest(new SuggestBuilder().addSuggestion(
"mySuggestion", // 建议名称
SuggestBuilders
.completionSuggestion("title") // 补全字段
.prefix("h") // 用户输入前缀
.skipDuplicates(true) // 去重
.size(10) // 返回数量
));
// 3.发送请求
restHighLevelClient.search(request,RequestOptions.DEFAULT);
// 4.处理结果
Suggest suggest = response.getSuggest();
// 4.1.根据名称获取补全结果(注意名称需与请求一致)
CompletionSuggestion suggestion = suggest.getSuggestion("hotelSuggestion"); // 名称可能与请求不一致
// 4.2.遍历选项并提取文本
for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
// 4.3.获取补全词条文本
String text = option.getText().string();
System.out.println(text);
}
}
}
数据同步
数据同步问题在许多地方都有体现不单单是在ES这里,还有像redis与MYSQL数据同步等。在实际的业务里面,我们往往是需要对数据库的数据进行相关操作的,那么基于数据库所使用的中间件就需要完成对数据库数据的实时对接与更新,以满足数据的一致性原则。这里给出三种数据同步的解决思路,当然在企业级别的开发中肯定是要使用代码耦合度低,效率高的方法。这里给出的相关策略可以作为大家的知识扩充。
方案一:同步调用
业务代码耦合度高,数据同步一致性强但反应时间长(主业务逻辑耗时长)效率低下。了解
方案二:异步调用
业务代码耦合度低,数据同步一致性好反应时间短(主业务逻辑数据返回耗时短)效率高。比较推荐
方案三:监听binlog
这种方式业务代码的耦合度最低。需要开启MYSQL的binlog功能,会增加数据库的性能开销加之新的中间件的引入,难度不言而喻。