springboot 集成 es--未完结

基于es7.10.x版本

一、前提知识

常见的两种方式:spring boot提供的APIES 官方提供的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 方式

xml 复制代码
        <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>
yaml 复制代码
spring:
  elasticsearch:
    uris: http://172.31.97.4:9280
    username: xxxx
    password: xxxx

2、springdata

ElasticsearchRestTemplate+ElasticsearchRepository 方式

首先springdata操作es必须要将版本号和es的版本号对应上 ,否则会报错(倒不用完全一一对应,但版本号最好不要相差太多)。springdata引入的版本号由springboot的版本号决定,对应关系如下:

xml 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
yaml 复制代码
spring:
  elasticsearch:
    uris: http://172.31.97.4:9280
    username: xxxx
    password: xxxx

二、API方法

1、es 官方

工工具类

java 复制代码
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;
    }
}
相关推荐
弗拉唐3 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
2401_857610034 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_4 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
天天进步20155 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
Karoku0666 小时前
【企业级分布式系统】ELK优化
运维·服务器·数据库·elk·elasticsearch
乌啼霜满天2496 小时前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
tangliang_cn6 小时前
java入门 自定义springboot starter
java·开发语言·spring boot
Grey_fantasy6 小时前
高级编程之结构化代码
java·spring boot·spring cloud
苹果酱05677 小时前
前端面试vue篇:Vue2 和 Vue3 在设计和性能上有显著区别
java·spring boot·毕业设计·layui·课程设计