自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深度探索:Spring 借助 Easy - Es 開啟 ElasticSearch 操作實(shí)戰(zhàn)篇章

開發(fā)
本文將 Easy-Es 基本集成與文檔和索引的基礎(chǔ)操作都進(jìn)行的比較詳盡的演示,希望對(duì)你有幫助。

這篇文章原本是采用spring-boot-starter-data-elasticsearch演示如何在spring boot項(xiàng)目中使用es,經(jīng)一個(gè)讀者的建議打算將文章加以重構(gòu),改用一個(gè)更強(qiáng)大號(hào)稱傻瓜級(jí)ElasticSearch搜索引擎ORM框架Easy-Es,像操作MP一樣操作ES。

需要補(bǔ)充的是,在編寫這篇文章之前,筆者對(duì)Easy-Es文檔進(jìn)行相對(duì)詳細(xì)的閱讀,個(gè)人認(rèn)為Easy-Es1.0版本在實(shí)際項(xiàng)目中的集成和使用相對(duì)穩(wěn)定一些,所以本文將以Easy-Es1.0版本展開演示,所以為保證后續(xù)集成步驟的順利建議讀者采用2.5.x +版本的Spring Boot(筆者直接使用2.6.0)。

詳解Easy-Es集成與操作

1. 集成Easy-Es與創(chuàng)建索引

集成Easy-Es的時(shí)首先自然是引入相關(guān)依賴,以筆者為例,項(xiàng)目中引用的版本就是1.1.1版本:

<dependency>
            <groupId>cn.easy-es</groupId>
            <artifactId>easy-es-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>

Easy-Es默認(rèn)情況下會(huì)掃描我們的文檔實(shí)體完成索引創(chuàng)建,所以我們就可以直接聲明文檔的實(shí)體類型即直接使用,以本文為例,筆者創(chuàng)建的測(cè)試文檔包含id、標(biāo)題、內(nèi)容幾個(gè)字段,因?yàn)楸景咐嘤脙?nèi)容的檢索且文本內(nèi)容多是中文,所以在進(jìn)行字段設(shè)計(jì)的時(shí)候針對(duì)內(nèi)容字段嘗試將其設(shè)置為text類型,并將索引文檔時(shí)用的分詞器設(shè)置為ik_max_word以保證切出盡可能多的詞項(xiàng)提升檢索相關(guān)性數(shù)據(jù)的概率,同時(shí)指明搜索分詞器為ik_smart以保證檢索詞匯盡可能少切割得到最相關(guān)的結(jié)果:

對(duì)應(yīng)的我們給出這段代碼示例,默認(rèn)情況下Easy-Es會(huì)將所有的字符串類型設(shè)置為keyword,由于內(nèi)容字段的特殊性,筆者通過(guò)IndexField指明索引和搜索的分詞器以達(dá)到上述效果:

@Data
public class Document {
    /**
     * es中的唯一id
     */
    private String id;
    /**
     * 文檔標(biāo)題
     */
    private String title;
    /**
     * 文檔內(nèi)容
     */
    @IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_MAX_WORD, searchAnalyzer = Analyzer.IK_SMART)
    private String content;
}

基于文檔的實(shí)體類,編寫Es持久層mapper,和MP類似通過(guò)繼承BaseEsMapper獲得文檔操作的所有能力并通過(guò)泛型指明操作的文檔類型為Document:

public interface DocumentMapper extends BaseEsMapper<Document> {
}

完成上述操作后,在啟動(dòng)器上注明mapper的全路徑開啟自動(dòng)掃描注入:

@EsMapperScan("com.sharkChili.mapper")

完成上述配置之后將項(xiàng)目啟動(dòng),如果輸出Congratulations auto process index by Easy-Es is done !則說(shuō)明文檔自動(dòng)創(chuàng)建完成,此時(shí)我們就可以開始基本操作了:

當(dāng)然Easy-Es也支持顯示的創(chuàng)建和刪除索引,需要注意1.x版本使用的模式是平滑模式回基于原有索引進(jìn)行遷移,如果我們希望手動(dòng)創(chuàng)建索引可以將模式改為手動(dòng)模式:

easy-es.global-config.process_index_mode: manual

這里我們也直接給出使用示例:

Boolean createRes = documentMapper.createIndex();
        Boolean delRes = documentMapper.deleteIndex("document");

2. 插入數(shù)據(jù)

對(duì)應(yīng)我們也給出一份插入的基礎(chǔ)使用示例,如下所示可以看到操作步驟也只是聲明一下待插入文檔的實(shí)體然后調(diào)用insert即可完成插入:

Document document = new Document();
        document.setTitle("測(cè)試標(biāo)題");
        document.setContent("測(cè)試的文本內(nèi)容");
        Integer count = documentMapper.insert(document);
        log.info("count:{}", count);

在用戶使用的角度,看起來(lái)像是操作MP一樣,實(shí)際上在執(zhí)行insert方法時(shí),Easy-Es底層也是和Mybatis類似,用到動(dòng)態(tài)代理的機(jī)制,通過(guò)掃描實(shí)體類信息獲得索引名稱,然后構(gòu)建restful風(fēng)格的API請(qǐng)求執(zhí)行文檔插入,完成后直接將生成的id結(jié)果設(shè)置到組裝實(shí)體中,并返回操作成功數(shù):

我們可以直接從BaseEsMapperImpl的insert方法的源碼中看到實(shí)現(xiàn),它首先會(huì)基于實(shí)體類調(diào)用getIndexName獲得索引名稱,然后調(diào)用insert執(zhí)行當(dāng)前文檔的插入工作:

@Override
    public Integer insert(T entity) {
        //......
        //基于實(shí)體獲得索引名稱后調(diào)用insert進(jìn)行插入
        return insert(entity, EntityInfoHelper.getEntityInfo(entityClass).getIndexName());
    }

不入其內(nèi)部即可看到基于我們的實(shí)體信息構(gòu)建restful api的入?yún)ⅲㄟ^(guò)Easy-Es聚合的原生RestHighLevelClient發(fā)送POST請(qǐng)求提交文檔,如果成功則將文檔的id賦值到實(shí)體上返回給用戶:

private Integer doInsert(T entity, String indexName) {
        // 基于實(shí)體構(gòu)建請(qǐng)求入?yún)?        IndexRequest indexRequest = buildIndexRequest(entity, indexName);
        indexRequest.setRefreshPolicy(getRefreshPolicy());

        try {
         //發(fā)送POST請(qǐng)求插入文檔
            IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
            //如果插入成功則將返回的id值賦值到傳入的實(shí)體上
            if (Objects.equals(indexResponse.status(), RestStatus.CREATED)) {
                setId(entity, indexResponse.getId());
                return BaseEsConstants.ONE;
            } else if (Objects.equals(indexResponse.status(), RestStatus.OK)) {
                // 該id已存在,數(shù)據(jù)被更新的情況
                return BaseEsConstants.ZERO;
            } else {
                        //......
            }
        } catch (IOException e) {
         //......
        }
    }

3. 查詢數(shù)據(jù)

上文提到字符串類型默認(rèn)情況下是keyword類型,所以title字段查出是精準(zhǔn)匹配的,對(duì)應(yīng)的查詢組裝如下所示通過(guò)LambdaEsQueryWrapper的eq函數(shù)指明等值查詢,檢索一條標(biāo)題為測(cè)試標(biāo)題,最終就可以將上一步插入操作的文檔返回:

String title = "測(cè)試標(biāo)題";
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        //底層走must term查詢
        wrapper.eq(Document::getTitle, title);

        Document document = documentMapper.selectOne(wrapper);
        log.info(JSONUtil.toJsonStr(document));

這里我們查看eq函數(shù)的底層實(shí)現(xiàn)可以看到實(shí)現(xiàn)精準(zhǔn)匹配本質(zhì)上就是通過(guò)must查詢term為測(cè)試標(biāo)題的文檔:

@Override
    public Children eq(boolean condition, String column, Object val, Float boost) {
        return doIt(condition, TERM_QUERY, MUST, column, val, boost);
    }

其底層操作邏輯和插入操作整體步驟是差不多的,即通過(guò)代理構(gòu)建restful api發(fā)起請(qǐng)求并將結(jié)果映射為java bean返回,這里我們就貼出selectOne操作底層的核心實(shí)現(xiàn),即位于BaseEsMapperImpl的getSearchResponse方法,它就是會(huì)基于我們的參數(shù)調(diào)用search接口,并將響應(yīng)結(jié)果返回給上層組裝成實(shí)體對(duì)象給用戶:

private SearchResponse getSearchResponse(LambdaEsQueryWrapper<T> wrapper, Object[] searchAfter) {
        // 構(gòu)建es restHighLevelClient 查詢參數(shù)
        SearchRequest searchRequest = new SearchRequest(getIndexNames(wrapper.indexNames));

        // 用戶在wrapper中指定的混合查詢條件優(yōu)先級(jí)最高
        SearchSourceBuilder searchSourceBuilder = Optional.ofNullable(wrapper.searchSourceBuilder)
                .map(builder -> {
                    // 兼容混合查詢時(shí)用戶在分頁(yè)中自定義的分頁(yè)參數(shù)
                    Optional.ofNullable(wrapper.from).ifPresent(builder::from);
                    Optional.ofNullable(wrapper.size).ifPresent(builder::size);
                    return builder;
                }).orElseGet(() -> 
                //基于我們的wrapper構(gòu)建出請(qǐng)求入?yún)?                WrapperProcessor.buildSearchSourceBuilder(wrapper, entityClass));
         //......

        // 執(zhí)行查詢
        SearchResponse response;
        try {
            response = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw ExceptionUtils.eee("search exception", e);
        }
        //將結(jié)果返回
        printResponseErrors(response);
        return response;
    }

源碼邏輯實(shí)現(xiàn)如上所示,這里我們就看看document文檔底層代理基于我們的入?yún)⑺鶚?gòu)建的請(qǐng)求參數(shù)searchSourceBuilder ,很明顯就是一個(gè)典型的restful api參數(shù):

對(duì)于自然語(yǔ)言處理的文本檢索,也就是match查詢,Easy-Es也做了很好的封裝,對(duì)應(yīng)的使用示例如下所示:

LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.match(Document::getContent, "你好,這是 elasticsearch操作教程");
        List<Document> documentList = documentMapper.selectList(wrapper);
        if (CollUtil.isNotEmpty(documentList)) {
            log.info("size:{}", documentList.size());
            log.info("first data:{}", JSONUtil.toJsonStr(documentList.get(0)));
        }

4. 更新和刪除數(shù)據(jù)

有了上述的基礎(chǔ),對(duì)于更新操作等操作都比較好理解了,這里我們直接貼出基于id更新操作的使用示例也是類似于主流ORM框架Mybatis,讀者可參考源碼了解使用步驟:

String id = "HVWfjpQBtr9x3QfTu299";
        Document updateDocument = new Document();
        updateDocument.setId(id);
        updateDocument.setContent("修改后的文本內(nèi)容");
        Integer count = documentMapper.updateById(updateDocument);
        log.info("update count:{}", count);

刪除操作同理,這里就不多做贅述了:

LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        String title = "測(cè)試標(biāo)題";
        wrapper.eq(Document::getTitle, title);
        int successCount = documentMapper.delete(wrapper);
        log.info("delete count:{}", successCount);

5. 分頁(yè)查詢

和Mybatis-Plus類似,Easy-Es也針對(duì)分頁(yè)查詢做了很好的封裝,使用時(shí)我們也僅需指定頁(yè)碼和頁(yè)數(shù)即可完成查詢:

LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.match(Document::getContent, "你好,這是 elasticsearch操作教程");
        EsPageInfo<Document> documentPageInfo = documentMapper.pageQuery(wrapper, 1, 10);
        log.info("query res:{}", documentPageInfo.toString());

從分頁(yè)查詢的API即pageQuery可以看到該查詢本質(zhì)上也是服用了BaseEsMapperImpl的getSearchResponse方法,底層回基于我們傳入的參數(shù)封裝from和size并發(fā)送HTTP請(qǐng)求獲取分頁(yè)結(jié)果:

private SearchResponse getSearchResponse(LambdaEsQueryWrapper<T> wrapper, Object[] searchAfter) {
        // 構(gòu)建es restHighLevelClient 查詢參數(shù)
        SearchRequest searchRequest = new SearchRequest(getIndexNames(wrapper.indexNames));

        // 用戶在wrapper中指定的混合查詢條件優(yōu)先級(jí)最高
        SearchSourceBuilder searchSourceBuilder = Optional.ofNullable(wrapper.searchSourceBuilder)
                .map(builder -> {
                    // 兼容混合查詢時(shí)用戶在分頁(yè)中自定義的分頁(yè)參數(shù),基于我們傳參的wrapper得到頁(yè)數(shù)和頁(yè)碼構(gòu)建from和size參數(shù)
                    Optional.ofNullable(wrapper.from).ifPresent(builder::from);
                    Optional.ofNullable(wrapper.size).ifPresent(builder::size);
                    return builder;
                }).orElseGet(() -> WrapperProcessor.buildSearchSourceBuilder(wrapper, entityClass));
       //......

        // 執(zhí)行查詢
        SearchResponse response;
        try {
            response = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw ExceptionUtils.eee("search exception", e);
        }
        //......
        return response;
    }

按照ES官方的說(shuō)法,默認(rèn)情況下超過(guò)10000條之后的數(shù)據(jù),from-size查詢是不允許的,原因是避免多分片歸并聚合所導(dǎo)致的OOM問(wèn)題,所以對(duì)于深分頁(yè),ES官方是推薦采用search_after:https://www.elastic.co/guide/en/elasticsearch/reference/8.3/paginate-search-results.html#search-after

對(duì)此我們也給出searchAfter 的使用示例:

LambdaEsQueryWrapper<Document> lambdaEsQueryWrapper = EsWrappers.lambdaQuery(Document.class);
        lambdaEsQueryWrapper.size(10);
        // 必須指定一種排序規(guī)則,且排序字段值必須唯一 此處我選擇用id進(jìn)行排序 實(shí)際可根據(jù)業(yè)務(wù)場(chǎng)景自由指定,不推薦用創(chuàng)建時(shí)間,因?yàn)榭赡軙?huì)相同
        lambdaEsQueryWrapper.orderByDesc(Document::getId);
        SAPageInfo<Document> saPageInfo = documentMapper.searchAfterPage(lambdaEsQueryWrapper, null, 10);
        // 第一頁(yè)
        log.info("first page:{}", saPageInfo);

        // 獲取下一頁(yè)
        List<Object> nextSearchAfter = saPageInfo.getNextSearchAfter();
        SAPageInfo<Document> next = documentMapper.searchAfterPage(lambdaEsQueryWrapper, nextSearchAfter, 10);
        log.info("second page:{}", next);
責(zé)任編輯:趙寧寧 來(lái)源: 寫代碼的SharkChili
相關(guān)推薦

2010-05-20 13:19:35

GoogleSpringVMware

2016-12-09 13:45:21

RNN大數(shù)據(jù)深度學(xué)習(xí)

2023-08-02 07:21:30

工具搜索排序

2023-06-13 08:00:57

ChatGPT語(yǔ)言模型

2025-04-03 00:03:00

數(shù)據(jù)內(nèi)存網(wǎng)絡(luò)

2018-11-02 15:45:41

Spring BootRedis數(shù)據(jù)庫(kù)

2024-11-11 10:02:37

Spring搜索數(shù)據(jù)

2020-10-23 09:03:28

Flask

2010-08-24 10:07:48

IMOS Inside安防監(jiān)控H3C

2022-11-28 08:37:03

2019-05-21 14:33:01

2009-06-15 16:05:30

設(shè)計(jì)AnnotatioJava

2021-07-02 10:10:55

SecurityJWT系統(tǒng)

2022-10-14 07:42:50

LuceneHTTPWeb

2021-11-19 11:25:45

網(wǎng)絡(luò)安全

2023-11-29 08:35:28

群多租戶ES運(yùn)維
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)