基于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:9280username: xxxxpassword: 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:9280username: xxxxpassword: 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 {@AutowiredRestHighLevelClient 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;}
}