Elastic Search Java API(文檔操作API、Query DSL查詢API)、es搜索引擎實戰demo


elastic search實戰小demo:https://github.com/simonsfan/springboot-quartz-demo,分支:feature_es


之前在 Elastic Search之Search API(Query DSL)Elasticsearch之索引和文檔API 文章中講到過elastic search(以下簡稱es)的以下常用的原生api,本篇開始講述如何結合java開發使用es api進行索引的CRUD及複雜查詢。我這裏以springboot中使用爲例方便測試,首先需要引入maven依賴:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>5.6.1</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.7</version>
</dependency>

springboot配置文件application.yml中加如下配置,注意9300是es與java交互的端口,不是es與http交互的端口號9200:

elasticsearch:
  cluster:
    name: elasticsearch
  host: 127.0.0.1
  port: 9300

建立測試用到的movie_index索引並且新增兩條測試數據:

PUT /movie_index
{
  "mappings": {
    "info": {
      "properties": {
        "actors": {
          "type": "text"
        },
        "alias": {
          "type": "text"
        },
        "directors": {
          "type": "text"
        },
        "introduction": {
          "type": "text"
        },
        "label": {
          "type": "text"
        },
        "name": {
          "type": "text"
        },
        "score": {
          "type": "float"
        },
        "release": {
          "type": "date"
        },
        "area": {
          "type": "keyword"
        }
      }
    }
  }
}

POST /movie_index/info
{
  "name": "毒液",
  "alias": "毒液",
  "actors": "湯姆·哈迪 米歇爾·威廉姆斯 伍迪·哈里森 里茲·阿邁德 珍妮·斯蕾特",
  "directors": "魯本·弗雷斯徹",
  "score": 9,
  "area": "美國",
  "label": "2018年 美國 電影 動作 好萊塢 VIP電影 VIP尊享 動作 新片",
  "release": "2019",
  "introduction": "017年3月17日,索尼宣佈將爲蜘蛛俠的死對頭“毒液”(Venom)打造外傳電影,並計劃於2018年10月5日上映。《毒液》被視作蜘蛛俠系列的外傳,將由《超凡蜘蛛俠2》的編劇艾裏克斯·庫茲曼(《木乃伊》)執導,《蜘蛛俠:英雄歸來》的製片人馬修·托馬齊以及漫威影業前CEO阿維·阿拉德擔任製片,由丹特·哈珀(《明日邊緣》)編劇。他們表示,此片與湯姆·赫蘭德主演的蜘蛛俠三部曲沒什麼關係,是一個獨立的外傳。關於此片的更多細節並未透露。   2017年3月28日,索尼確認《毒液》將以R級的形式進行開發。   2017年5月,確認英國演員湯姆·哈迪將出演漫威蜘蛛俠衍生片《毒液》,將扮演自由攝影師Eddie Brock 。   2017年6月,製片人艾米·帕斯卡爾證實影片將和漫威電影宇宙連接,作爲附屬電影,並且有機會讓湯姆·赫蘭德迴歸飾演蜘蛛俠。"
}

POST /movie_index/info
{
  "name": "我不是藥神",
  "alias": "我不是藥神",
  "actors": "徐崢 王傳君 週一圍 譚卓 章宇",
  "directors": "文牧野",
  "score": 9.2,
  "area": "內地",
  "label": "國內院線 VIP電影 劇情 VIP尊享 院線 喜劇 喜劇 新片 劇情",
  "release": "2018",
  "introduction": "普通中年男子程勇(徐崢 飾)經營着一家保健品店,失意又失婚。不速之客呂受益(王傳君 飾)的到來,讓他開闢了一條去印度買藥做“代購”的新事業,雖然困難重重,但他在這條“買藥之路”上發現了商機,一發不可收拾地做起了治療慢粒白血病的印度仿製藥獨家代理商。賺錢的同時,他也認識了幾個病患及家屬,爲救女兒被迫做舞女的思慧(譚卓 飾)、說一口流利“神父腔”英語的劉牧師(楊新鳴 飾),以及脾氣暴烈的“黃毛”(章宇 飾),幾個人合夥做起了生意,利潤倍增的同時也危機四伏。程勇昔日的小舅子曹警官(週一圍 飾)奉命調查仿製藥的源頭,假藥販子張長林(王硯輝 飾)和瑞士正牌醫藥代表(李乃文 飾)也對其虎視眈眈,生意逐漸變成了一場關於救贖的拉鋸戰。"
}

首先注入elastic search的客戶端TransportClient:

@Configuration
public class ElasticSearchConfig {

    @Value("${elasticsearch.host}")
    private String esHost;

    @Value("${elasticsearch.port}")
    private int esPort;

    @Value("${elasticsearch.cluster.name}")
    private String esName;

    @Bean
    public TransportClient esClient() throws UnknownHostException {
        TransportClient client = null;
        try {
            Settings settings = Settings.builder()
                    .put("client.transport.sniff", true)
                    .put("cluster.name", this.esName)
                    .build();

            InetSocketTransportAddress master = new InetSocketTransportAddress(InetAddress.getByName(esHost), esPort);

            client = new PreBuiltTransportClient(settings).addTransportAddress(master);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return client;
    }
}

後續需要使用es客戶端TransportClient 時直接使用@Autowired注入即可。


books Document APIS

主要體現爲文檔的增刪改查、批量操作等

Index API

index api表現爲新增文檔(如果索引不存在會直接新建),參數需要指定索引名稱、類型、json數據三個參數,然後根據返回IndexResponse的狀態判斷是否新增成功:

    @Autowired
    private TransportClient transportClient;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 新增文檔
     *
     * @param indexTemplate
     * @return
     */
    @Override
    public String addIndex(MovieIndexTemplate indexTemplate) {
        if (indexTemplate == null) {
            return ResultUtil.fail();
        }
        try {
            IndexRequestBuilder indexRequestBuilder = transportClient.prepareIndex(INDEX, TYPE).setSource(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON);
            IndexResponse indexResponse = indexRequestBuilder.get();
            if (indexResponse.status() == RestStatus.CREATED) {
                logger.info("name={},新增文檔成功", indexTemplate.getName());
                return ResultUtil.success();
            }
        } catch (JsonProcessingException e) {
            logger.error("");
        }
        return ResultUtil.fail();
    }
    @Autowired
    private MovieSearchService movieSearchService;

    @Test
    public void testAddDocument() {
        MovieIndexTemplate indexTemplate = new MovieIndexTemplate();
        indexTemplate.setName("喜劇之王");
        indexTemplate.setAlias("喜劇之王");
        indexTemplate.setActors("周星馳,莫文蔚,張柏芝,吳孟達,林子善,田啓文");
        indexTemplate.setDirectors("周星馳  李力持");
        indexTemplate.setIntroduction("《喜劇之王》是星輝海外有限公司出品的一部喜劇電影,由李力持、周星馳執導,周星馳、 莫文蔚、張柏芝等主演。該片於1999年2月13日在香港上映。影片講述對喜劇情有獨鍾的尹天仇與舞女柳飄飄逐漸產生感情,之後在杜娟兒的幫助下,尹天仇終於獲得機會演主角,但又陷入與柳飄飄、杜娟兒的三角戀漩渦之中");
        indexTemplate.setArea("內地");
        indexTemplate.setScore(9.4f);
        indexTemplate.setLabel("喜劇 愛情");
        indexTemplate.setRelease("1999");
        movieSearchService.addIndex(indexTemplate);
    }

Get API

get api用於獲取指定的文檔數據,接收索引名稱、類型、id三個參數:

    @Override
    public String getIndex(String id) {
        GetResponse getResponse = transportClient.prepareGet(INDEX, TYPE, id).get();
        String result = getResponse.getSourceAsString();
        logger.info("get api result ={},", result);
        if (StringUtils.isEmpty(result)) {
            return ResultUtil.fail();
        }
        return ResultUtil.success(result);
    }
    @Test
    public void getIndex(){
        String id = "hTCYWmgBBHn7EfncQOq6";
        movieSearchService.getIndex(id);
    }

get api只支持一個id,但是你可以使用multi get api來一次性傳入多個id,並返回多個對應的結果集:

    //multi getindex
    public void getIndex2(String[] keyword) {
        MultiGetResponse multiGetItemResponse = transportClient.prepareMultiGet()
                .add(INDEX, TYPE, keyword[0])
                .add(INDEX, TYPE, keyword[1])
                .get();
        for (MultiGetItemResponse getItemResponse : multiGetItemResponse) {
            GetResponse response = getItemResponse.getResponse();
            String json = response.getSourceAsString();
        }
    }

Delete API

delete api用於刪除指定的文檔數據,同get api類似,也是接收索引名稱、類型、id三個參數:

    @Override
    public String deleteIndex(String id) {
        DeleteRequestBuilder deleteRequestBuilder = transportClient.prepareDelete(INDEX, TYPE, id);
        DeleteResponse deleteResponse = deleteRequestBuilder.get();
        if (deleteResponse.status() == RestStatus.OK) {
            logger.info("");
            return ResultUtil.success();
        }
        return ResultUtil.fail();
    }
    @Test
    public void deleteIndex(){
        String id = "hTCYWmgBBHn7EfncQOq6";
        movieSearchService.deleteIndex(id);
    }

Delete By Query API

這個刪除操作根據篩選條件來刪除指定數據,比單純的根據id進行刪除靈活一些,例如下面的根據電影name字段匹配刪除:

    @Override
    public String deleteByQueryAction(String keyword) {
        BulkByScrollResponse bulkByScrollResponse = DeleteByQueryAction.INSTANCE.newRequestBuilder(transportClient).filter(QueryBuilders.matchQuery(MovieSearch.NAME, keyword)).source(INDEX).get();
        long deleted = bulkByScrollResponse.getDeleted();
        logger.info("根據條件keyword={}刪除result={}", keyword, deleted);
        if (deleted < 1) {
            logger.info("根據條件keyword={}刪除失敗", keyword);
            return ResultUtil.fail();
        }
        logger.info("根據條件keyword={}刪除成功", keyword);
        return ResultUtil.success();
    }
    @Test
    public void deleteByQueryAction(){
        String name = "喜劇之王";
        movieSearchService.deleteByQueryAction(name);
    }

由於這個delete by query操作可能執行較長時間,因此你可以設置異步方式執行替代直接get,並設置這個delete by query操作的監聽器,在監聽器裏面處理成功和失敗的具體邏輯:

    //異步執行delete by query
    public void deleteByQueryAction1(String keyword) {
        DeleteByQueryAction.INSTANCE.newRequestBuilder(transportClient).filter(QueryBuilders.matchQuery(MovieSearch.NAME, keyword)).source(INDEX).execute(new ActionListener<BulkByScrollResponse>() {
            @Override
            public void onResponse(BulkByScrollResponse bulkByScrollResponse) {
                long deleted = bulkByScrollResponse.getDeleted();
                logger.info("根據條件keyword={}刪除result={}", keyword, deleted);
                if (deleted < 1) {
                    logger.info("根據條件keyword={}刪除失敗", keyword);
                }
                logger.info("根據條件keyword={}刪除成功", keyword);
            }

            @Override
            public void onFailure(Exception e) {
                // Hanlder the exception……
            }
        });
    }

Bulk API

bulk api支持批量操作,例如一個請求裏面同時包含刪除、新增功能:

    //批量bulk操作
    @Override
    public void buldOption(MovieIndexTemplate indexTemplate, String id) throws JsonProcessingException {
        BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();

        bulkRequestBuilder.add(transportClient.prepareDelete(INDEX, TYPE, id));

        bulkRequestBuilder.add(transportClient.prepareIndex(INDEX, TYPE).setSource(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON));

        BulkResponse bulkItemResponses = bulkRequestBuilder.get();
        RestStatus status = bulkItemResponses.status();
        logger.info("bulk option result status={}", status);
    }

Update API和Reindex API這裏不講,用的不多,由於在elastic search中不推薦對Index進行修改,而應該直接刪除再新增的方式,所以update和reindex api用的並不多。


books Query DSL Java API

之前在Elastic Search之Search API(Query DSL)、字段類查詢、複合查詢 一文中說到了es的原生Query DSL API,分爲字段類查詢和複合查詢,字段類查詢又可以分爲單詞類查詢和全文匹配。

books 全文匹配:包括match、match_phrase、query_string、simple_query_string等查詢語句;

//Match Query
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(MovieSearch.NAME, "毒液");

//Muitl Match Query
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("", MovieSearch.NAME, MovieSearch.AREA, MovieSearch.INTRODUCTION, MovieSearch.ACTORS, MovieSearch.DIRECTORS);

//Common Terms Query
CommonTermsQueryBuilder commonTermsQueryBuilder = QueryBuilders.commonTermsQuery(MovieSearch.NAME, "毒液");

//Query String Query
QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("毒液").field(MovieSearch.NAME).defaultOperator(Operator.OR);

//Simple Query String Query
SimpleQueryStringBuilder simpleQueryStringBuilder = QueryBuilders.simpleQueryStringQuery("毒液").field(MovieSearch.NAME);

books 單詞匹配:包括term、terms、range等查詢語句;

//Term Query
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(MovieSearch.NAME, "毒液");

//Terms Query
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(MovieSearch.NAME, "毒液", "我不是藥神");

//Range Query      篩選評分在7~10分之間的數據集,includeLower(false)表示from是gt,反之;includeUpper(false)表示to是lt,反之
QueryBuilders.rangeQuery(MovieSearch.SCORE).from(7).to(10).includeLower(false).includeUpper(false);

books Compound Query

複合查詢是es中用的最多的,常見的是Bool查詢,包括must、should、filter、must_not,在Elastic Search之Search API(Query DSL)、字段類查詢、複合查詢 一文中也有說到過其原生api。下面是綜合使用Bool查詢示例code:

 public void boolDsl() {
        //Bool Query
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //電影名稱必須包含我不是藥神經過分詞後的文本,比如我、不、是、藥、神
        boolQueryBuilder.must(QueryBuilders.matchQuery(MovieSearch.NAME, "我不是藥神"));

        //排除導演是張三的電影信息
        boolQueryBuilder.mustNot(QueryBuilders.termQuery(MovieSearch.DIRECTORS, "張三"));

        //別名中應該包含藥神經過分詞後的文本,比如藥、神
        boolQueryBuilder.should(QueryBuilders.matchQuery(MovieSearch.ALIAS, "藥神"));

        //評分必須大於9(因爲es對filter會有智能緩存,推薦使用)
        boolQueryBuilder.filter(QueryBuilders.rangeQuery(MovieSearch.SCORE).gt(9));

        //name、actors、introduction、alias、label 多字段匹配"藥神",或的關係
        boolQueryBuilder.filter(QueryBuilders.multiMatchQuery("藥神", MovieSearch.NAME, MovieSearch.ACTORS, MovieSearch.INTRODUCTION, MovieSearch.ALIAS, MovieSearch.LABEL));

        String[] includes = {MovieSearch.NAME, MovieSearch.ALIAS, MovieSearch.SCORE, MovieSearch.ACTORS, MovieSearch.DIRECTORS, MovieSearch.INTRODUCTION};
        SearchRequestBuilder searchRequestBuilder = transportClient.prepareSearch(INDEX).setTypes(TYPE).setQuery(boolQueryBuilder).addSort(MovieSearch.SCORE, SortOrder.DESC).setFrom(0).setSize(10).setFetchSource(includes, null);
        SearchResponse searchResponse = searchRequestBuilder.get();
        if (!RestStatus.OK.equals(searchResponse.status())) {
            return;
        }
        for (SearchHit searchHit : searchResponse.getHits()) {
            String name = (String) searchHit.getSource().get(MovieSearch.NAME);
            //TODO
        }
    }

根據這些基礎api做了個基於es搜索引擎的demo項目,功能比較簡單,就是可以根據用戶輸入的文本去es中實時搜索數據集,效果如下圖:

項目github:https://github.com/simonsfan/springboot-quartz-demo分支:feature_es。要注意:由於這個項目之前是用來展示 "使用quartz實現定製化定時任務" 功能的,所以需要kafka服務,並且要安裝elastic search服務。項目中有很多問題需要完善,有興趣的小夥伴可以自己完善下。有問題歡迎留言交流!


books 上一篇: Elastic Search之分頁展示

books 下一篇: Elastic Search與mysql數據同步方案

books 參考資料: es官網https://www.elastic.co/guide/en/elasticsearch/client/java-api/6.3/index.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章