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

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页