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;
    }
}
相关推荐
洛森唛1 小时前
ElasticSearch查询语句Query String详解:从入门到精通
后端·elasticsearch
用户8307196840821 小时前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解2 小时前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解2 小时前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记6 小时前
Spring Boot Web MVC配置详解
spring boot·后端
洛森唛1 天前
Elasticsearch DSL 查询语法大全:从入门到精通
后端·elasticsearch
初次攀爬者1 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840821 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解1 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者2 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq