SpringBoot集成Elasticsearch 實(shí)戰(zhàn)(1)

1. 目錄

20201230114440

2. SpringBoot集成

開(kāi)發(fā)工具,這里選擇的是IDEA 2021.1.2,構(gòu)建 Gradle 工程等一堆通用操作,不清楚的自行百度 或者 參看 90分鐘玩轉(zhuǎn)Gradle

2.1. 依賴配置

我這邊選擇 spring-boot-starter-data-elasticsearch 方式來(lái)集成 spring-boot 中集成的版本號(hào)與實(shí)際安裝版本號(hào)的差異,盡量選擇一致的版本,否則在集成過(guò)程中,會(huì)有莫名的問(wèn)題。讀者在選擇的時(shí)候多加留意。


api("org.springframework.boot:spring-boot-starter-data-elasticsearch")

我在此基礎(chǔ)上封裝一層 persistence-elasticsearch,更貼近一般項(xiàng)目使用。

  • 中央倉(cāng)庫(kù)下載
中央倉(cāng)庫(kù)
  • 阿里云的倉(cāng)庫(kù)下載
20211227173753

2.2. 核心操作類

為了規(guī)范索引管理,這里將所有的操作都封裝成一個(gè)基類,實(shí)現(xiàn)對(duì)索引的增刪改查。同時(shí)還集成了對(duì)數(shù)據(jù)的單個(gè)以及批量的插入以及刪除。避免針對(duì)每個(gè)索引都自己寫一套實(shí)現(xiàn),杜絕代碼的冗余,同時(shí)這樣的集成對(duì)代碼的結(jié)構(gòu)本身也是低侵入性。

  • AbstractElasticIndexManger

public abstract class AbstractElasticIndexManger {

    protected ElasticsearchRestTemplate elasticsearchRestTemplate;

    protected RestHighLevelClient restHighLevelClient;

    @Autowired
    public void setRestHighLevelClient(RestHighLevelClient restHighLevelClient) {
        this.restHighLevelClient = restHighLevelClient;
    }

    @Autowired
    public void setElasticsearchRestTemplate(ElasticsearchRestTemplate elasticsearchRestTemplate) {
        this.elasticsearchRestTemplate = elasticsearchRestTemplate;
    }

    /**
     * 設(shè)置分片 和 副本
     * 副本作用主要為了保證數(shù)據(jù)安全
     *
     * @param request 請(qǐng)求
     * @author <a >Sam</a>
     * @date 2019/10/17 19:27
     */
    protected void buildSetting(CreateIndexRequest request, int replicas, int shards) {
        request.settings(Settings.builder().put("index.number_of_shards", shards)
                .put("index.number_of_replicas", replicas));
    }

    /**
     * 查詢匹配條件的數(shù)據(jù)量,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
     *
     * @param builder    BoolQueryBuilder類型查詢實(shí)例
     * @param indexNames 索引名,可以一次性查詢多個(gè)
     * @return long 最終數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-9:26
     **/
    protected long count(BoolQueryBuilder builder, String... indexNames) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(builder);
        return elasticsearchRestTemplate.count(nativeSearchQueryBuilder.build(), IndexCoordinates.of(indexNames));
    }

    /**
     * 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
     *
     * @param builder    BoolQueryBuilder類型查詢實(shí)例
     * @param clazz      Class對(duì)象
     * @param indexNames 索引名,可以一次性查詢多個(gè)
     * @return long 最終數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-9:26
     **/
    protected SearchHits search(BoolQueryBuilder builder, Class<? extends BasePo> clazz, String... indexNames) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(builder);
        Pageable pageable = PageRequest.of(1, 20);
        nativeSearchQueryBuilder.withPageable(pageable);
        return elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), clazz, IndexCoordinates.of(indexNames));
    }

    /**
     * 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
     *
     * @param page       當(dāng)前頁(yè)
     * @param size       每頁(yè)大小
     * @param builder    BoolQueryBuilder類型查詢實(shí)例
     * @param clazz      Class對(duì)象
     * @param indexNames 索引名,可以一次性查詢多個(gè)
     * @return SearchHits 命中結(jié)果的數(shù)據(jù)集
     * @author <a >Sam</a>
     * @date 2021/11/1-9:26
     **/
    protected SearchHits<? extends BasePo> searchPage(int page, int size, BoolQueryBuilder builder, Class<? extends BasePo> clazz, String... indexNames) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(builder);
        Pageable pageable = PageRequest.of(page, size);
        nativeSearchQueryBuilder.withPageable(pageable);
        return elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), clazz, IndexCoordinates.of(indexNames));
    }

    protected DeleteByQueryRequest builderDeleteRequest(QueryBuilder builder, String... indexNames) {
        DeleteByQueryRequest request = new DeleteByQueryRequest(indexNames);
        request.setQuery(builder);
        request.setBatchSize(0X5F5E0FF);
        request.setConflicts("proceed");
        return request;
    }

    /**
     * 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
     *
     * @param params     Map形式的 字段名 和 字段內(nèi)容 組成的條件
     * @param builder    BoolQueryBuilder類型查詢實(shí)例
     * @param indexNames 索引名,可以一次性查詢多個(gè)
     * @return long 最終數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-9:26
     **/
    protected BulkByScrollResponse update(Map<String, Object> params, BoolQueryBuilder builder, String... indexNames) {
        UpdateByQueryRequest request = buildUpdateByQueryReq(params, builder, indexNames);
        try {
            return restHighLevelClient.updateByQuery(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 構(gòu)建更新 QueryRequest
     *
     * @param params     參數(shù)
     * @param builder    布爾構(gòu)建
     * @param indexNames 索引
     * @return UpdateByQueryRequest
     * @author <a >Sam</a>
     * @date 2021/11/28-15:50
     **/
    protected UpdateByQueryRequest buildUpdateByQueryReq(Map<String, Object> params, BoolQueryBuilder builder, String... indexNames) {
        Script script = buildScriptType(params);
        UpdateByQueryRequest request = new UpdateByQueryRequest(indexNames);
        request.setQuery(builder);
        request.setScript(script);
        request.setConflicts("proceed");
        request.setRefresh(true);
        request.setTimeout(TimeValue.timeValueMinutes(3));
        return request;
    }

    /**
     * 以 K-V鍵值對(duì) 方式構(gòu)建條件 Script
     *
     * @param params Map形式的 字段名 和 字段內(nèi)容 組成的條件
     * @return Script
     * @author <a >Sam</a>
     * @date 2021/11/28-13:19
     **/
    protected Script buildScriptType(Map<String, Object> params) {
        Set<String> keys = params.keySet();
        StringBuffer idOrCodeStb = new StringBuffer();
        for (String key : keys) {
            idOrCodeStb.append("ctx._source.").append(key).append("=params.").append(key).append(";");
        }
        ScriptType type = ScriptType.INLINE;
        return new Script(type, Script.DEFAULT_SCRIPT_LANG, idOrCodeStb.toString(), params);
    }

    /**
     * @param builder BoolQueryBuilder
     * @param bool    布爾類條件
     * @author <a >Sam</a>
     * @date 2021/11/28-14:45
     **/
    protected void setBuilders(BoolQueryBuilder builder, BoolCondition bool) {
        mustBuilders(builder, bool);
        mustNotBuilders(builder, bool);
        shouldBuilders(builder, bool);
        filterBuilders(builder, bool);
    }

    /**
     * 構(gòu)建滿足 必須 條件 的方法
     *
     * @param builder BoolQueryBuilder
     * @param bool    布爾類條件
     * @author <a >Sam</a>
     * @date 2021/11/28-14:45
     **/
    protected void mustBuilders(BoolQueryBuilder builder, BoolCondition bool) {
        List<AtomicCondition> must = bool.getMust();
        if (must.isEmpty()) {
            return;
        }
        for (AtomicCondition cds : must) {
            builder.must(getQueryBuilder(cds));
        }
    }

    /**
     * 構(gòu)建滿足 非必須 條件 的方法
     *
     * @param builder BoolQueryBuilder
     * @param bool    布爾類條件
     * @author <a >Sam</a>
     * @date 2021/11/28-14:45
     **/
    protected void mustNotBuilders(BoolQueryBuilder builder, BoolCondition bool) {
        List<AtomicCondition> mustNot = bool.getMustNot();
        if (mustNot.isEmpty()) {
            return;
        }
        for (AtomicCondition cds : mustNot) {
            builder.mustNot(getQueryBuilder(cds));
        }
    }

    /**
     * 構(gòu)建滿足 可選 條件 的方法
     *
     * @param builder BoolQueryBuilder
     * @param bool    布爾類條件
     * @author <a >Sam</a>
     * @date 2021/11/28-14:45
     **/
    protected void shouldBuilders(BoolQueryBuilder builder, BoolCondition bool) {
        List<AtomicCondition> should = bool.getShould();
        if (should.isEmpty()) {
            return;
        }
        for (AtomicCondition cds : should) {
            builder.should(getQueryBuilder(cds));
        }
    }

    /**
     * 構(gòu)建滿足 必須 條件 的方法,推薦使用
     *
     * @param builder BoolQueryBuilder
     * @param bool    布爾類條件
     * @author <a >Sam</a>
     * @date 2021/11/28-14:45
     **/
    protected void filterBuilders(BoolQueryBuilder builder, BoolCondition bool) {
        List<AtomicCondition> filter = bool.getFilter();
        if (filter.isEmpty()) {
            return;
        }
        for (AtomicCondition cds : filter) {
            builder.filter(getQueryBuilder(cds));
        }
    }

    public QueryBuilder getQueryBuilder(AtomicCondition cds) {
        QueryBuilder queryBuilder;
        Tuple tuple = cds.getTuple();
        switch (cds.getStatus()) {
            case (Constants.SUFFIX_QUERY):
                queryBuilder = QueryBuilders.wildcardQuery(cds.getField(), Constants.MULTI_CHARACTER + tuple.v1());
                break;
            case (Constants.SUFFIX_SINGLE_QUERY):
                queryBuilder = QueryBuilders.wildcardQuery(cds.getField(), Constants.SINGLE_CHARACTER + tuple.v1());
                break;
            case (Constants.RANGE_QUERY):
                queryBuilder = QueryBuilders.rangeQuery(cds.getField()).from(tuple.v1()).to(tuple.v2());
                break;
            case (Constants.PREFIX_QUERY):
                queryBuilder = QueryBuilders.prefixQuery(cds.getField(), tuple.v1().toString());
                break;
            case (Constants.REG_QUERY):
                queryBuilder = QueryBuilders.regexpQuery(cds.getField(), tuple.v1().toString());
                break;
            default:
                queryBuilder = QueryBuilders.termQuery(cds.getField(), tuple.v1().toString());
                break;
        }
        return queryBuilder;
    }
}


  • ElasticIndexManger

public class ElasticIndexManger extends AbstractElasticIndexManger {


    /**
     * 創(chuàng)建索引,默認(rèn)分片數(shù)量為 1,即一個(gè)主片,副本數(shù)量為 0
     *
     * @param indexName 索引名稱
     * @param mapping   索引定義,JSON形式的字符串
     * @author <a >Sam</a>
     * @date 2019/10/17 17:30
     */
    public void createIndex(String indexName, String mapping) {
        createIndex(indexName, mapping, 0, 1);
    }


    /**
     * 指定索引結(jié)構(gòu)創(chuàng)建索引
     *
     * @param indexName 索引名稱
     * @param mapping   索引定義,JSON形式的字符串
     * @param replicas  副本的數(shù)量
     * @param shards    分片數(shù)量
     * @author <a >Sam</a>
     * @date 2019/10/17 17:30
     */
    public void createIndex(String indexName, String mapping, int replicas, int shards) {
        try {
            if (!this.existIndex(indexName)) {
                log.error(" indexName={} 已經(jīng)存在,mapping={}", indexName, mapping);
                return;
            }
            CreateIndexRequest request = new CreateIndexRequest(indexName);
            buildSetting(request, replicas, shards);
            request.mapping(mapping, XContentType.JSON);
            CreateIndexResponse res = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
            if (!res.isAcknowledged()) {
                throw new RuntimeException("初始化失敗");
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
    }

    /**
     * 獲取所有索引,默認(rèn)為所有索引
     *
     * @return List
     * @author <a >Sam</a>
     * @date 2021/10/30-11:54
     **/
    public List getAllIndex() {
        return getAllIndex(Constants.MULTI_CHARACTER);
    }

    /**
     * 獲取所有索引,按照正則表達(dá)式方式過(guò)濾 索引名稱,并返回符合條件的索引名字
     *
     * @param inPattern 正則表達(dá)式
     * @return List
     * @author <a >Sam</a>
     * @date 2021/10/30-11:54
     **/
    public List<String> getAllIndex(String inPattern) {
        GetIndexRequest getIndexRequest = new GetIndexRequest(inPattern);
        try {
            GetIndexResponse getIndexResponse = restHighLevelClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
            String[] indices = getIndexResponse.getIndices();
            return Arrays.asList(indices);
        } catch (IOException e) {
            log.error("獲取索引失敗 {} 已經(jīng)存在", e.getMessage());
        } catch (ElasticsearchStatusException e) {
            log.error("獲取索引失敗 {} 索引本身不存在", e.getMessage());
        }
        return Collections.EMPTY_LIST;
    }

    /**
     * 制定配置項(xiàng)的判斷索引是否存在,注意與 isExistsIndex 區(qū)別
     * <ul>
     *     <li>1、可以指定 用本地檢索 還是用 主動(dòng)節(jié)點(diǎn)方式檢索</li>
     *     <li>2、是否適應(yīng)被人讀的方式</li>
     *     <li>3、返回默認(rèn)設(shè)置</li>
     * </ul>
     *
     * @param indexName index名
     * @return boolean
     * @author <a >Sam</a>
     * @date 2019/10/17 17:27
     */
    public boolean existIndex(String indexName) throws IOException {
        GetIndexRequest request = new GetIndexRequest(indexName);
        //TRUE-返回本地信息檢索狀態(tài),F(xiàn)ALSE-還是從主節(jié)點(diǎn)檢索狀態(tài)
        request.local(false);
        //是否適應(yīng)被人可讀的格式返回
        request.humanReadable(true);
        //是否為每個(gè)索引返回所有默認(rèn)設(shè)置
        request.includeDefaults(false);
        //控制如何解決不可用的索引以及如何擴(kuò)展通配符表達(dá)式,忽略不可用索引的索引選項(xiàng),僅將通配符擴(kuò)展為開(kāi)放索引,并且不允許從通配符表達(dá)式解析任何索引
        request.indicesOptions(IndicesOptions.lenientExpandOpen());
        return restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
    }

    /**
     * 單純斷某個(gè)索引是否存在
     *
     * @param indexName index名
     * @return boolean 存在為True,不存在則 False
     * @author <a >Sam</a>
     * @date 2019/10/17 17:27
     */
    public boolean isIndexExists(String indexName) throws Exception {
        return restHighLevelClient.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT);
    }

    /**
     * 批量插入數(shù)據(jù),通過(guò) {@link List} 的對(duì)象集合進(jìn)行插入,此處對(duì)失敗的提交進(jìn)行二次提交,并覆蓋原有數(shù)據(jù),這一層面是 ElasticSearch自行控制
     *
     * @param indexName index
     * @param list      列表
     * @author <a >Sam</a>
     * @date 2019/10/17 17:26
     */
    public void batch(String indexName, List<? extends BasePo> list) throws IOException {
        int sleep = 15;
        BulkRequest request = new BulkRequest();
        list.forEach(item -> request.add(new IndexRequest(indexName)
                .id(item.getId().toString())
                .source(JSON.toJSONString(item), XContentType.JSON)));
        try {
            BulkResponse bulkResponse = bulk(request);
            log.error("[Verification BulkResponse bulk 操作結(jié)果] {}, 文件大小 {} ", bulkResponse.status(), list.size());
            if (bulkResponse.hasFailures()) {
                log.error(bulkResponse.buildFailureMessage());
                for (BulkItemResponse bulkItemResponse : bulkResponse) {
                    if (bulkItemResponse.isFailed()) {
                        log.error(bulkItemResponse.getFailureMessage());
                    }
                }
                log.error("批量操作失敗,重新再提交一次,間隔時(shí)間{}, 文件大小 {} ", sleep, list.size());
                TimeUnit.SECONDS.sleep(sleep);
                bulkResponse = bulk(request);
                if (bulkResponse.hasFailures()) {
                    log.error("再次提交失敗,需要寫入MQ , 文件大小 {} ", list.size());
                }
            }
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * bulk 方式批量提交
     *
     * @param request {@link BulkRequest} 請(qǐng)求
     * @return BulkResponse
     * @author <a >Sam</a>
     * @date 2021/10/24-15:50
     **/
    private BulkResponse bulk(BulkRequest request) throws IOException {
        return restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
    }

    /**
     * <p>
     * 批量插入數(shù)據(jù),通過(guò) {@link List} 的對(duì)象集合進(jìn)行插入,提交前,判斷 該索引是否存在不存在則直接創(chuàng)建 該索引
     * 并對(duì)失敗的提交進(jìn)行二次提交,并覆蓋原有數(shù)據(jù),這一層面是 ElasticSearch自行控制
     * </p>
     *
     * @param indexName index
     * @param list      列表
     * @param created   當(dāng)索引不存在,則創(chuàng)建索引,默認(rèn)為 true,即索引不存在,創(chuàng)建該索引,此時(shí) mapping 應(yīng)該不為空
     * @param mapping   索引定義,JSON形式的字符串
     * @author <a >Sam</a>
     * @date 2019/10/17 17:26
     */
    public void batch(List<? extends BasePo> list, String indexName, boolean created, String mapping) throws Exception {
        try {
            if (!isIndexExists(indexName)) {
                log.error("[Index does not exist] Rebuilding index. IndexName ={}", indexName);
                if (created && StringUtils.isNotBlank(mapping)) {
                    createIndex(indexName, mapping);
                } else {
                    log.error("[Index does not exist , No index creation] IndexName ={}", indexName);
                    return;
                }
            }
            batch(indexName, list);
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 批量刪除,根據(jù)索引名稱,刪除索引下數(shù)據(jù)
     *
     * @param indexName index
     * @param idList    待刪除列表
     * @author <a >Sam</a>
     * @date 2019/10/17 17:14
     */
    public <T> void deleteBatch(String indexName, Collection<T> idList) {
        BulkRequest request = new BulkRequest();
        idList.forEach(item -> request.add(new DeleteRequest(indexName, item.toString())));
        try {
            restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根據(jù)索引名稱,和 {@link SearchSourceBuilder} 條件,以及返回對(duì)象實(shí)體類,返回列表
     *
     * @param indexName index
     * @param builder   查詢參數(shù)
     * @param clazz     結(jié)果類對(duì)象
     * @return java.util.List<T>
     * @author <a >Sam</a>
     * @date 2019/10/17 17:14
     */
    public <T> List<T> search(String indexName, SearchSourceBuilder builder, Class<T> clazz) {
        List res = Collections.EMPTY_LIST;
        try {
            SearchRequest request = new SearchRequest(indexName);
            request.source(builder);
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            SearchHit[] hits = response.getHits().getHits();
            res = new ArrayList<>(hits.length);
            for (SearchHit hit : hits) {
                res.add(JSON.parseObject(hit.getSourceAsString(), clazz));
            }
        } catch (IOException e) {
            log.error("[ElasticSearch] connect err ,err-msg {}", e.getMessage());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    /**
     * 刪除 index,以及索引下數(shù)據(jù)
     *
     * @param indexName 索引名字
     * @author <a >Sam</a>
     * @date 2019/10/17 17:13
     */
    public void deleteIndex(String indexName) {
        try {
            restHighLevelClient.indices().delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 刪除索引下數(shù)據(jù),但是不刪除索引結(jié)構(gòu)
     *
     * @param builder    條件構(gòu)建模式
     * @param indexNames 索引名稱列表
     * @author <a >Sam</a>
     * @date 2019/10/17 17:13
     */
    public void deleteByQuery(QueryBuilder builder, String... indexNames) {
        try {
            DeleteByQueryRequest request = builderDeleteRequest(builder, indexNames);
            BulkByScrollResponse response = restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 不推薦使用,原因?yàn)椴粔蜢`活,獲取該索引下可以匹配的數(shù)量,支持 模糊查詢和精確查詢,
     * 用法 在 方法 <b>field</b> 的處理上。
     * <ul>
     *     <li>模糊匹配模式:字段</li>
     *     <li>精確匹配模式:字段.類型</li>
     * </ul>
     *
     * @param indexName 文檔索引名
     * @param field     字段
     * @param text      內(nèi)容
     * @return long 數(shù)量
     * @author <a >Sam</a>
     * @date 2018/07/20-20:47
     **/
    @Deprecated
    public long countMatchPhrasePrefixQuery(String indexName, String field, String text) {
        CountRequest countRequest = new CountRequest(indexName);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(matchPhrasePrefixQuery(field, text));
        countRequest.source(searchSourceBuilder);
        CountResponse countResponse = null;
        try {
            countResponse = restHighLevelClient.count(countRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return countResponse == null ? 0L : countResponse.getCount();
    }


    /**
     * 按照字段 內(nèi)容進(jìn)行精確匹配,返回匹配的數(shù)量
     *
     * @param field      字段名
     * @param content    內(nèi)容
     * @param indexNames 索引名
     * @return long 數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-10:49
     **/
    public long exactCondition(String field, String content, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        builder.must(QueryBuilders.termQuery(field, content));
        return count(builder, indexNames);
    }


    /**
     * 按照字段的前綴內(nèi)容進(jìn)行匹配,返回匹配的數(shù)量
     *
     * @param field      字段名
     * @param prefix     前綴
     * @param indexNames 索引名
     * @return long 數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-10:49
     **/
    public long prefix(String field, String prefix, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        builder.must(QueryBuilders.prefixQuery(field, prefix));
        return count(builder, indexNames);
    }


    /**
     * 按照字段對(duì) 內(nèi)容進(jìn)行后綴匹配,返回匹配的數(shù)量
     *
     * @param suffix     后綴
     * @param indexNames 索引名
     * @return long 數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-10:56
     **/
    public long suffix(String field, String suffix, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        builder.must(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
        return count(builder, indexNames);
    }


    /**
     * 字段的前綴和后綴都必須滿足條件
     *
     * @param field      字段
     * @param prefix     前綴
     * @param suffix     后綴
     * @param indexNames 索引名
     * @return long 數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-10:59
     **/
    public long prefixAndSuffix(String field, String prefix, String suffix, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        builder.must(QueryBuilders.prefixQuery(field, prefix));
        builder.must(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
        return count(builder, indexNames);
    }

    /**
     * 字段的前綴和后綴都滿足一個(gè)條件按即可
     *
     * @param field      字段
     * @param prefix     前綴
     * @param suffix     后綴
     * @param indexNames 索引名
     * @return long 數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-10:59
     **/
    public long prefixOrSuffix(String field, String prefix, String suffix, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        builder.should(QueryBuilders.prefixQuery(field, prefix));
        builder.should(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
        return count(builder, indexNames);
    }

    /**
     * 字段的前綴必須滿足,而 后綴則不要求 不一定滿足
     *
     * @param field      字段
     * @param prefix     前綴
     * @param suffix     后綴
     * @param indexNames 索引名
     * @return long 數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-10:59
     **/
    public long prefixMustSuffixShould(String field, String prefix, String suffix, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        builder.must(QueryBuilders.prefixQuery(field, prefix));
        builder.should(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
        return count(builder, indexNames);
    }

    /**
     * 字段的前綴選擇性滿足,而 后綴則一定要滿足
     *
     * @param field      字段
     * @param prefix     前綴
     * @param suffix     后綴
     * @param indexNames 索引名
     * @return long 數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-10:59
     **/
    public long prefixShouldSuffixMust(String field, String prefix, String suffix, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        builder.should(QueryBuilders.prefixQuery(field, prefix));
        builder.must(QueryBuilders.wildcardQuery(field, Constants.MULTI_CHARACTER + suffix));
        return count(builder, indexNames);
    }


    /**
     * 查詢總數(shù)
     *
     * @param indexNames 索引文檔名稱,可以是多個(gè)
     * @return long 匹配的數(shù)量
     * @author <a >Sam</a>
     * @date 2021/10/29-21:11
     **/
    public long total(String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        return count(builder, indexNames);
    }

    /**
     * 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
     *
     * @param params     Map形式的 字段名 和 字段內(nèi)容 組成的條件
     * @param bool      復(fù)合條件封裝
     * @param indexNames 索引名,可以一次性查詢多個(gè)
     * @return long 最終數(shù)量
     * @author <a >Sam</a>
     * @date 2021/11/1-9:26
     **/
    public BulkByScrollResponse update(Map<String, Object> params, BoolCondition bool, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        setBuilders(builder,bool);
        return update(params,builder,indexNames);
    }

    /**
     * 查詢匹配條件,支持同時(shí)對(duì)多個(gè)索引進(jìn)行查詢,只要將索引名稱按照 字符數(shù)組形式組成即可
     *
     * @param page    當(dāng)前頁(yè)
     * @param size    每頁(yè)大小
     * @param clazz      Class對(duì)象
     * @param indexNames 索引名,可以一次性查詢多個(gè)
     * @return SearchHits 命中結(jié)果的數(shù)據(jù)集
     * @author <a >Sam</a>
     * @date 2021/11/1-9:26
     **/
    protected SearchHits<? extends BasePo> searchPage(int page, int size, BoolCondition bool,Class<? extends BasePo> clazz, String... indexNames) {
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        setBuilders(builder,bool);
        return searchPage(page,size,builder, clazz, indexNames);
    }

}

3. 項(xiàng)目代碼

通過(guò)以上的集成,我們看到完成在項(xiàng)目中對(duì) elasticsearch 的集成,同時(shí)也用基類,將所有可能的操作都封裝起來(lái)。下來(lái)我們通過(guò)對(duì)基類的講解,來(lái)逐個(gè)說(shuō)明!

3.1. 索引管理

由于在ElasticIndexManger類定義了所有方法,直接調(diào)用即可。

3.1.1. 創(chuàng)建索引

我們?cè)趧?chuàng)建索引過(guò)程中需要先判斷是否有這個(gè)索引,否則不允許創(chuàng)建,由于我案例采用的是手動(dòng)指定 indexNameSettings ,大家看的過(guò)程中要特別注意下,而且還有一點(diǎn) indexName 必須是小寫,如果是大寫在創(chuàng)建過(guò)程中會(huì)有錯(cuò)誤

官方索引創(chuàng)建說(shuō)明
索引名大寫

。詳細(xì)的代碼實(shí)現(xiàn)見(jiàn)如下:


 /**
     * 創(chuàng)建索引,默認(rèn)分片數(shù)量為 1,即一個(gè)主片,副本數(shù)量為 0
     *
     * @param indexName 索引名稱
     * @param mapping   索引定義,JSON形式的字符串
     * @author <a >Sam</a>
     * @date 2019/10/17 17:30
     */
    public void createIndex(String indexName, String mapping) {
        createIndex(indexName, mapping, 0, 1);
    }


    /**
     * 指定索引結(jié)構(gòu)創(chuàng)建索引
     *
     * @param indexName 索引名稱
     * @param mapping   索引定義,JSON形式的字符串
     * @param replicas  副本的數(shù)量
     * @param shards    分片數(shù)量
     * @author <a >Sam</a>
     * @date 2019/10/17 17:30
     */
    public void createIndex(String indexName, String mapping, int replicas, int shards) {
        try {
            if (!this.existIndex(indexName)) {
                log.error(" indexName={} 已經(jīng)存在,mapping={}", indexName, mapping);
                return;
            }
            CreateIndexRequest request = new CreateIndexRequest(indexName);
            buildSetting(request, replicas, shards);
            request.mapping(mapping, XContentType.JSON);
            CreateIndexResponse res = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
            if (!res.isAcknowledged()) {
                throw new RuntimeException("初始化失敗");
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
    }

創(chuàng)建索引需要設(shè)置分片,這里采用Settings.Builder方式,當(dāng)然也可以 JSON 自定義方式,本文篇幅有限,不做演示。

index.number_of_shards:分片數(shù)

number_of_replicas:副本數(shù)

    /**
     * 設(shè)置分片 和 副本
     * 副本作用主要為了保證數(shù)據(jù)安全
     *
     * @param request 請(qǐng)求
     * @author <a >Sam</a>
     * @date 2019/10/17 19:27
     */
    protected void buildSetting(CreateIndexRequest request, int replicas, int shards) {
        request.settings(Settings.builder().put("index.number_of_shards", shards)
                .put("index.number_of_replicas", replicas));
    }


[elastic@localhost elastic]$ curl -H "Content-Type: application/json" -X GET "http://localhost:9200/_cat/indices?v"

health status index        uuid                   pri rep docs.count docs.deleted store.size pri.store.size

yellow open   twitter      scSSD1SfRCio4F77Hh8aqQ   3   2          2            0      8.3kb          8.3kb

yellow open   idx_location _BJ_pOv0SkS4tv-EC3xDig   3   2          1            0        4kb            4kb

yellow open   wongs        uT13XiyjSW-VOS3GCqao8w   3   2          1            0      3.4kb          3.4kb

yellow open   idx_locat    Kr3wGU7JT_OUrRJkyFSGDw   3   2          3            0     13.2kb         13.2kb

yellow open   idx_copy_to  HouC9s6LSjiwrJtDicgY3Q   3   2          1            0        4kb            4kb
  

說(shuō)明創(chuàng)建成功,這里總是通過(guò)命令行來(lái)驗(yàn)證,有點(diǎn)繁瑣,既然我都有WEB服務(wù),為什么不直接通過(guò)HTTP驗(yàn)證了?

3.1.2. 查看索引

查看索引這個(gè)操作支持模糊操作,即以通配符 * 作為一個(gè)或者多個(gè)字符匹配,這個(gè)操作在實(shí)際應(yīng)用非常好用,將來(lái)有機(jī)會(huì)說(shuō)到 Index 設(shè)計(jì)過(guò)程中就顯得尤為重要。


    /**
     * 獲取所有索引,默認(rèn)為所有索引
     *
     * @return List
     * @author <a >Sam</a>
     * @date 2021/10/30-11:54
     **/
    public List getAllIndex() {
        return getAllIndex(Constants.MULTI_CHARACTER);
    }

    /**
     * 獲取所有索引,按照正則表達(dá)式方式過(guò)濾 索引名稱,并返回符合條件的索引名字
     *
     * @param inPattern 正則表達(dá)式
     * @return List
     * @author <a >Sam</a>
     * @date 2021/10/30-11:54
     **/
    public List<String> getAllIndex(String inPattern) {
        GetIndexRequest getIndexRequest = new GetIndexRequest(inPattern);
        try {
            GetIndexResponse getIndexResponse = restHighLevelClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
            String[] indices = getIndexResponse.getIndices();
            return Arrays.asList(indices);
        } catch (IOException e) {
            log.error("獲取索引失敗 {} 已經(jīng)存在", e.getMessage());
        } catch (ElasticsearchStatusException e) {
            log.error("獲取索引失敗 {} 索引本身不存在", e.getMessage());
        }
        return Collections.EMPTY_LIST;
    }

3.1.3. 刪除索引

刪除的邏輯就比較簡(jiǎn)單,這里就不多說(shuō)。


    /**
     * 刪除 index,以及索引下數(shù)據(jù)
     *
     * @param indexName 索引名字
     * @author <a >Sam</a>
     * @date 2019/10/17 17:13
     */
    public void deleteIndex(String indexName) {
        try {
            restHighLevelClient.indices().delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

3.2. 引用依賴

構(gòu)建一個(gè)工程,我這里依然用 Gralde 工程作為樣例說(shuō)明, Maven 項(xiàng)目類似。

3.2.1. 依賴管理


implementation("io.github.rothschil:persistence-elasticsearch:1.2.3.RELEASE")

3.2.2. 依賴注入

在工程項(xiàng)目中直接使用 ElasticIndexManger 作為實(shí)例注入進(jìn)來(lái),后面我們可以直接使用它提供的各種方法。樣例中我是定義一個(gè)精確查詢作為說(shuō)明,TermQueryBuilder("sysCode","crm") 中參數(shù)分別代表匹配條件的列名和列的值; 在索引列名中我這里用的是 通配符,即可以在多個(gè)索引之間查詢; AccLog.class 這是我自定義的類,用以接收查詢出來(lái)的結(jié)果進(jìn)行實(shí)例化映射。


@Component
public class LogIndexManager{

    private ElasticIndexManger elasticIndexManger;

    @Autowired
    public void setElasticIndexManger(ElasticIndexManger elasticIndexManger) {
        this.elasticIndexManger = elasticIndexManger;
    }

    public List<AccLog> query(){
        QueryBuilder queryBuilder = new TermQueryBuilder("sysCode","crm");
        SearchSourceBuilder sb = new SearchSourceBuilder();
        sb.query(queryBuilder);
        return elasticIndexManger.search("hnqymh_hpg*",sb,AccLog.class);
    }
}

![查詢結(jié)果]](https://upload-images.jianshu.io/upload_images/7232803-c3509fce823f59ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

4. 源碼

Github演示源碼 ,記得給Star

Gitee演示源碼,記得給Star

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容