SpringBoot集成Elasticsearch8.X

668 阅读5分钟

Elasticsearch 的API 分为 REST Client API(http请求形式)以及 transportClient API两种。相比来说transportClient API效率更高,transportClient 是通过Elasticsearch内部RPC的形式进行请求的,连接可以是一个长连接,相当于是把客户端的请求当成Elasticsearch 集群的一个节点,当然 REST Client API 也支持http keepAlive形式的长连接,只是非内部RPC形式。但是从Elasticsearch 7 后就会移除transportClient 。主要原因是transportClient 难以向下兼容版本。

spring将elasticsearch过度封装,让开发者很难跟ES的DSL查询语句进行关联。再者就是更新速度,ES的更新速度是非常快的,但是spring-data-elasticsearch更新速度比较缓慢。

另外,spring-data-elasticsearch底层依赖的是TransportClient。而TransportClient在ES7中已被弃用,取而代之的是 Java 高级 REST 客户端,并将在 Elasticsearch 8.0 中删除。

es在7.15.0及以后版本放弃对旧版本中的Java REST Client (High Level Rest Client (HLRC))支持,推荐使用的Java API Client 8.x

<dependency>
    <!--es的starter, 主要是为了实现自动化配置,方便快捷的获取rest client-->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

image.png

说到底硕放底层还是对如下jar的封装

<dependency>
   <groupId>org.elasticsearch.client</groupId>
   <artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
</dependency>

因此这里我就按照官方文档使用了推荐的,同时最小依赖原则

<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>8.5.0</version>
</dependency>

<!-- 依赖的 jakarta 2.X 低版本 要 升级-->
<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.0.1</version>
</dependency>

bootstrap.yml增加自定义配置如下:

elasticsearch:
  #ES的连接地址,多个地址用逗号分隔
  uris: 1.13.102.59:9200
  username:
  password:

接下来 声明一个配置类ElasticSearchConfig,创建一个客户端

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.commons.compress.utils.Lists;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * @author frank
 * @version 1.0
 */
@Configuration
public class ElasticSearchConfig {

    @Value("${elasticsearch.uris}")
    private String uris;
    @Value("${elasticsearch.username}")
    private String name;
    @Value("${elasticsearch.password}")
    private String password;

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        ElasticsearchTransport transport = new RestClientTransport(restClient(), new JacksonJsonpMapper());
        return new ElasticsearchClient(transport);
    }

    @Bean
    public RestClient restClient() {
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(name, password));
        List<HttpHost> httpHosts = Lists.newArrayList();
        String[] split = uris.split(",");
        for (int i = 0; i < split.length; i++) {
            httpHosts.add(HttpHost.create(split[i]));
        }
        HttpHost[] httpHosts1 = httpHosts.toArray(new HttpHost[0]);
        RestClient client = RestClient
                .builder(httpHosts1)
                .setHttpClientConfigCallback(httpAsyncClientBuilder ->
                        httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider).setKeepAliveStrategy((response, context) -> 180 * 1000))
                .build();
        return client;
    }
}

接下来定义索引与文档的操作服务,忽略接口定义类,相关自定义类后面给出

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse;
import co.elastic.clients.elasticsearch.indices.GetIndexResponse;
import co.elastic.clients.elasticsearch.indices.IndexState;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author frank
 * @version 1.0
 */
@Service
@Slf4j
public class EsearchServiceImpl{

    @Autowired
    private ElasticsearchClient client;

    /**
     * 判断索引是否存在
     *
     * @param indexName
     * @return
     * @throws IOException
     */
    public boolean hasIndex(String indexName) throws IOException {
        BooleanResponse exists = client.indices().exists(d -> d.index(indexName));
        return exists.value();
    }

    /**
     * 查询索引详情
     *
     * @param indexName
     * @return
     * @throws IOException
     */
 
    public Map<String, IndexState> queryIndex(String indexName) {
        try {
            GetIndexResponse getIndexResponse = client.indices().get(i -> i.index(indexName));
            //stringIndexStateMap.get(indexName).mappings().toString()  //注意序列化问题
            return getIndexResponse.result();
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("查询索引失败");
        }
    }

    /**
     * 删除索引
     *
     * @param indexName
     * @throws IOException
     */
    public boolean deleteIndex(String indexName) {
        try {
            DeleteIndexResponse response = client.indices().delete(d -> d.index(indexName));
            return response.acknowledged();
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("删除索引失败");
        }
    }

    /**
     * 创建索引
     *
     * @param indexName
     * @return
     * @throws IOException
     */
    public boolean createIndex(String indexName) {
        try {
            client.indices().create(c -> c.index(indexName));
        } catch (IOException e) {
            log.error("索引创建失败:{}", e.getMessage());
            throw new ServiceException("创建索引失败");
        }
        return true;
    }

    /**
     * 创建索引,不允许外部直接调用
     *
     * @param indexName
     * @param mapping
     * @throws IOException
     */
    private boolean createIndex(String indexName, Map<String, Property> mapping) throws IOException {
        CreateIndexResponse createIndexResponse = client.indices().create(c -> {
            c.index(indexName).mappings(mappings -> mappings.properties(mapping));
            return c;
        });
        return createIndexResponse.acknowledged();
    }

    /**
     * 重新创建索引,如果已存在先删除
     *
     * @param indexName
     * @param mapping
     */
    public void reCreateIndex(String indexName, Map<String, Property> mapping) {
        try {
            if (this.hasIndex(indexName)) {
                this.deleteIndex(indexName);
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("删除索引失败");
        }

        try {
            this.createIndex(indexName, mapping);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("重新创建索引失败");
        }
    }


    /**
     * 新增数据
     *
     * @param indexName
     * @param id        自己的主键标识
     * @throws IOException
     */
    public String insertDocument(String indexName, Map<String, Object> obj, String id) {
        try {
            IndexRequest.Builder builder = new IndexRequest.Builder().index(indexName).document(obj);
            if (StringUtils.isNotBlank(id)) {
                builder.id(id);
            }
            IndexResponse index = client.index(builder.build());
            return index.id();
        } catch (IOException e) {
            log.error("数据插入ES异常:{}", e.getMessage());
            throw new ServiceException("ES新增数据失败");
        }
    }


    /**
     * 修改数据
     *
     * @param indexName
     * @param id        自己的主键标识
     * @throws IOException
     */
    public boolean updateDocument(String indexName, Map<String, Object> obj, String id) {
        try {
            client.update(u -> u
                            .index(indexName)
                            .id(id)
                            .doc(obj)
                    , String.class);
            return true;
        } catch (IOException e) {
            log.error("数据修改ES异常:{}", e.getMessage());
            throw new ServiceException("ES修改数据失败");
        }
    }


    /**
     * 查询数据
     *
     * @param indexName
     * @param id
     * @return
     */
    public Map searchDocument(String indexName, String id) {

        try {
            GetResponse<Map> getResponse = client.get(g -> g
                            .index(indexName)
                            .id(id)
                    , Map.class
            );
            return getResponse.source();
        } catch (IOException e) {
            log.error("查询ES异常:{}", e.getMessage());
            throw new ServiceException("查询ES数据失败");
        }
    }

    /**
     * 删除数据
     *
     * @param indexName
     * @param id
     * @return
     */
    public boolean deleteDocument(String indexName, String id) {
        try {
            client.delete(d -> d
                    .index(indexName)
                    .id(id)
            );
        } catch (IOException e) {
            log.error("删除Es数据异常:{}", e.getMessage());
            throw new ServiceException("数据删除失败");
        }
        return true;
    }


    /**
     * 批量增加数据
     *
     * @param indexName
     * @return
     */
    public boolean batchInsertDocument(String indexName, List<Map<String, Object>> objs) {
        try {
            List<BulkOperation> bulkOperationArrayList = new ArrayList<>();
            for (Map<String, Object> obj : objs) {
                bulkOperationArrayList.add(BulkOperation.of(o -> o.index(i -> i.document(obj))));
            }
            client.bulk(b -> b.index(indexName).operations(bulkOperationArrayList));
            return true;
        } catch (IOException e) {
            log.error("数据插入ES异常:{}", e.getMessage());
            throw new ServiceException("ES新增数据失败");
        }
    }

    /**
     * 非主键查询
     *
     * @param indexName
     * @return
     */
    private TableDataInfo searchDocumentList(String indexName, Query query, EsPage esPage) {
        try {
            SearchRequest.Builder request = new SearchRequest.Builder().index(indexName);
            if (query != null) {
                request.query(query);
            }
            request.from(esPage.getPageNum() - 1);
            request.size(esPage.getPageSize());
            if (esPage.getOrderBy() != null) {
                request.sort(f -> f.field(o -> o.field(esPage.getOrderBy()).order("asc".equals(esPage.getType()) ? SortOrder.Asc : SortOrder.Desc)));
            }
            List<String> includes = esPage.getIncludes();
            if (includes != null && includes.size() > 0) {
                request.source(a -> a.filter(v -> v.includes(includes)));
            }
            SearchResponse<Map> search = client.search(request.build(), Map.class);
            List<Map<String, Object>> data = new ArrayList<>();
            HitsMetadata<Map> hits = search.hits();
            TotalHits total = hits.total();
            for (Hit<Map> hit : hits.hits()) {
                data.add(hit.source());
            }
            return new TableDataInfo(data, (int) total.value());
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException(String.format("【查询 -> 失败】从es中查询分析后的日志出错,错误信息为:{}", e.getMessage()));
        }
    }

    @Override
    public TableDataInfo searchTermDocumentList(String indexName, EsPage esPage) {
        Query query = null;
        String cond = esPage.getCond();
        if (cond != null) {
            List<Query> list = new ArrayList<>();
            String[] split = cond.split(",");
            for (int i = 0; i < split.length; i++) {
                String[] split1 = split[i].split(":");
                list.add(QueryBuilders.match()
                        .field(split1[0]).query(split1[1])
                        //.value(split1[1])
                        .build()._toQuery());
            }
            query = QueryBuilders.bool().must(list).build()._toQuery();
        }
        return searchDocumentList(indexName, query, esPage);
    }
}

构建列表查询分页类

import java.util.List;

/**
 * @author frank
 * @version 1.0
 */
public class EsPage {

