本文主要讲述了 Spring Boot 如何集成 ElasticSearch 搜索引擎,并使用 ElasticSearch 官方提供的 Java High Level REST Client官方文档 进行 Java 客户端操作

添加依赖

在 pom.xml 文件中,添加 ElasticSearch 提供的 Java High Level REST Client 相关依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.9.2</elasticsearch.version>
</properties>
<!-- ElasticSearch High Level Client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>

<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>

<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<!-- ./ ElasticSearch High Level Client -->

注:在添加 ElasticSearch 相关依赖时,一定要指明版本号。如果不指定版本号,会直接继承 Spring Boot 的版本号,这样会导致与 ElasticSearch 的版本不一致,而出现错误。

配置 RestHighLevelClient

application.yml 配置文件

1
2
3
4
5
spring: 
elasticsearch:
rest:
ip-address: 127.0.0.1:9200,127.0.0.1:9201
scheme: http

RestHighLevelClient 配置,用于操作 ElasticSearch

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
@Configuration
public class EsRestHighLevelClientConfig {

@Value("${spring.elasticsearch.rest.scheme}")
private String scheme;

@Value("${spring.elasticsearch.rest.ip-address}")
private List<String> ipAddressList;

@Bean
public RestHighLevelClient restHighLevelClient() {
return new RestHighLevelClient(RestClient.builder(this.createHttpHost()));
}

/**
* 创建 HttpHost 对象
*
* @return 返回 HttpHost 对象数组
*/
private HttpHost[] createHttpHost() {
Asserts.check(!CollectionUtils.isEmpty(ipAddressList), "ElasticSearch cluster ip address cannot empty");

HttpHost[] httpHosts = new HttpHost[ipAddressList.size()];
for (int i = 0, len = ipAddressList.size(); i < len; i++) {
String ipAddress = ipAddressList.get(i);
String[] values = ipAddress.split(":");
String ip = values[0];
int port = Integer.parseInt(values[1]);
// 创建 HttpHost
httpHosts[i] = new HttpHost(ip, port, scheme);
}
return httpHosts;
}

}

编写 Elaticsearch 相关 API

操作类汇总

创建索引

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
@Service
public class ElasticsearchUtils {

//分片数量
private static final int DEFAULT_SHARDS = 3;
//副本数量
private static final int DEFAULT_REPLICAS = 1;

@Autowired
private RestHighLevelClient restHighLevelClient;

private static final String INDEX = "";

private static final String TYPE = "";

private static final Logger logger = LoggerFactory.getLogger(ElasticsearchUtils.class);

/**
* 创建 ES 索引
* (需要注意的是,7.x后,es删除了type,只允许存在一种type,不需要指定type的值,默认是_doc)
* @param index 索引
* @param properties 文档属性集合
* @return 返回 true,表示创建成功
* @throws IOException
*/
public boolean createIndex(String index, Map<String, Map<String, Object>> properties) throws IOException {
XContentBuilder builder = XContentFactory.jsonBuilder();
// ES 7.0 后的版本中,已经弃用 type
builder.startObject()
.startObject("mappings")
.field("properties", properties)
.endObject()
.startObject("settings")
.field("number_of_shards", DEFAULT_SHARDS)
.field("number_of_replicas", DEFAULT_REPLICAS)
.endObject()
.endObject();
CreateIndexRequest request = new CreateIndexRequest(index).source(builder);
CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response);
return response.isAcknowledged();
}

/**
* 创建Index
* @throws IOException
*/
private void createIndex1() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("test");
// 设置分片数量和副本数量
request.settings(Settings.builder().put("index.number_of_shards", 3).put("index.number_of_replicas", 2));
// 字段映射
Map<String, Object> message = new HashMap<>();
message.put("type", "text");
Map<String, Object> properties = new HashMap<>();
properties.put("message", message);
Map<String, Object> mapping = new HashMap<>();
mapping.put("properties", properties);
request.mapping(mapping);
// 执行创建请求
CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
boolean acknowledged = createIndexResponse.isAcknowledged();
boolean shardsAcknowledged = createIndexResponse.isShardsAcknowledged();
System.out.println(acknowledged && shardsAcknowledged);
// on shutdown
restHighLevelClient.close();
}
/**
* 设置idex及mapping 和 setting
* @param index
* @return
* @throws IOException
*/
public boolean createPinyIndex(String index) throws IOException {
// XContentBuilder builder = XContentFactory.jsonBuilder();
// ES 7.0 后的版本中,已经弃用 type
CreateIndexRequest request = new CreateIndexRequest(index);
//设置setting
ElasticsearchMS.indexSetting(request);
//设置mapping
ElasticsearchMS.indexMappingCommunity(request);
CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response);
return response.isAcknowledged();
}
/**
* 获取Index的分片数量和副本数量
* @throws IOException
*/
private void GetIndexRequest() throws IOException {
GetSettingsRequest request = new GetSettingsRequest().indices("test");
GetSettingsResponse getSettingsResponse = restHighLevelClient.indices().getSettings(request, RequestOptions.DEFAULT);
String numberOfShardsString = getSettingsResponse.getSetting("test", "index.number_of_shards");
System.out.println(numberOfShardsString);
Settings indexSettings = getSettingsResponse.getIndexToSettings().get("test");
Integer numberOfShards = indexSettings.getAsInt("index.number_of_shards", null);
System.out.println(numberOfShards);

// on shutdown
restHighLevelClient.close();
}
}

设置独有的setting和mapping

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
public class ElasticsearchMS {
/**
* 索引设置
* @param request
* @return
*/
public static CreateIndexRequest indexSetting(CreateIndexRequest request) {

Settings.Builder builder = Settings.builder();
builder.put("analysis.tokenizer.my_pinyin.type", "pinyin");

/**********中文转拼音的配置**********/
//keep_first_letter启用此选项时,例如:刘德华> ldh,默认值:true
builder.put("analysis.tokenizer.my_pinyin.keep_first_letter", true);
//limit_first_letter_length 设置first_letter结果的最大长度,默认值:16
builder.put("analysis.tokenizer.my_pinyin.limit_first_letter_length", 16);
//keep_full_pinyin当启用该选项,例如:刘德华> [ liu,de,hua],默认值:true
builder.put("analysis.tokenizer.my_pinyin.keep_full_pinyin", true);
//keep_joined_full_pinyin启用此选项时,例如:刘德华> [ liudehua],默认值:false
builder.put("analysis.tokenizer.my_pinyin.keep_joined_full_pinyin", false);
//keep_none_chinese 将非中文字母或数字保留在结果中,默认值:true
builder.put("analysis.tokenizer.my_pinyin.keep_none_chinese", true);
//keep_none_chinese_together 保持非中国信一起,默认值:true,如:DJ音乐家- > DJ,yin,yue,jia,当设置为false,例如:DJ音乐家- > D,J,yin,yue,jia,注意:keep_none_chinese应先启用
builder.put("analysis.tokenizer.my_pinyin.keep_none_chinese_together", true);
//keep_none_chinese_in_first_letter 将非中文字母保留在首字母中,例如:刘德华AT2016-> ldhat2016,默认值:true
builder.put("analysis.tokenizer.my_pinyin.keep_none_chinese_in_first_letter", false);
//keep_none_chinese_in_joined_full_pinyin 将非中文字母保留在拼音中,例如:刘德华2016-> liudehua2016,默认值:false
builder.put("analysis.tokenizer.my_pinyin.keep_none_chinese_in_joined_full_pinyin", true);
//none_chinese_pinyin_tokenize 打破非中国信成单独的拼音项,如果他们是拼音,默认值:true,如:liudehuaalibaba13zhuanghan- > liu,de,hua,a,li,ba,ba,13,zhuang,han,注意: keep_none_chinese和keep_none_chinese_together应首先启用
builder.put("analysis.tokenizer.my_pinyin.none_chinese_pinyin_tokenize", true);
//keep_original 启用此选项后,还将保留原始输入,默认值:false
builder.put("analysis.tokenizer.my_pinyin.keep_original", true);
//lowercase 小写非中文字母,默认:true
builder.put("analysis.tokenizer.my_pinyin.lowercase", true);
//trim_whitespace 默认值:true
builder.put("analysis.tokenizer.my_pinyin.trim_whitespace", true);
//remove_duplicated_term 启用此选项后,将删除重复的术语以保存索引,例如:de的> de,默认值:false,注意:与位置相关的查询可能会受到影响
builder.put("analysis.tokenizer.my_pinyin.remove_duplicated_term", true);
//ignore_pinyin_offset 在6.0之后,严格限制偏移量,不允许使用重叠的标记,使用此参数时,忽略偏移量将允许使用重叠的标记,请注意,所有与位置相关的查询或突出显示都将变为错误,您应使用多个字段并为不同的字段指定不同的设置查询目的。如果需要偏移量,请将其设置为false。默认值:true。
builder.put("analysis.tokenizer.my_pinyin.ignore_pinyin_offset", true);
//keep_separate_first_letter启用该选项时,将保留第一个字母分开,例如:刘德华> l,d,h,默认:假的,注意:查询结果也许是太模糊,由于长期过频
builder.put("analysis.tokenizer.my_pinyin.keep_separate_first_letter", true);

/******************当前索引的分词器配置*******************/
builder.put("analysis.analyzer.pinyin_analyzer.tokenizer", "my_pinyin");

request.settings(builder);
return request;
}

/**
* mapping的设置,带有拼音搜索
* @param request
* @return
*/
public static CreateIndexRequest indexMappingCommunity(CreateIndexRequest request) throws IOException {
XContentBuilder builder = null;
builder = XContentFactory.jsonBuilder();
builder.startObject();
{
builder.startObject("properties");
{
/* builder.startObject("coId");
{
builder.field("type", "text");
}
builder.endObject();*/
builder.startObject("userName");
{
builder.field("type", "keyword");
builder.startObject("fields");
{
builder.startObject("pinyin");
{
builder.field("type", "text");
builder.field("store", false);
builder.field("term_vector", "with_offsets");
builder.field("analyzer", "pinyin_analyzer");
builder.field("boost", 10);
}
builder.endObject();
}
builder.endObject();
}
builder.endObject();
}
builder.endObject();
}
builder.endObject();
request.mapping(builder);
return request;
}
}

