天天看點

ElasticSearch8.x Java API 實體類、工具類、測試類及常見問題

作者:小馬搞程式設計

ES 8.x 新版本中,Type 概念被棄用,是以新版 JavaAPI 也相應做出了改變,使用更加簡便。ES 官方從 7.15 起開始建議使用新的 JavaAPI

<!-- elasticsearch-java -->
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>8.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.3</version>
        </dependency>
        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.13.3</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.6.7</version>
        </dependency>           
  • NoClassDefFoundError 異常

java.lang.NoClassDefFoundError: jakarta/json/JsonException

如果出現這個異常,要加入 jakarta.json-api 包,版本 2.x ;

  • LocalDateTime

如果實體類中有 LocalDateTime 字段,要加入 jackson-datatype-jsr310 ,可以實作序列化與反序列化;同時實體類中的 LocalDateTime 字段加如下注解:

@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化
private LocalDateTime time;           

2、實體類

2.1、文檔實體類 EsDocument.java

如果有自定義的構造器,一定不要忘記把無參構造器補上; @NoArgsConstructor ,否則無法反序列化。

package com.tuwer.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.Instant;

/**
 * <p>ES文檔實體類</p>
 * ----------------------
 * 一定要有無參構造器;否則反序列化會失敗
 * ----------------------
 *
 * @author 土味兒
 * Date 2022/8/9
 * @version 1.0
 */
@Data
@NoArgsConstructor
public class EsDocument {
 
    /**
     * 文檔id
     */
    private String id;
    /**
     * 文檔類型
     * 公共 0、私有 1...
     */
    private Integer type;
    /**
     * 文檔标題
     */
    private String title;
    /**
     * 文檔内容
     */
    private String content;
    /**
     * 文檔屬主
     * 公開文檔沒有屬主
     */
    private String owner;
    /**
     * 文檔url
     */
    private String url;
    /**
     * 時間(采集/更新)
     */
    //@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化
    //@JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化
    //private LocalDateTime time;
    private Long time;

    public EsDocument(String id, Integer type, String title, String content, String owner, String url) {
 
        this.id = id;
        this.type = type;
        this.title = title;
        this.content = content;
        this.owner = owner;
        this.url = url;
        //this.time = LocalDateTime.now();
        //this.time = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
        // 目前時刻
        this.time = Instant.now().getEpochSecond();
    }
}           

2.2、文檔VO對象類

package com.tuwer.pojo;

import lombok.Data;

/**
 * <p>文檔視圖層對象</p>
 * @author 土味兒
 * Date 2022/9/17
 * @version 1.0
 */
@Data
public class EsDocVo {
 
    /**
     * 文檔id
     */
    private String id;
    /**
     * 文檔标題
     */
    private String title;
    /**
     * 文檔内容
     */
    private String content;
    /**
     * 文檔url
     */
    private String url;
    /**
     * 時間
     * 1年前、5個月前、3星期前、5天前、8小時前、47分鐘前、剛剛
     */
    private String time;
}           

2.3、分頁對象類 ESPage.java

package com.tuwer.pojo;

import lombok.Data;

import java.util.List;

/**
 * @author 土味兒
 * Date 2022/9/17
 * @version 1.0
 */
@Data
public class EsPage {
 
    private String keyword;
    private Long total;
    private Integer current = 1;
    private Integer pageSize = 10;
    private List<EsDocVo> records;
}           

3、工具類 EsUtil.java

重點類

  • 基礎操作:擷取用戶端(同步/異步)、擷取Transport、close();僅供内部調用
  • 索引操作類:封裝在内部類 Index 中,crud 操作
  • 文檔操作類:封裝在内部類 Doc 中,crud 操作,分頁、高亮…
package com.tuwer.util;

import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.ExistsRequest;
import co.elastic.clients.elasticsearch.core.bulk.*;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.tuwer.pojo.EsDocVo;
import com.tuwer.pojo.EsDocument;
import com.tuwer.pojo.EsPage;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>ES操作工具類</p>
 * -----------------------------
 * 調用異步用戶端就是異步方法;預設異步
 * 索引名稱/Id:一律轉為小寫
 * 文檔Id:可以為大寫,無須轉換
 * -----------------------------
 *
 * @author 土味兒
 * Date 2022/8/9
 * @version 1.0
 */
