SpringBoot整合ElasticSearch实现常用查询功能

SpringBoot整合ElasticSearch实现常用查询功能
彼岸的風ES简介
Elaticsearch,简称为ES,它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据。
全文搜索引擎
Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。
一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。
全文搜索引擎指的是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。
下载安装
首先需要检查SpringBoot对应的Elaticsearch版本,这里要以SpringBoot版本为主,安装对应的ES以及相关插件。
Spring Data Elasticsearch - 参考文档
下面需要下载ES和ik分词器,这里我的项目是SpringBoot:2.4.0版本,对应的ES版本为7.9.3Elasticsearch官网。
elasticsearch-7.9.3下载地址
elasticsearch-analysis-ik-7.9.3下载地址
注意:ik分词器需要解压到es的安装目录 /plugins/ik-7.9.3 内。
进入es安装目录 /bin 右键运行 elasticsearch.bat 文件启动 ES 服务。
打开浏览器,输入地址: http://localhost:9200 测试返回结果,返回结果如下:
1 | { |
整合SpringBoot和Elasticsearch
Spring Data Elasticsearch 介绍
Spring Data Elasticsearch基于Spring Data API简化 Elasticsearch 操作,将原始操作Elasticsearch 的客户端API进行封装。Spring Data为Elasticsearch 项目提供集成搜索引擎。Spring Data Elasticsearch POJO的关键功能区域为中心的模型与Elastichsearch交互文档和轻松地编写一个存储索引库数据访问层。
Spring Data Elasticsearch官网
- 在项目中添加es相关依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency> - 给application.yml添加es配置
1
2
3
4
5
6
7
8
9
10spring:
elasticsearch:
host: 127.0.0.1
port: 9200
logging:
level:
com:
es: debug - 数据实体类
Spring Data Elasticsearch常用注解
@Document() 指定实体类和索引对应关系
属性:
- indexName 索引名称
- shards 主分片数量。从ES 7开始默认1
- replicas 复制分片数量。从ES 7开始默认1
@Id ES主键
@Field() 指定普通属性
属性:
- name 字段类型。默认为当前属性名
- type 对应Elasticsearch中属性类型,默认是FieldType.Auto
- analyzer 分词器名称,必须type=Text才有效。默认为standard
- index 是否创建索引。默认true
1 | import com.baomidou.mybatisplus.annotation.IdType; |
- 配置类
- ElasticsearchRestTemplate是spring-data-elasticsearch项目中的一个类,和其他spring项目中的 template类似。
- 在新版的spring-data-elasticsearch 中,ElasticsearchRestTemplate 代替了原来的ElasticsearchTemplate。
- 原因是ElasticsearchTemplate基于TransportClient,TransportClient即将在8.x 以后的版本中移除。所以,我们推荐使用ElasticsearchRestTemplate。
- ElasticsearchRestTemplate基于RestHighLevelClient客户端的。需要自定义配置类,继承AbstractElasticsearchConfiguration,并实现elasticsearchClient()抽象方法,创建RestHighLevelClient对象。
- 创建AbstractElasticsearchConfiguration配置类
需要自定义配置类,继承AbstractElasticsearchConfiguration,并实现elasticsearchClient()抽象方法,创建RestHighLevelClient对象。1
2
3
4
5
6
7
8
9
10public abstract class AbstractElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
//需重写本方法
public abstract RestHighLevelClient elasticsearchClient();
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration{
private String host ;
private Integer port ;
//重写父类方法
public RestHighLevelClient elasticsearchClient() {
RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
RestHighLevelClient restHighLevelClient = new
RestHighLevelClient(builder);
return restHighLevelClient;
}
} - Mapper数据访问对象
1
2
3
public interface ProductMapper extends ElasticsearchRepository<ProductES, Long> {
}
Spring Data Elasticsearch集成测试
- 索引操作打开POSTMAN 检测索引是否创建和删除 GET请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SpringDataESIndexTest {
private ElasticsearchRestTemplate elasticsearchRestTemplate;
public void createIndex(){
//创建索引,系统初始化会自动创建索引
System.out.println("创建索引");
}
public void deleteIndex(){
//创建索引,系统初始化会自动创建索引
boolean flg = elasticsearchRestTemplate.deleteIndex(ProductES.class);
System.out.println("删除索引 = " + flg);
}
}1
http://localhost:9200/_cat/indices?v
- 文档操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class SpringDataESProductDaoTest {
private ProductMapper productMapper;
// 新增
public void save() {
ProductES product = new ProductES();
product.setId(2L);
product.setName("苹果手机");
product.setCategory("手机");
product.setPrice(7999.0);
product.setImages("https://wind-of-grace.gitee.io/");
productMapper.save(product);
}
// POSTMAN, GET http://localhost:9200/product/_doc/2
// 修改
public void update() {
ProductES product = new ProductES();
product.setId(2L);
product.setName("小米 2 手机");
product.setCategory("手机");
product.setPrice(9999.0);
product.setImages("https://wind-of-grace.gitee.io/");
productMapper.save(product);
}
// POSTMAN, GET http://localhost:9200/product/_doc/2
// 根据 id 查询
public void findById() {
ProductES product = productMapper.findById(2L).get();
System.out.println(product);
}
// 查询所有
public void findAll() {
Iterable<ProductES> products = productMapper.findAll();
for (ProductES product : products) {
System.out.println(product);
}
}
// 删除
public void delete() {
ProductES product = new ProductES();
product.setId(2L);
productMapper.delete(product);
}
// 批量新增
// POSTMAN, GET http://localhost:9200/product/_search
public void saveAll() {
List<ProductES> productList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ProductES product = new ProductES();
product.setId(Long.valueOf(i));
product.setName("[" + i + "]小米手机");
product.setCategory("手机");
product.setPrice(1999.0 + i);
product.setImages("https://wind-of-grace.gitee.io/");
productList.add(product);
}
productMapper.saveAll(productList);
}
// 分页查询
public void findByPageable() {
//设置排序(排序方式,正序还是倒序,排序的 id)
Sort sort = Sort.by(Sort.Direction.DESC, "id");
int pageNo = 0;//当前页,第一页从 0 开始, 1 表示第二页
int pageSize = 5;//每页显示多少条
//设置查询分页
PageRequest pageRequest = PageRequest.of(pageNo, pageSize, sort);
//分页查询
Page<ProductES> productPage = productMapper.findAll(pageRequest);
for (ProductES Product : productPage.getContent()) {
System.out.println(Product);
}
}
} - 文档搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
public class SpringDataESSearchTest {
private ProductMapper productMapper;
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* term 查询
* search(termQueryBuilder) 调用搜索方法,参数查询构建器对象
*/
public void termQuery() {
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "小米");
Iterable<ProductES> products = productMapper.search(termQueryBuilder);
for (ProductES product : products) {
System.out.println(product);
}
}
/**
* term 查询加分页
*/
public void termQueryByPage() {
int pageNo = 0;
int pageSize = 5;
//设置查询分页
PageRequest pageRequest = PageRequest.of(pageNo, pageSize);
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "小米");
Iterable<ProductES> products =
productMapper.search(termQueryBuilder, pageRequest);
for (ProductES product : products) {
System.out.println(product);
}
}
/**
* 高亮查询
*/
public void testHighlightQuery() {
//构建查询条件
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.should(QueryBuilders.matchQuery("name", "华为手机"));
//设置高亮字段
HighlightBuilder highlightBuilder = new HighlightBuilder();
//设置标签前缀
highlightBuilder.preTags("<font color='red'>");
//设置标签后缀
highlightBuilder.postTags("</font>");
//设置高亮字段
highlightBuilder.field("name");
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withHighlightBuilder(highlightBuilder)
.build();
//执行查询
SearchHits<ProductES> searchHits = elasticsearchRestTemplate.search(searchQuery, ProductES.class);
//获取查询结果
searchHits.forEach(searchHit -> {
System.out.println(searchHit.toString());
});
}
/**
* 高亮查询加分页
*/
public void testHighlightByPage() {
//当前页
int pageNo = 0;
//每页显示条数
int pageSize = 5;
//设置查询分页
PageRequest pageRequest = PageRequest.of(pageNo, pageSize);
//构建查询条件
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.should(QueryBuilders.matchQuery("name", "华为手机"));
//设置高亮字段
HighlightBuilder highlightBuilder = new HighlightBuilder();
//设置标签前缀
highlightBuilder.preTags("<font color='red'>");
//设置标签后缀
highlightBuilder.postTags("</font>");
//设置高亮字段
highlightBuilder.field("name");
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withHighlightBuilder(highlightBuilder)
.withPageable(pageRequest)
.build();
//执行查询
SearchHits<ProductES> searchHits = elasticsearchRestTemplate.search(searchQuery, ProductES.class);
//获取查询结果
searchHits.forEach(searchHit -> {
System.out.println(searchHit.toString());
});
}
/**
* 模糊查询
*/
public void testFuzzyQuery() {
//构建查询条件
FuzzyQueryBuilder fuzzyQuery = QueryBuilders.fuzzyQuery("name", "苹果").fuzziness(Fuzziness.ONE);
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(fuzzyQuery)
.build();
//执行查询
SearchHits<ProductES> searchHits = elasticsearchRestTemplate.search(searchQuery, ProductES.class);
//获取查询结果
searchHits.forEach(searchHit -> {
System.out.println(searchHit.toString());
});
}
/**
* 最大查询
*/
public void testMaxQuery() {
FuzzyQueryBuilder fuzzyQuery = QueryBuilders.fuzzyQuery("name", "华为").fuzziness(Fuzziness.ONE);
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(fuzzyQuery)
.addAggregation(AggregationBuilders.max("maxAge").field("age"))
.build();
//执行查询
SearchHits<ProductES> searchHits = elasticsearchRestTemplate.search(searchQuery, ProductES.class);
//获取查询结果
searchHits.forEach(searchHit -> {
System.out.println(searchHit.toString());
});
}
/**
* 分组查询
*/
public void testGroupQuery() {
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.addAggregation(AggregationBuilders.terms("name").field("price"))
.build();
//执行查询
SearchHits<ProductES> searchHits = elasticsearchRestTemplate.search(searchQuery, ProductES.class);
//获取查询结果
searchHits.forEach(searchHit -> {
System.out.println(searchHit.toString());
});
}
/**
* 范围查询
*/
public void testRangeQuery() {
//构建条件查询:id为30-50,按价格分组
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.rangeQuery("id").from(30).to(50));
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.addAggregation(AggregationBuilders.terms("name").field("price"))
.build();
//执行查询
SearchHits<ProductES> searchHits = elasticsearchRestTemplate.search(searchQuery, ProductES.class);
//获取查询结果
searchHits.forEach(searchHit -> {
System.out.println(searchHit.toString());
});
}
/**
* 组合查询
*/
public void testConditionQuery() {
//构建条件查询:id为30-50,按价格分组
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.rangeQuery("id").from(30).to(50));
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.addAggregation(AggregationBuilders.terms("price").field("price"))
.build();
//执行查询
SearchHits<ProductES> searchHits = elasticsearchRestTemplate.search(searchQuery, ProductES.class);
//获取查询结果
searchHits.forEach(searchHit -> {
System.out.println(searchHit.toString());
});
}
}
总结
为什么要使用Elasticsearch
系统中的数据, 随着业务的发展,时间的推移, 将会非常多, 而业务中往往采用模糊查询进行数据的搜索, 而模糊查询会导致查询引擎放弃索引,导致系统查询数据时都是全表扫描,在百万级别的数据库中,查询效率是非常低下的,而我们使用 ES 做一个全文索引,将经常查询的系统功能的某些字段,比如说电商系统的商品表中商品名,描述、价格还有 id 这些字段我们放入 ES 索引库里,可以提高查询速度。