基于es7.10.x版本
一、前提知识
常见的两种方式:spring boot提供的API 和 ES 官方提供的API
-
ES官方:
RestHighLevelClient:
适用于复杂、更细粒度控制的Elasticsearch 操作 -
spring boot:
ElasticsearchRestTemplate:比 RestHighLevelClient 抽象更高,更接近于 Spring Data 的风格,当你想利用 Spring Data 的特性(如查询方法、分页等)与 Elasticsearch 交互时,这是一个很好的选择,但有些复杂查询无法完成。
ElasticsearchRepository:抽象级别最高,隐藏了与 Elasticsearch 交互的底层细节,并提供了基于方法的查询功能,能够快速实现 CRUD 操作。
建议使用RestHighLevelClient
原因:
版本:ElasticsearchRestTemplate本身与spring-boot-starter-data-elasticsearch紧密依赖。如果想升级ElasticsearchRestTemplate,那就必须连带升级项目的Springboot版本,这个风险就比较高了,一般项目的Springboot版本不会轻易升级
灵活度:比较灵活,可以直接使用ES的DSL语法,实现复杂查询,同时没有与其他部件绑定,所以版本可以自由选择。,由于ElasticsearchRestTemplate是spring-boot-starter-data-elasticsearch封装的工具类,虽然使用上稍微方便一些,但是失去了灵活性,出现问题时也不易排查。
二、环境搭建
1、es 官方
RestHighLevelClient 方式
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.10.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.10.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.10.0</version>
</dependency>
spring:
elasticsearch:
uris: http://172.31.97.4:9280
username: xxxx
password: xxxx
2、springdata
ElasticsearchRestTemplate+ElasticsearchRepository 方式
首先springdata操作es必须要将版本号和es的版本号对应上,否则会报错(倒不用完全一一对应,但版本号最好不要相差太多)。springdata引入的版本号由springboot的版本号决定,对应关系如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
spring:
elasticsearch:
uris: http://172.31.97.4:9280
username: xxxx
password: xxxx
二、API方法
1、es 官方
工工具类
package com.wang.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.query.AbstractQueryBuilder;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Spider Man
* @date 2024-05-23 15:15
*/
@Component
@Slf4j
public class ESUtils {
@Autowired
RestHighLevelClient esHighLevelClient;
/**
* 获取总条数
*
* @param indexName
* @return
* @throws IOException
*/
public long getTotalNum(String indexName) throws IOException {
CountRequest countRequest = new CountRequest(indexName);
// 如果需要,你可以在这里添加查询条件
// countRequest.query(QueryBuilders.matchQuery("field_name", "value"));
CountResponse countResponse = esHighLevelClient.count(countRequest, RequestOptions.DEFAULT);
return countResponse.getCount();
}
/**
* 获取总页数
*
* @param totalNum
* @param limit
* @return
*/
public int getTotalPage(long totalNum, int limit) {
//总页数
return (int) Math.ceil((double) totalNum / limit);
}
/**
* 批量插入数据
*
* @param indexName
* @param list
* @return boolean
*/
public boolean multiAddDoc(String indexName, List<JSONObject> list) {
try {
BulkRequest bulkRequest = new BulkRequest();
list.forEach(doc -> {
String source = JSONUtil.toJsonStr(doc);
IndexRequest indexRequest = new IndexRequest(indexName);
indexRequest.source(source, XContentType.JSON);
bulkRequest.add(indexRequest);
});
BulkResponse bulkResponse = esHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()){
log.error("批量插入失败,第一条错误原因为 {}",bulkResponse.getItems()[0].getFailureMessage());
}else {
log.info("批量插入成功,向索引 {} 中批量插入 {} 条数据", indexName, list.size());
}
return !bulkResponse.hasFailures();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 根据id更新文档,全部字段更新
*
* @param indexName
* @param docId
* @param jsonObject
* @return boolean
*/
public boolean updateDocAllFiled(String indexName, String docId, JSONObject jsonObject) {
try {
UpdateRequest updateRequest = new UpdateRequest(indexName, docId).doc(JSONUtil.toJsonStr(jsonObject), XContentType.JSON);
UpdateResponse updateResponse = esHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
int total = updateResponse.getShardInfo().getTotal();
log.info("更新文档的影响数量为{}", total);
return total > 0;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 局部更新
*
* @param indexName
* @param map key为文档的id,map为所要更新字段的字段名称和值
* @return
* @throws IOException
*/
public boolean updateDocSomeFiled(String indexName, Map<String,Map<String, Object>> map) throws IOException {
if (CollUtil.isEmpty(map)) {
log.info("局部更新数据不能为空");
return false;
}
BulkRequest bulkRequest = new BulkRequest();
map.forEach((docId, value) -> {
UpdateRequest updateRequest = new UpdateRequest(indexName, docId);
updateRequest.doc(value);
bulkRequest.add(updateRequest);
});
if (CollUtil.isNotEmpty(bulkRequest.requests())) {
BulkResponse bulkResponse = esHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()) {
log.error("更新失败====》" + bulkResponse.buildFailureMessage());
return false;
}
return true;
} else {
return false;
}
}
/**
* 例如:
* TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("ethnic_code.keyword", "汉族");
* Map<String, Object> map = esUtils.conditionSearchBySelfQuery(index, 1, 60, "pat_name",
* termQueryBuilder, "age_year", SortOrder.ASC, null, true);
*
*
* 条件搜索分页
*
* @param indexName 索引库
* @param pageNum 起始页
* @param pageSize 每页大小
* @param highName 高亮字段
* @param abstractQueryBuilder 搜索条件
* @param sortName 排序字段
* @param sortOrder 排序类型
* @param includes 显示的字段
* @param isShowDocumentId 是否显示文档id
* @return
* @throws IOException
*/
public Map<String, Object> conditionSearchBySelfQuery(String indexName, Integer pageNum, Integer pageSize, String highName, AbstractQueryBuilder abstractQueryBuilder, String sortName, SortOrder sortOrder, String[] includes, boolean isShowDocumentId) throws IOException {
SearchRequest searchRequest = new SearchRequest(indexName);
//构造搜索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.fetchSource(includes, null);
if (sortName != null && sortOrder != null) {
sourceBuilder.sort(sortName, sortOrder);
}
sourceBuilder.query(abstractQueryBuilder);
//高亮处理
if (!StrUtil.isEmpty(highName)) {
buildHighlight(sourceBuilder, highName);
}
//分页处理
if (pageNum != null && pageSize != null) {
sourceBuilder.from(pageSize * (pageNum - 1));
sourceBuilder.size(pageSize);
}
//超时设置
sourceBuilder.timeout(TimeValue.timeValueSeconds(60));
System.out.println("DSL语句为:\n"+sourceBuilder);
searchRequest.source(sourceBuilder);
//执行搜索
SearchResponse searchResponse = esHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
List<JSONObject> resultList = new ArrayList<>();
for (SearchHit hit : searchHits) {
//原始查询结果数据
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
if (isShowDocumentId) {
sourceAsMap.put("_id", hit.getId());
}
//高亮处理
if (!StrUtil.isEmpty(highName)) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField highlightField = highlightFields.get(highName);
if (highlightField != null) {
Text[] fragments = highlightField.fragments();
StringBuilder value = new StringBuilder();
for (Text text : fragments) {
value.append(text);
}
sourceAsMap.put(highName, value.toString());
}
}
JSONObject jsonObject = JSONUtil.parseObj(JSONUtil.toJsonStr(sourceAsMap));
resultList.add(jsonObject);
}
long total = searchHits.getTotalHits().value;
Map<String, Object> pageMap = new HashMap<>();
if (pageNum != null && pageSize != null) {
//当前页
pageMap.put("pageNum", pageNum);
//每页显示条数
pageMap.put("pageSize", pageSize);
//总页数
pageMap.put("totalPage", total == 0 ? 0 : (int) (total % pageSize == 0 ? total / pageSize : (total / pageSize) + 1));
}
//总条数
pageMap.put("totalNum", total);
//数据
pageMap.put("data", resultList);
return pageMap;
}
/**
* 构建高亮字段
*
* @param sourceBuilder
* @param highName
*/
private void buildHighlight(SearchSourceBuilder sourceBuilder, String highName) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
//设置高亮字段
highlightBuilder.field(highName);
//多个高亮显示
highlightBuilder.requireFieldMatch(false);
//高亮标签前缀
highlightBuilder.preTags("<span style='color:red'>");
//高亮标签后缀
highlightBuilder.postTags("</span>");
sourceBuilder.highlighter(highlightBuilder);
}
/**
* 根据id删除
*
* @param indexName
* @param id
*/
public void deleteById(String indexName, String id) {
DeleteRequest deleteRequest = new DeleteRequest(indexName).id(id);
try {
DeleteResponse deleteResponse = esHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
if (deleteResponse.status().getStatus() != RestStatus.OK.getStatus()) {
log.error(">>>> 删除id={}数据失败,返回状态码={} <<<<", id, deleteResponse.status().getStatus());
}
} catch (IOException e) {
log.error(">>>> 删除数据发生异常,id={},异常信息={} <<<<", id, e.getMessage());
}
}
/**
* 根据id查询
*
* @param indexName
* @param id
* @return
*/
public Map<String, Object> queryById(String indexName, String id) {
GetRequest getRequest = new GetRequest(indexName).id(id);
Map<String, Object> map = null;
try {
GetResponse getResponse = esHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
map = getResponse.getSource();
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
/**
* 判断索引是否存在
*
* @param indexName 索引名称
* @return
* @throws IOException
*/
public boolean indexIsExists(String indexName) throws IOException {
GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
return esHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
}
public boolean createIndex(String indexName, Map<String, Object> propertyMap) throws IOException {
boolean flag = false;
if (!indexIsExists(indexName)) {
try {
CreateIndexRequest index = new CreateIndexRequest("index_name");
Map<String, Object> properties = new HashMap<>();
Map<String, Object> propertie = new HashMap<>();
propertie.put("type", "text");
propertie.put("index", true);
propertie.put("analyzer", "ik_max_word");
properties.put("field_name", propertie);
XContentBuilder builder = JsonXContent.contentBuilder();
builder.startObject()
.startObject("mappings")
.startObject("index_name")
.field("properties", properties)
.endObject()
.endObject()
.startObject("settings")
.field("number_of_shards", 3)
.field("number_of_replicas", 1)
.endObject()
.endObject();
index.source(builder);
esHighLevelClient.indices().create(index, RequestOptions.DEFAULT);
flag = true;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("创建索引和映射关系失败");
}
}
return flag;
}
}