@Slf4j
@SuppressWarnings("all")
@Component
public class EsUtil {
 
    public Index index = new Index();
    public Doc doc = new Doc();

    // ===================== 索引操作(封裝在Index内部類中) ============================

    public class Index {
 
        /**
         * 建立索引(同步)
         *
         * @param indexName
         * @return true:成功,false:失敗
         */
        @SneakyThrows
        public Boolean createSync(String indexName) {
 
            // 索引名稱轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);
            //String iName = indexName;

            // 擷取【索引用戶端對象】
            ElasticsearchIndicesClient indexClient = getEsClient().indices();

            /**
             * ===== 判斷目标索引是否存在(等價于下面的Lambda寫法)=====
             * ---- 1、建構【存在請求對象】
             * ExistsRequest existsRequest = new ExistsRequest.Builder().index(indexName).build();
             * ---- 2、判斷目标索引是否存在
             * boolean flag = indexClient.exists(existsRequest).value();
             */
            boolean flag = indexClient.exists(req -> req.index(iName)).value();

            //CreateIndexResponse createIndexResponse = null;
            boolean result = false;
            if (flag) {
 
                // 目标索引已存在
                //System.out.println("索引【" + indexName + "】已存在!");
                log.info("索引【" + iName + "】已存在!");
            } else {
 
                // 不存在
                // 擷取【建立索引請求對象】
                //CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder().index(indexName).build();
                // 建立索引,得到【建立索引響應對象】
                //CreateIndexResponse createIndexResponse = indexClient.create(createIndexRequest);
                //createIndexResponse = indexClient.create(req -> req.index(indexName));
                result = indexClient.create(req -> req.index(iName)).acknowledged();

                //System.out.println("建立索引響應對象:" + createIndexResponse);
                if (result) {
 
                    log.info("索引【" + iName + "】建立成功!");
                } else {
 
                    log.info("索引【" + iName + "】建立失敗!");
                }
            }

            // 關閉transport
            close();

            return result;
        }

        /**
         * 建立索引(異步)
         *
         * @param indexName
         * @return
         */
        @SneakyThrows
        public Boolean create(String indexName) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);
            // 異步索引用戶端
            ElasticsearchIndicesAsyncClient indexClient = getEsAsyncClient().indices();

            // 查詢索引是否存在;get()方法阻塞
            boolean isExist = indexClient.exists(
                    existsRequest -> existsRequest.index(iName)
            ).get().value();

            // 建立索引
            boolean result = false;
            if (isExist) {
 
                log.info("索引【" + iName + "】已存在!");
            } else {
 
                // 目前索引不存在,建立索引
                result = indexClient.create(
                        createIndexRequest -> createIndexRequest.index(iName)
                ).get().acknowledged();

                if (result) {
 
                    log.info("索引【" + iName + "】建立成功!");
                } else {
 
                    log.info("索引【" + iName + "】建立失敗!");
                }
            }