获取mapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 /**
* 获取mapping
* @throws IOException
*/
private void getMapping() throws IOException {
// on startup
/* RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 19200, "http")));*/
GetMappingsRequest request = new GetMappingsRequest();
request.indices("test");
GetMappingsResponse getMappingResponse = restHighLevelClient.indices().getMapping(request, RequestOptions.DEFAULT);
Map<String, MappingMetadata> allMappings = getMappingResponse.mappings();
MappingMetadata indexMapping = allMappings.get("test");
Map<String, Object> mapping = indexMapping.sourceAsMap();
System.out.println(mapping);
// on shutdown
restHighLevelClient.close();
}

修改mapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 修改mapping
* @throws IOException
*/
private void putMapping() throws IOException {
// on startup
/* RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 19200, "http")));*/
PutMappingRequest request = new PutMappingRequest("test");
Map<String, Object> jsonMap = new HashMap<>();
Map<String, Object> message = new HashMap<>();
message.put("type", "text");
Map<String, Object> properties = new HashMap<>();
properties.put("message", message);
jsonMap.put("properties", properties);
request.source(jsonMap);

AcknowledgedResponse putMappingResponse = restHighLevelClient.indices().putMapping(request, RequestOptions.DEFAULT);
boolean acknowledged = putMappingResponse.isAcknowledged();
System.out.println(acknowledged);
// on shutdown
restHighLevelClient.close();
}

判断索引是否存在

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
 /**
* 判断是否存在
* @throws IOException
*/
private void exists() throws IOException {
GetIndexRequest request = new GetIndexRequest("test");
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
// on shutdown
restHighLevelClient.close();
}
/**
* 判断索引是否存在
* @param index 索引
* @return 返回 true,表示存在
*/
public boolean isExistIndex(String index) throws IOException {
GetIndexRequest getIndexRequest = new GetIndexRequest(index);
getIndexRequest.local(false);
getIndexRequest.humanReadable(true);
getIndexRequest.includeDefaults(false);

return restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
}


删除索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 删除索引
* @param index 索引
* @return 返回 true,表示删除成功
*/
public boolean deleteIndex(String index) throws IOException {
try {
DeleteIndexRequest request = new DeleteIndexRequest(index);
AcknowledgedResponse response = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);

return response.isAcknowledged();
} catch (ElasticsearchException exception) {
if (exception.status() == RestStatus.NOT_FOUND) {
throw new RuntimeException("Not found index: " + index);
}

throw exception;
}
}

保存文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ElasticSearchDocument<T> {

private String id;

private T data;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}
}
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
/**
* 保存文档
* <p>
* 如果文档存在,则更新文档;如果文档不存在,则保存文档。
* @param document 文档数据
*/
public void save(String index, ElasticSearchDocument<?> document) throws IOException {
IndexRequest indexRequest = new IndexRequest(index);
indexRequest.id(document.getId());
indexRequest.source(JSON.toJSONString(document.getData()), XContentType.JSON);
// 保存文档数据
restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
}