    /**
     * 当前记录起始索引
     */

    private Integer pageNum = 1;

    /**
     * 查询条件 eg:  name:doc,age:12
     */
    private String cond;

    /**
     * 每页显示记录数
     */
    private Integer pageSize = 10;

    /**
     * 排序列
     */
    private String orderBy;

    /**
     * 排序的方向desc或者asc
     */
    private String type = "asc";


    //选择展示的字段,默认所有
    private List<String> includes;


    public List<String> getIncludes() {
        return includes;
    }

    public void setIncludes(List<String> includes) {
        this.includes = includes;
    }

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public String getOrderBy() {
        return orderBy;
    }

    public void setOrderBy(String orderBy) {
        this.orderBy = orderBy;
    }

    public String getCond() {
        return cond;
    }

    public void setCond(String cond) {
        this.cond = cond;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

表格分页数据对象 TableDataInfo

import java.io.Serializable;
import java.util.List;

public class TableDataInfo implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 总记录数
     */
    private long total;

    /**
     * 列表数据
     */
    private List<?> records;

    /**
     * 表格数据对象
     */
    public TableDataInfo() {
    }

    /**
     * 分页
     *
     * @param list  列表数据
     * @param total 总记录数
     */
    public TableDataInfo(List<?> list, int total) {
        this.records = list;
        this.total = total;
    }

    public long getTotal() {
        return total;
    }

    public void setTotal(long total) {
        this.total = total;
    }

    public List<?> getRecords() {
        return records;
    }

    public void setRecords(List<?> records) {
        this.records = records;
    }
}