            return result;
        }

        /**
         * 查詢索引(同步)
         *
         * @param indexName
         * @return
         */
        @SneakyThrows
        public Map<String, IndexState> querySync(String indexName) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);
            // 擷取【索引用戶端對象】
            ElasticsearchIndicesClient indexClient = getEsClient().indices();

            // 查詢結果;得到【查詢索引響應對象】
            GetIndexRequest getIndexRequest = new GetIndexRequest.Builder().index(iName).build();
            //GetIndexRequest getIndexRequest = new GetIndexRequest.Builder().index("*").build();

            GetIndexResponse getIndexResponse = indexClient.get(getIndexRequest);
            //GetIndexResponse getIndexResponse = indexClient.get(req -> req.index(iName));

            // 關閉transport
            close();

            return getIndexResponse.result();
        }

        /**
         * 查詢索引(異步)
         *
         * @param indexName
         * @return
         */
        @SneakyThrows
        public Map<String, IndexState> query(String indexName) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);
            //getEsAsyncClient().indices().get()
            return getEsAsyncClient().indices().get(req -> req.index(iName)).get().result();
        }

        /**
         * 查詢全部索引
         *
         * @return 索引名稱 Set 集合
         */
        @SneakyThrows
        public Set<String> all() {
 
            GetIndexResponse getIndexResponse = getEsAsyncClient().indices().get(req -> req.index("*")).get();

            return getIndexResponse.result().keySet();
        }

        /**
         * 删除索引
         *
         * @param indexName
         * @return
         */
        @SneakyThrows
        public Boolean del(String indexName) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);
            // 擷取【索引用戶端對象】
            //ElasticsearchIndicesClient indexClient = getEsClient().indices();
            // 【删除索引響應對象】
            //DeleteIndexResponse deleteIndexResponse = getEsClient().indices().delete(req -> req.index(iName));
            DeleteIndexResponse deleteIndexResponse = getEsAsyncClient().indices().delete(req -> req.index(iName)).get();

            // 關閉transport
            //close();

            return deleteIndexResponse.acknowledged();
        }
    }

    // ===================== 文檔異步操作(封裝在Doc内部類中) ============================

    public class Doc {
 
        /**
         * 建立/更新文檔(異步)
         * 存在:
         *
         * @param indexName  索引名稱
         * @param documentId 文檔ID
         * @param esDocument 文檔内容
         * @return 不存在:created、存在:updated
         */
        @SneakyThrows
        public String createOrUpdate(
                String indexName,
                String docId,
                EsDocument esDocument
        ) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);

            // 可建立/可更新
            IndexRequest<EsDocument> indexRequest = new IndexRequest.Builder<EsDocument>()
                    .index(iName)
                    .id(docId)
                    .document(esDocument)
                    .build();

            // 不存在:created、存在:updated
            return getEsAsyncClient().index(indexRequest).get().result().jsonValue();
        }

        /**
         * 批量建立/更新文檔(異步)
         * 存在就更新,不存在就建立
         *
         * @param indexName 索引名稱
         * @param userMap   文檔Map,格式:(文檔id : 文檔)
         * @return 成功操作的數量
         */
        @SneakyThrows
        public Integer createOrUpdateBth(
                String indexName,
                Map<String, EsDocument> userMap
        ) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);

            // 批量操作對象集合
            List<BulkOperation> bs = new ArrayList<>();

            // 建構【批量操作對象】,并裝入list集合中
            userMap.entrySet().stream().forEach(userEntry -> {
 
                // 操作對象(可建立/可更新)
                IndexOperation<EsDocument> idxOpe = new IndexOperation.Builder<EsDocument>()
                        // 文檔id
                        .id(userEntry.getKey())
                        // 文檔内容
                        .document(userEntry.getValue())
                        .build();

                // 建構【批量操作對象】
                BulkOperation opt = new BulkOperation.Builder().index(idxOpe).build();
                // 裝入list集合
                bs.add(opt);
            });

            // 建構【批理請求對象】
            BulkRequest bulkRequest = new BulkRequest.Builder()
                    // 索引
                    .index(iName)
                    // 批量操作對象集合
                    .operations(bs)
                    .build();

            // 批量操作
            BulkResponse bulkResponse = getEsAsyncClient().bulk(bulkRequest).get();

            int i = bulkResponse.items().size();

            log.info("成功處理 {} 份文檔!", i);

            return i;
        }

        /**
         * 檢測文檔是否存在
         *
         * @param indexName
         * @param documentId
         * @return
         */
        @SneakyThrows
        public Boolean isExist(
                String indexName,
                String docId
        ) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);

            ExistsRequest existsRequest = new ExistsRequest.Builder()
                    .index(iName)
                    .id(docId)
                    .build();
            return getEsAsyncClient().exists(existsRequest).get().value();
        }

        /**
         * 查詢距離目前最近的文檔的時間值
         *
         * @param indexName
         * @return
         */
        @SneakyThrows
        public Long lastTime(String indexName) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);

            // 排序字段規則
            FieldSort fs = new FieldSort.Builder()
                    .field("time")
                    .order(SortOrder.Desc)
                    .build();

            // 排序操作項
            SortOptions so = new SortOptions.Builder()
                    .field(fs)
                    .build();

            // 查詢請求對象
            SearchRequest searchRequest = new SearchRequest.Builder()
                    .index(iName)
                    // 可以接收多個值
                    .sort(so)
                    .size(1).build();

            // 異步查詢
            SearchResponse<EsDocument> response = getEsAsyncClient().search(searchRequest, EsDocument.class).get();

            // 結果集
            List<Hit<EsDocument>> hits = response.hits().hits();

            // 時間最近的文檔
            EsDocument doc = hits.stream().findFirst().get().source();

            // 傳回時間值(秒)
            return doc.getTime();
        }

        /**
         * 根據關鍵字查文檔
         * ---------------------------
         * 隻要标題和内容中有一個比對即可
         * ---------------------------
         *
         * @param indexName 索引名稱
         * @param keyword   關鍵字
         * @return List 集合
         */
        @SneakyThrows
        public List<EsDocVo> query(
                String indexName,
                String keyword
        ) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);

        /*MatchQuery matchQuery = new MatchQuery.Builder()
                .field(fieldName)
                .query(fieldValue)
                .build();

        Query query = new Query.Builder()
                .match(matchQuery)
                .build();

        //SearchRequest searchRequest = new SearchRequest.Builder().index(indexName).query(query).build();
        SearchRequest searchRequest = new SearchRequest.Builder().index(indexName).query(query).build();

        SearchResponse<EsDocument> searchResponse = getEsClient().search(searchRequest, EsDocument.class);
        */

            // ---------------- lambda表達式寫法(嵌套搜尋查詢)------------------

            // 标題中查找
            Query byTitle = MatchQuery.of(m -> m
                    // EsDocument的标題字段名
                    .field("title")
                    .query(keyword)
            )._toQuery();

            // 内容中查找
            Query byContent = MatchQuery.of(m -> m
                    // EsDocument的内容字段名
                    .field("content")
                    .query(keyword)
            )._toQuery();

            // 異步
            SearchResponse<EsDocument> response = getEsAsyncClient().search(s -> s
                            .index(iName)
                            .query(q -> q
                                    // boolean 嵌套搜尋;must需同時滿足,should一個滿足即可
                                    .bool(b -> b
                                            //
                                            //.must(byTitle )
                                            //.must(byContent )
                                            .should(byTitle)
                                            .should(byContent)
                                    )
                            ),
                    EsDocument.class
            ).get();

            List<Hit<EsDocument>> hits = response.hits().hits();
            // 轉為 List<EsDocument>
            //List<EsDocument> docs = hits.stream().map(hit -> hit.source()).collect(Collectors.toList());
            //List<EsDocument> docs = hits.stream().map(Hit::source).collect(Collectors.toList());
            List<EsDocVo> docs = hits.stream().map(hit -> getEsDocVo(hit.source())).collect(Collectors.toList());

            // 關閉transport
            //close();

            return docs;
        }

        /**
         * 【分頁查找】根據關鍵字查文檔
         * ---------------------------
         * 隻要标題和内容中有一個比對即可
         * 預設目前頁:1
         * 預設頁面記錄數:10
         * 支援高亮
         * ---------------------------
         *
         * @param indexName 索引名稱
         * @param keyword   關鍵字
         * @return List 集合
         */
        @SneakyThrows
        public EsPage page(
                String indexName,
                String keyword
        ) {
 
            return page(indexName, keyword, 1, 10);
        }

        /**
         * 【分頁查找】根據關鍵字查文檔
         * ---------------------------
         * 隻要标題和内容中有一個比對即可
         * 支援高亮
         * ---------------------------
         *
         * @param indexName 索引名稱
         * @param keyword   關鍵字
         * @param current   目前頁
         * @param pageSize  頁面記錄數
         * @return EsPage 對象
         */
        @SneakyThrows
        public EsPage page(
                String indexName,
                String keyword,
                Integer current,
                Integer pageSize
        ) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);

        /*MatchQuery matchQuery = new MatchQuery.Builder()
                .field(fieldName)
                .query(fieldValue)
                .build();

        Query query = new Query.Builder()
                .match(matchQuery)
                .build();

        //SearchRequest searchRequest = new SearchRequest.Builder().index(indexName).query(query).build();
        SearchRequest searchRequest = new SearchRequest.Builder().index(indexName).query(query).build();

        SearchResponse<EsDocument> searchResponse = getEsClient().search(searchRequest, EsDocument.class);
        */

            // ---------------- lambda表達式寫法(嵌套搜尋查詢)------------------

            // 多條件查詢(從title或content中查詢keyword)
            Query byKeyword = MultiMatchQuery.of(m -> m
                    .fields("title", "content")
                    //.fields("title")
                    .query(keyword)
            )._toQuery();

            // 起始文檔值(從0開始)
            Integer from = (current - 1) * pageSize;

            // 存放高亮的字段,預設與文檔字段一緻
            HighlightField hf = new HighlightField.Builder().build();

            Highlight highlight = new Highlight.Builder()
                    // 前字尾預設就是em,可省略
                    //.preTags("<em>")
                    //.postTags("</em>")
                    .fields("title", new HighlightField.Builder().build())
                    .fields("content", new HighlightField.Builder().build())
                    .requireFieldMatch(false)
                    .build();

            // 異步
            SearchResponse<EsDocument> response = getEsAsyncClient().search(s -> s
                            .index(iName)
                            .query(byKeyword)
                            .highlight(highlight)
                            .from(from).size(pageSize),
                    EsDocument.class
            ).get();

            // 建構EsPage
            EsPage esPage = new EsPage();
            esPage.setKeyword(keyword);
            esPage.setTotal(response.hits().total().value());
            esPage.setCurrent(current);
            esPage.setPageSize(pageSize);

            // 查詢結果
            List<Hit<EsDocument>> hits = response.hits().hits();

            // 文檔VO對象集合;實作高亮
            List<EsDocVo> docs = new ArrayList<>();

            // 流式周遊查詢結果:用高亮字段替換原文檔字段
            hits.stream().forEach(hit -> {
 
                // 原文檔
                EsDocument doc = hit.source();
                // 高亮标題字段
                List<String> titles = hit.highlight().get("title");
                if(!CollectionUtils.isEmpty(titles)){
 
                    // 替換原标題
                    doc.setTitle(titles.get(0));
                }
                // 高亮内容字段
                List<String> contents = hit.highlight().get("content");
                if(!CollectionUtils.isEmpty(contents)){
 
                    // 替換原内容
                    doc.setContent(contents.get(0));
                }

                // 原文檔轉為VO,加入VO對象集合中
                docs.add(getEsDocVo(doc));
            });

            // VO對象集合注入page對象
            esPage.setRecords(docs);

            // 關閉transport
            //close();

            // 傳回page
            return esPage;
        }

        /**
         * 【分頁查找】根據屬主、文檔類型、關鍵字查文檔
         * 支援高亮
         * ---------------------------
         * 1、公共文檔:類型 0;任何人都可以查詢,不需要比對屬主
         * 2、非公共文檔:類型 1、2、3.;有限制查詢,隻有文檔屬主可以查詢;如:tom的文檔,隻有tom可以查詢
         * 3、關鍵字:隻要标題和内容中有一個比對即可
         * ---------------------------
         * 查詢中文與英文的匹别:
         * 1、中文:單個漢字為一個詞;如:中國,可以分為:中、國,有一個比對上就算成功
         * 2、英文:一個單詞為一個詞;
         * ---------------------------
         * 注意:
         * 屬主名稱選擇時,不要用中文,全部用英文,且有固定格式,不可修改
         * ---------------------------
         *
         * @param indexName 索引名稱
         * @param keyword   關鍵字
         * @param owner     文檔屬主
         * @param current   目前頁
         * @param pageSize  頁面記錄數
         * @return EsPage 對象
         */
        @SneakyThrows
        public EsPage page(
                String indexName,
                String keyword,
                String owner,
                Integer current,
                Integer pageSize
        ) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);

            // ---------------- lambda表達式寫法(嵌套搜尋查詢)------------------

            // 多條件查詢(從title或content中查詢keyword)
            Query byKeyword = MultiMatchQuery.of(m -> m
                    .fields("title", "content")
                    .query(keyword)
            )._toQuery();

            // 文檔類型(公共文檔)
            Query byType1 = TermQuery.of(m -> m
                    // EsDocument的内容字段名
                    .field("type")
                    .value(0)
            )._toQuery();

            // 文檔類型(私有文檔)
            Query byType2 = TermQuery.of(m -> m
                    // EsDocument的内容字段名
                    .field("type")
                    .value(1)
            )._toQuery();

            // 文檔屬主(屬主名稱完全比對)
            Query byOwner = TermQuery.of(m -> m
                    // EsDocument的内容字段名
                    .field("owner")
                    .value(owner)
            )._toQuery();

            // 起始文檔值(從0開始)
            Integer from = (current - 1) * pageSize;

            // 存放高亮的字段,預設與文檔字段一緻
            HighlightField hf = new HighlightField.Builder().build();

            Highlight highlight = new Highlight.Builder()
                    // 前字尾預設就是em,可省略
                    //.preTags("<em>")
                    //.postTags("</em>")
                    .fields("title", new HighlightField.Builder().build())
                    .fields("content", new HighlightField.Builder().build())
                    .requireFieldMatch(false)
                    .build();

            // 異步
            SearchResponse<EsDocument> response = getEsAsyncClient().search(s -> s
                            .index(iName)
                            .query(q -> q
                                    // 布爾比較:有一個條件滿足即可
                                    .bool(b -> b
                                            // 條件一:must:兩個子條件都滿足時,條件才成立;【公共文檔】
                                            .should(sq1 -> sq1.bool(sqb1 -> sqb1.must(byType1, byKeyword)))
                                            // 條件二:must:三個子條件都滿足時,條件才成立;【私有文檔】
                                            .should(sq2 -> sq2.bool(sqb2 -> sqb2.must(byType2, byOwner, byKeyword)))
                                    )
                            ).highlight(highlight)
                            .from(from).size(pageSize),
                    EsDocument.class
            ).get();

            // 建構EsPage
            EsPage esPage = new EsPage();
            esPage.setKeyword(keyword);
            esPage.setTotal(response.hits().total().value());
            esPage.setCurrent(current);
            esPage.setPageSize(pageSize);

            // 查詢結果
            List<Hit<EsDocument>> hits = response.hits().hits();

            // 文檔VO對象集合;實作高亮
            List<EsDocVo> docs = new ArrayList<>();

            // 流式周遊查詢結果:用高亮字段替換原文檔字段
            hits.stream().forEach(hit -> {
 
                // 原文檔
                EsDocument doc = hit.source();
                // 高亮标題字段
                List<String> titles = hit.highlight().get("title");
                if(!CollectionUtils.isEmpty(titles)){
 
                    // 替換原标題
                    doc.setTitle(titles.get(0));
                }
                // 高亮内容字段
                List<String> contents = hit.highlight().get("content");
                if(!CollectionUtils.isEmpty(contents)){
 
                    // 替換原内容
                    doc.setContent(contents.get(0));
                }

                // 原文檔轉為VO,加入VO對象集合中
                docs.add(getEsDocVo(doc));
            });

            // VO對象集合注入page對象
            esPage.setRecords(docs);

            // 關閉transport
            //close();

            // 傳回page
            return esPage;
        }

        /**
         * 批量删除文檔
         *
         * @param indexName   索引名稱
         * @param documentIds 文檔ID集合
         * @return 成功删除數量
         */
        @SneakyThrows
        public Integer del(
                String indexName,
                List<String> docIds
        ) {
 
            // 轉為小寫
            String iName = indexName.toLowerCase(Locale.ROOT);

            // 批量操作對象集合
            List<BulkOperation> bs = new ArrayList<>();

            // 建構【批量操作對象】,并裝入list集合中
            docIds.stream().forEach(docId -> {
 
                // 删除操作對象
                DeleteOperation delOpe = new DeleteOperation.Builder().id(docId).build();

                // 建構【批量操作對象】
                BulkOperation opt = new BulkOperation.Builder().delete(delOpe).build();
                // 裝入list集合
                bs.add(opt);
            });

            // 建構【批理請求對象】
            BulkRequest bulkRequest = new BulkRequest.Builder()
                    // 索引
                    .index(iName)
                    // 批量操作對象集合
                    .operations(bs)
                    .build();

            // 批量操作
            BulkResponse bulkResponse = getEsAsyncClient().bulk(bulkRequest).get();

            int i = bulkResponse.items().size();

            log.info("成功處理 {} 份文檔!", i);

            return i;
        }

        /**
         * 删除所有文檔
         * 實際上删除的是索引
         *
         * @param indexName
         * @return
         */
        public Boolean delAll(String indexName) {
 
            return index.del(indexName);
        }

        private EsDocVo getEsDocVo(EsDocument esDocument) {
 
            EsDocVo esDocVo = new EsDocVo();
            esDocVo.setId(esDocument.getId());
            esDocVo.setTitle(esDocument.getTitle());
            esDocVo.setContent(esDocument.getContent());
            esDocVo.setUrl(esDocument.getUrl());

            // ------ 時間轉換 ------
            // 目前時刻
            Long now = Instant.now().getEpochSecond();
            Long n = now - esDocument.getTime();

            // 秒數
            Long secOfMinute = 60L;
            //Long secOfHour = secOfMinute * 60L;
            Long secOfHour = 3600L;
            //Long secOfDay = secOfHour * 24L;
            Long secOfDay = 86400L;
            //Long secOfWeek = secOfDay * 7L;
            Long secOfWeek = 604800L;
            //Long secOfMonth = secOfDay * 30L;
            Long secOfMonth = 2592000L;
            //Long secOfYear = secOfMonth * 12L;
            Long secOfYear = 31104000L;

            if (n > secOfYear) {
 
                Double floor = Math.floor(n / secOfYear);
                esDocVo.setTime(floor.intValue() + "年前");
            } else if (n > secOfMonth) {
 
                Double floor = Math.floor(n / secOfMonth);
                esDocVo.setTime(floor.intValue() + "個月前");
            } else if (n > secOfWeek) {
 
                Double floor = Math.floor(n / secOfWeek);
                esDocVo.setTime(floor.intValue() + "周前");
            } else if (n > secOfDay) {
 
                Double floor = Math.floor(n / secOfDay);
                esDocVo.setTime(floor.intValue() + "天前");
            } else if (n > secOfHour) {
 
                Double floor = Math.floor(n / secOfHour);
                esDocVo.setTime(floor.intValue() + "小時前");
            } else if (n > secOfMinute) {
 
                Double floor = Math.floor(n / secOfMinute);
                esDocVo.setTime(floor.intValue() + "分鐘前");
            } else {
 
                esDocVo.setTime("剛剛");
            }

            return esDocVo;
        }
    }

    // ===================== 基礎操作(僅供内部調用) ============================

    private static ElasticsearchTransport transport;

    /**
     * 同步用戶端;調用結束後,需調用close()關閉transport
     *
     * @return
     */
    private static ElasticsearchClient getEsClient() {
 
        ElasticsearchClient client = new ElasticsearchClient(getEsTransport());
        return client;
    }

    /**
     * 異步用戶端
     *
     * @return
     */
    private static ElasticsearchAsyncClient getEsAsyncClient() {
 
        ElasticsearchAsyncClient asyncClient =
                new ElasticsearchAsyncClient(getEsTransport());
        return asyncClient;
    }

    /**
     * 擷取Transport
     *
     * @return
     */
    private static ElasticsearchTransport getEsTransport() {
 
        RestClient restClient = RestClient.builder(
                new HttpHost("localhost", 9200)).build();

        // Create the transport with a Jackson mapper
        transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper());

        return transport;
    }

    /**
     * 關閉transport
     */
    private static void close() {
 
        if (transport != null) {
 
            try {
 
                transport.close();
            } catch (IOException e) {
 
                e.printStackTrace();
            }
        }
    }
}           

4、測試類

package com.tuwer;

import co.elastic.clients.elasticsearch.indices.*;
import com.tuwer.pojo.EsDocVo;
import com.tuwer.pojo.EsDocument;
import com.tuwer.pojo.EsPage;
import com.tuwer.util.EsUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resources;
import java.util.*;

/**
 * @author 土味兒
 * Date 2022/8/9
 * @version 1.0
 */
@SpringBootTest
public class MyTest {
 
    @Autowired
    EsUtil esUtil;

    // 目标索引
    String indexName = "tuwer_index001";
    

    // --------------------------- 工具類方法 ---------------------------------
    // -----索引-----
    @Test
    public void testCreateIndexByUtil() {
 
        //System.out.println(EsClientUtil.createIndex(indexName));
        //EsClientUtil.createIndex("INDEX_abc");
        esUtil.index.create("INDEX_abc123");
    }

    @Test
    public void testQueryIndexByUtil() {
 
        Map<String, IndexState> result = esUtil.index.query("tuwer_index");
        //Map<String, IndexState> result = EsClientUtil.indexQueryAsync("tuwer_index");
/*        for (Map.Entry<String, IndexState> entry : result.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }*/
        for (String s : result.keySet()) {
 
            System.out.println(result.get(s).dataStream());
        }
    }

    @Test
    public void testGetAllIndex(){
 
        Set<String> idxs = esUtil.index.all();
        for (String idx : idxs) {
 
            System.out.println(idx);
        }
    }

    @Test
    public void testDeleteIndexByUtil() {
 
        boolean b = esUtil.index.del("tuwer_index001");
        System.out.println(b);
    }

    // -----文檔-----
    @Test
    public void testCreateDocument() {
 
        EsDocument esDocument = new EsDocument("123",0,"标題","測試123","admin","abc123");
        String res = esUtil.doc.createOrUpdate(indexName, "ABC", esDocument);
        System.out.println(res);
    }

    @Test
    public void testBatchCreateDocument() {
 
        Map<String, EsDocument> userMap = new HashMap<>();
        //for (int i = 0; i < 3; i++) {
 
        EsDocument doc1 = new EsDocument("11",0,"中","沒123世213界人","","abc123");
        userMap.put(doc1.getId(), doc1);
        EsDocument doc2 = new EsDocument("12",0,"世","河231人測123南測試中","","abc123");
        userMap.put(doc2.getId(), doc2);
        EsDocument doc3 = new EsDocument("13",0,"原中","天大1231去131南","","abc123");
        userMap.put(doc3.getId(), doc3);

        EsDocument doc4 = new EsDocument("21",1,"中","沒123世213界人","admin","abc123");
        userMap.put(doc4.getId(), doc4);
        EsDocument doc5 = new EsDocument("22",1,"世","河231人測123南測試中","34admin","abc123");
        userMap.put(doc5.getId(), doc5);
        EsDocument doc6 = new EsDocument("23",1,"原中","天大1231去131南","admin67","abc123");
        userMap.put(doc6.getId(), doc6);
        //}

        int i  = esUtil.doc.createOrUpdateBth(indexName, userMap);
        /*for (BulkResponseItem item : bulkResponse.items()) {
            System.out.println(item.id());
        }*/
        System.out.println(i);
    }