/**
* 批量保存文档
* <p>
* 如果集合中有些文档已经存在,则更新文档;不存在,则保存文档。
* @param index 索引
* @param documentList 文档集合
*/
public <T> void saveAll(String index, List<ElasticSearchDocument<T>> documentList) throws IOException {
if (CollectionUtils.isEmpty(documentList)) {
return;
}
// 批量请求
BulkRequest bulkRequest = new BulkRequest();
documentList.forEach(doc -> {
bulkRequest.add(new IndexRequest(index)
.id(doc.getId())
.source(JSON.toJSONString(doc.getData()), XContentType.JSON));
});

restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);

}

删除文档

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
 /**
* 根据文档 ID 删除文档
* @param index 索引
* @param id 文档 ID
*/
public void delete(String index, String id) throws IOException {
DeleteRequest deleteRequest = new DeleteRequest(index, id);

restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
}

/**
* 根据查询条件删除文档
* @param index 索引
* @param queryBuilder 查询条件构建器
*/
public void deleteByQuery(String index, QueryBuilder queryBuilder) throws IOException {
DeleteByQueryRequest deleteRequest = new DeleteByQueryRequest(index).setQuery(queryBuilder);
deleteRequest.setConflicts("proceed");

restHighLevelClient.deleteByQuery(deleteRequest, RequestOptions.DEFAULT);

}

/**
* 根据文档 ID 批量删除文档
* @param index 索引
* @param idList 文档 ID 集合
*/
public void deleteAll(String index, List<String> idList) throws IOException {
if (CollectionUtils.isEmpty(idList)) {
return;
}
BulkRequest bulkRequest = new BulkRequest();
idList.forEach(id -> bulkRequest.add(new DeleteRequest(index, id)));

restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
}

获取文档

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 根据索引和文档 ID 获取数据
* @param index 索引
* @param id 文档 ID
* @param <T> 数据类型
* @return T 返回 T 类型的数据
*/
public <T> T get(String index, String id, Class<T> resultType) throws IOException {
GetRequest getRequest = new GetRequest(index, id);
GetResponse response = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
String resultAsString = response.getSourceAsString();
return JSON.parseObject(resultAsString, resultType);
}

查询相关

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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/**
分页分词关键词查询
* 使用QueryBuilder
termQuery("key", obj) 完全匹配
termsQuery("key", obj1, obj2..) 一次匹配多个值
matchQuery("key", Obj) 单个匹配, field不支持通配符, 前缀具高级特性
multiMatchQuery("text", "field1", "field2"..); 匹配多个字段, field有通配符忒行
matchAllQuery(); 匹配所有文件
* 组合查询
must(QueryBuilders) : AND
mustNot(QueryBuilders): NOT
should: OR
percent_terms_to_match:匹配项(term)的百分比,默认是0.3
min_term_freq:一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
max_query_terms:一条查询语句中允许最多查询词语的个数,默认是25
stop_words:设置停止词,匹配时会忽略停止词
min_doc_freq:一个词语最少在多少篇文档中出现,小于这个值的词会将被忽略,默认是无限制
max_doc_freq:一个词语最多在多少篇文档中出现,大于这个值的词会将被忽略,默认是无限制
min_word_len:最小的词语长度,默认是0
max_word_len:最多的词语长度,默认无限制
boost_terms:设置词语权重,默认是1
boost:设置查询权重,默认是1
analyzer:设置使用的分词器,默认是使用该字段指定的分词器
*/
/**
* 查询
* @throws IOException
*/
private void searchESIndex() throws IOException {
String defaultIndex = "news";
// on startup
// RestHighLevelClient client = new RestHighLevelClient(
// RestClient.builder(new HttpHost("localhost", 9200, "http"), new HttpHost("localhost", 19200, "http")));
// 创建SearchRequest,可以指定Index,也可以不指定。不指定查询所有
SearchRequest searchRequest = new SearchRequest(defaultIndex);
// SearchRequest searchRequest = new SearchRequest(defaultIndex);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);

SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
TotalHits totalHits = hits.getTotalHits();
// the total number of hits, must be interpreted in the context of
// totalHits.relation
long numHits = totalHits.value;
System.out.println("numHits " + numHits);
// whether the number of hits is accurate (EQUAL_TO) or a lower bound of
// the total (GREATER_THAN_OR_EQUAL_TO)
TotalHits.Relation relation = totalHits.relation;
float maxScore = hits.getMaxScore();
System.out.println("maxScore " + maxScore);
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
// do something with the SearchHit
String sourceAsString = hit.getSourceAsString();
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
System.out.println(sourceAsMap);
}
// on shutdown
restHighLevelClient.close();
}

/**
* 分页
* @throws IOException
*/
private void searchListPage() throws IOException {
String defaultIndex = "news";
// on startup
// RestHighLevelClient client = new RestHighLevelClient(
// RestClient.builder(new HttpHost("localhost", 9200, "http"), new HttpHost("localhost", 19200, "http")));
// 创建SearchRequest,可以指定Index,也可以不指定。不指定查询所有
SearchRequest searchRequest = new SearchRequest(defaultIndex);
// SearchRequest searchRequest = new SearchRequest(defaultIndex);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 查询所有
// searchSourceBuilder.query(QueryBuilders.matchAllQuery());
// 查询type是杂文
searchSourceBuilder.query(QueryBuilders.termQuery("content", "后"));
// 类似于分页参数
searchSourceBuilder.from(0);
searchSourceBuilder.size(2);
// 排序
searchSourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC)); // 分数排序
searchSourceBuilder.sort(new FieldSortBuilder("createTime").order(SortOrder.ASC));// 时间升序
// 过滤指定的列。可以指定只搜索的列,也可以指定排除的列,也可以同时指定,同时指定include优先级高
searchSourceBuilder.fetchSource(true);
String[] includeFields = new String[] { "createTime", "innerObject.*" };
String[] excludeFields = new String[] { "title" };
searchSourceBuilder.fetchSource(null, excludeFields);
// 高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("content");
highlightBuilder.field(highlightTitle);
searchSourceBuilder.highlighter(highlightBuilder);

searchRequest.source(searchSourceBuilder);

SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
TotalHits totalHits = hits.getTotalHits();
// the total number of hits, must be interpreted in the context of
// totalHits.relation
long numHits = totalHits.value;
System.out.println("numHits " + numHits);
// whether the number of hits is accurate (EQUAL_TO) or a lower bound of
// the total (GREATER_THAN_OR_EQUAL_TO)
TotalHits.Relation relation = totalHits.relation;
float maxScore = hits.getMaxScore();
System.out.println("maxScore " + maxScore);

SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
// do something with the SearchHit
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
System.out.println(highlightFields);
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
System.out.println(sourceAsMap);
}
// on shutdown
restHighLevelClient.close();
}

/**
* 使用QueryBuilder DSL语法构建查询
* @throws IOException
*/
private void searchQueryBuilder() throws IOException {
String defaultIndex = "news";
// on startup
// RestHighLevelClient client = new RestHighLevelClient(
// RestClient.builder(new HttpHost("localhost", 9200, "http"), new HttpHost("localhost", 19200, "http")));

// 创建SearchRequest,可以指定Index,也可以不指定。不指定查询所有
SearchRequest searchRequest = new SearchRequest(defaultIndex);
// SearchRequest searchRequest = new SearchRequest(defaultIndex);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 可以结合Query DSL
BoolQueryBuilder filter = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("content", "后"))
.mustNot(QueryBuilders.termQuery("field", "nullappend"));
searchSourceBuilder.query(filter);
searchRequest.source(searchSourceBuilder);

SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
TotalHits totalHits = hits.getTotalHits();
// the total number of hits, must be interpreted in the context of
// totalHits.relation
long numHits = totalHits.value;
System.out.println("numHits " + numHits);
// whether the number of hits is accurate (EQUAL_TO) or a lower bound of
// the total (GREATER_THAN_OR_EQUAL_TO)
TotalHits.Relation relation = totalHits.relation;
float maxScore = hits.getMaxScore();
System.out.println("maxScore " + maxScore);

SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
// do something with the SearchHit
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
System.out.println(sourceAsMap);
}
// on shutdown
restHighLevelClient.close();
}

//AnalyzerAPI可以用于分析分词效果,用不同的分词器进行分词
private void anylyze() throws IOException {
// on startup
// RestHighLevelClient client = new RestHighLevelClient(
// RestClient.builder(new HttpHost("localhost", 9200, "http")));
AnalyzeRequest request = AnalyzeRequest.withGlobalAnalyzer("standard", "我是一个程序员", "I am cxy!");
request.explain(true);
AnalyzeResponse response = restHighLevelClient.indices().analyze(request, RequestOptions.DEFAULT);
DetailAnalyzeResponse detail = response.detail();
DetailAnalyzeResponse.AnalyzeTokenList analyzer = detail.analyzer();
String name = analyzer.getName();
System.out.println(name);
System.out.println("==============");

AnalyzeResponse.AnalyzeToken[] tokens1 = analyzer.getTokens();
for (AnalyzeResponse.AnalyzeToken t : tokens1) {
System.out.println(t.getTerm() + "\t" + t.getStartOffset() + "\t" + t.getEndOffset() + "\t" + t.getType());
}
// on shutdown
restHighLevelClient.close();
}
// =====================================================================================================================
/**
* 条件查询
*
* @param index 索引
* @param sourceBuilder 条件查询构建起
* @param <T> 数据类型
* @return T 类型的集合
*/
public <T> List<T> searchByQuery(String index, SearchSourceBuilder sourceBuilder, Class<T> resultType) throws IOException {
// 构建查询请求
SearchRequest searchRequest = new SearchRequest(index).source(sourceBuilder);
// 获取返回值
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
// 创建空的查询结果集合
List<T> results = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
// 以字符串的形式获取数据源
String sourceAsString = hit.getSourceAsString();
results.add(JSON.parseObject(sourceAsString, resultType));
}
return results;
}

/**
* 拼音分页
* @param index
* @param sourceBuilder
* @param resultType
* @param <T>
* @return
* @throws IOException
*/
public <T> List<T> searchByQueryPinyin(String index, SearchSourceBuilder sourceBuilder, Class<T> resultType) throws IOException {
// 构建查询请求
SearchRequest searchRequest = new SearchRequest(index).source(sourceBuilder);
// 获取返回值
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
List<T> results = null;
// 根据状态和数据条数验证是否返回了数据
if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
SearchHit[] hits = searchResponse.getHits().getHits();
// 创建空的查询结果集合
results = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
// 以字符串的形式获取数据源
String sourceAsString = hit.getSourceAsString();
// 将 JSON 转换成对象加入集合
results.add(JSON.parseObject(sourceAsString, resultType));
}
}
// 输出查询信息
logger.info(results.toString());
return results;
}
/**
* 分页搜索
* @param index
* @param searchSourceBuilder
* @param pageNum
* @param pageSize
* @return PageResponse<T>
*/
public <T> Page<T> searchListPage(String index, SearchSourceBuilder searchSourceBuilder, Class<T> clazz,
Integer pageNum, Integer pageSize){
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.source(searchSourceBuilder);
logger.info("DSL语句为:{}",searchRequest.source().toString());
try {
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Page<T> page = new Page<>();
page.setCurrent(pageNum);
page.setSize(pageSize);
page.setTotal(Integer.parseInt(String.valueOf(response.getHits().getTotalHits().value)));
List<T> dataList = new ArrayList<>();
SearchHits hits = response.getHits();
for(SearchHit hit : hits){
//高亮
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
System.out.println(highlightFields);
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
System.out.println(sourceAsMap);
//添加
dataList.add(JSONObject.parseObject(hit.getSourceAsString(), clazz));
}
page.setRecords(dataList);
return page;
} catch (Exception e) {
logger.error(e.getMessage());
throw new RuntimeException(String.valueOf(HttpStatus.BAD_REQUEST), e);
}
}
/**
* 聚合
* @param index
* @param searchSourceBuilder
* @param aggName 聚合名
* @return Map<Integer, Long> key:aggName value: doc_count
*/
public Map<Integer, Long> aggSearch(String index, SearchSourceBuilder searchSourceBuilder, String aggName){
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.source(searchSourceBuilder);
logger.info("DSL语句为:{}",searchRequest.source().toString());
try {
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggregations = response.getAggregations();
Terms terms = aggregations.get(aggName);
List<? extends Terms.Bucket> buckets = terms.getBuckets();
Map<Integer, Long> responseMap = new HashMap<>(buckets.size());
buckets.forEach(bucket-> {
responseMap.put(bucket.getKeyAsNumber().intValue(), bucket.getDocCount());
});
return responseMap;
} catch (Exception e) {
logger.error(e.getMessage());
throw new RuntimeException(String.valueOf(HttpStatus.BAD_REQUEST), e);
}

}

public User boolQuery() throws IOException {
// 创建 Bool 查询构建器
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 构建查询条件
boolQueryBuilder.must(QueryBuilders.matchPhraseQuery("userName.pinyin", "qiang"));
// 构建查询源构建器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(boolQueryBuilder);
// 创建查询请求对象,将查询对象配置到其中
SearchRequest searchRequest = new SearchRequest("test_index");
searchRequest.source(searchSourceBuilder);
// 执行查询,然后处理响应结果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 根据状态和数据条数验证是否返回了数据
if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
SearchHits hits = searchResponse.getHits();
for (SearchHit hit : hits) {
// 将 JSON 转换成对象
User userInfo = JSON.parseObject(hit.getSourceAsString(), User.class);
// 输出查询信息
logger.info(userInfo.toString());
return userInfo;
}
}
return null;
}

其他相关类

前端控制器

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
/**
* <p>
* 前端控制器
* </p>
*
* @author generation
* @since 2020-11-13
*/
@Controller
@RequestMapping("/demo/user")
public class UserController extends BaseController<UserService, User> {

@Autowired
private UserService userService;

@Autowired
private ElasticsearchUtils elasticsearchUtils;

@Override
public int getCurrentUserID() {
return super.getCurrentUserID();
}

/**
* @Description: 创建索引自定义mapping和setting
* @param index
* @return com.xiaowo.demo.common.msg.ResultEntity
* @author liuhw
*/
@GetMapping(value = "createPinyIndex")
@ResponseBody
public ResultEntity createPinyIndex(@RequestParam String index) throws Exception {
userService.createPinyIndex(index);
return new ResultEntity<>();
}

/**
* @Description: 简单拼音查询
* @param index
* @param userName
* @return com.xiaowo.demo.common.msg.ResultEntity
* @author liuhw
*/
@GetMapping(value = "searchUserListByPinyinName")
@ResponseBody
public ResultEntity searchUserListByPinyinName(@RequestParam String index, @RequestParam String userName) {
List<User> users = userService.searchUserListByPinyinName(index, userName);
return new ResultEntity<>(users);
}

/**
* @Description: 简单拼音查询结合分页参数
* @param spage
* @return com.xiaowo.demo.common.msg.ResultEntity
* @author liuhw
*/
@PostMapping(value = "searchUserListPageByPinyinName")
@ResponseBody
public ResultEntity searchUserListPageByPinyinName(@RequestBody ReqPageDto<UserQueryDto> spage) throws IOException {
Page<User> page = new Page<>(spage.getPagination().getCurrPage(),
spage.getPagination().getPageSize());
if (null == spage.getSearch().getIndex()){
return new ResultEntity("1001", "未传索引值");
}
Page list = userService.searchUserListPageByPinyinName(page, spage);
return new ResultEntity<>(list);
}

/**
* @Description: bool查询测试
* @param
* @return com.xiaowo.demo.common.msg.ResultEntity
* @author liuhw
*/
@GetMapping(value = "boolQuery")
@ResponseBody
public ResultEntity boolQuery() throws IOException {
User user = elasticsearchUtils.boolQuery();
return new ResultEntity<>(user);
}

/**
* @Description: 根据用户姓名查询
* @param userName
* @return com.xiaowo.demo.common.msg.ResultEntity
* @author liuhw
*/
@GetMapping(value = "searchUserByName")
@ResponseBody
public ResultEntity searchUserByName(@RequestParam String index, @RequestParam String userName) {
List<User> users = userService.searchUserByName(index, userName);
return new ResultEntity<>(users);
}

/**
* @Description: 保存用户
* @param user
* @return com.xiaowo.demo.common.msg.ResultEntity
* @author liuhw
*/
@PostMapping(value = "saveUser")
@ResponseBody
public ResultEntity saveUser(@RequestBody User user) {
userService.saveUser(user);
return new ResultEntity<>();
}
}