    @Test
    public void testDocIsExist(){
 
        //System.out.println(EsClientUtil.docIsExist(indexName, "8001"));
        System.out.println(esUtil.doc.isExist("tuwer_IndeX001", "8001"));
    }

    @Test
    public void testDeleteDocument() {
 
        List<String> documentIds = new ArrayList<>();
        documentIds.add("101");
        documentIds.add("102");
        documentIds.add("100");
        documentIds.add("201");
        documentIds.add("202");
        documentIds.add("203");
        documentIds.add("ABC");
        documentIds.add("_search");

        int i = esUtil.doc.del(indexName, documentIds);
        System.out.println(i);
    }

    @Test
    public void testDocDelAll(){
 
        esUtil.doc.delAll(indexName);
    }

    @Test
    public void testQueryDocument() {
 
        List<EsDocVo> docs = esUtil.doc.query(indexName, "中");
        //List<Hit<User>> users = EsClientUtil.queryDocumentByField(indexName, "name", "test_6001");

        for (EsDocVo doc : docs) {
 
            System.out.println(doc);
        }
    }

    @Test
    public void testDocPage(){
 
        //EsPage p = esUtil.doc.page(indexName, "中", 1, 5);
        EsPage p = esUtil.doc.page(indexName, "世", 1, 20);
        //esUtil.doc.page(indexName, "中", 1, 20);
        //EsPage p = esUtil.doc.page(indexName, "世", "admin67",1, 20);
        //EsPage p = esUtil.doc.page(indexName, "中");
        //System.out.println(p);
        System.out.println("--------------");
        for (EsDocVo record : p.getRecords()) {
 
            System.out.println(record);
        }
    }

    @Test
    public void testDocLastTime(){
 
        //EsPage p = esUtil.doc.page(indexName, "中", 1, 5);
        //EsPage p = esUtil.doc.page(indexName, "中", "admin",1, 5);
        //EsPage p = esUtil.doc.page(indexName, "中");
        Long lastTime = esUtil.doc.lastTime(indexName);
        System.out.println(lastTime);
    }
}           

繼續閱讀