服务类和实现服务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface UserService extends IService<User> {

void saveUser(User user);

void saveAllUser(List<User> userList);

List<User> searchUserByName(String index, String name);

List<User> searchUserListByPinyinName(String index, String name);

Page searchUserListPageByPinyinName(Page page, ReqPageDto<UserQueryDto> spage) throws IOException;

void createPinyIndex(String index) throws Exception;

}
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
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService {

@Autowired
private UserDao userDao;

private static final String ES_INDEX = "test";

private static final String TEST_INDEX = "idx_test";

private static final String TEST_DEV = "idx_dev";

@Autowired
private ElasticsearchUtils elasticsearchUtils;

@Override
public List<User> getUserListPage(Page page, UserDto user) {
List<User> userList = userDao.getUserListPage(page, user);
if (userList == null) {
return Collections.emptyList();
}
return userList;
}

@Override
public void createPinyIndex(String index) throws Exception {
elasticsearchUtils.createPinyIndex(index);
}

/**
* 保存用户信息
* @param user 用户信息
*/
@Override
public void saveUser(User user) {
ElasticSearchDocument<User> document = new ElasticSearchDocument<>();
document.setId(user.getId().toString());
document.setData(user);
try {
elasticsearchUtils.save("test_index", document);
} catch (IOException e) {
throw new RuntimeException("Failed to save user, id: " + user.getId(), e);
}

}

/**
* 批量保存用户
* @param userList 用户集合
*/
@Override
public void saveAllUser(List<User> userList) {
if (CollectionUtils.isEmpty(userList)) {
return;
}
List<ElasticSearchDocument<User>> documentList = new ArrayList<>(userList.size());
ElasticSearchDocument<User> document = null;
for (User user : userList) {
document = new ElasticSearchDocument<>();
document.setId(String.valueOf(user.getId()));
document.setData(user);
documentList.add(document);
}
try {
elasticsearchUtils.saveAll(ES_INDEX, documentList);
} catch (IOException e) {
throw new RuntimeException("Failed to batch save users", e);
}
}

/**
* 根据用户 ID 获取用户信息
* @param id 用户 ID
* @return 用户对象
*/
public User getUser(String id) {
try {
User user = elasticsearchUtils.get(ES_INDEX, id, User.class);
return user;
} catch (IOException e) {
throw new RuntimeException("Failed to get user, id: " + id, e);
}
}

/**
* 根据用户姓名查询
* @param name 用户姓名
* @return 用户集合
*/
@Override
public List<User> searchUserByName(String index, String name) {
// 构建查询条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchPhraseQuery("userName", name));
// 构建查询生成器
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(boolQueryBuilder);
try {
List<User> userList = elasticsearchUtils.searchByQuery(index, sourceBuilder, User.class);
return userList;
} catch (IOException e) {
throw new RuntimeException("Failed to search user by name: " + name, e);
}
}

@Override
public List<User> searchUserListByPinyinName(String index, String userName) {
// 构建查询源构建器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
String name = "userName";
if (StringTools.checkLetter(userName)){
name = "userName.pinyin";
}
// 创建 Bool 查询构建器
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchPhraseQuery(name, userName));
searchSourceBuilder = searchSourceBuilder.query(boolQueryBuilder);
try {
// 执行查询,然后处理响应结果
List<User> userList = elasticsearchUtils.searchByQueryPinyin(index, searchSourceBuilder, User.class);
return userList;
} catch (IOException e) {
throw new RuntimeException("Failed to search user by name: " + userName, e);
}
}

@Override
public Page searchUserListPageByPinyinName(Page page, ReqPageDto<UserQueryDto> spage) throws IOException {
// 构建查询源构建器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
String userName = spage.getSearch().getUserName();
String name = "userName";
if (StringTools.checkLetter(userName)){
name = "userName.pinyin";
}
// 创建 Bool 查询构建器
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchPhraseQuery(name, userName));
searchSourceBuilder = searchSourceBuilder.query(boolQueryBuilder);
// 排序
/* if (null != spage.getSort().getPredicate()){
searchSourceBuilder.sort(new FieldSortBuilder(spage.getSort().getPredicate()).order(SortOrder.ASC));// 升序
}*/
//设置高亮
/*String preTags = "<span style='color:red'>";
String postTags = "</span>";
HighlightBuilder highlightBuilder = new HighlightBuilder();
HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("content")
.preTags(preTags)
.postTags(postTags);
highlightBuilder.field(highlightTitle);
searchSourceBuilder.highlighter(highlightBuilder);*/
Page<User> listPage = elasticsearchUtils.searchListPage(spage.getSearch().getIndex(), searchSourceBuilder, User.class,
Integer.parseInt(String.valueOf(page.getCurrent())), Integer.parseInt(String.valueOf(page.getSize())));
return listPage;
}

/**
* 根据用户 ID 删除用户信息
* @param id 用户 ID
*/
public void deleteUser(String id) {
try {
elasticsearchUtils.delete(ES_INDEX, id);
} catch (IOException e) {
throw new RuntimeException("Failed to delete user by id: " + id, e);
}
}

/**
* 根据用户姓名删除
*
* @param name 用户姓名
*/
public void deleteUserByName(String name) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchPhraseQuery("name", name));
try {
elasticsearchUtils.deleteByQuery(ES_INDEX, boolQueryBuilder);
} catch (IOException e) {
throw new RuntimeException("Failed to delete user by name: " + name, e);
}
}

}