ElasticSearch5.x入门

ES的安装

安装ElasticSearch

ElasticSearch 各个版本之间的差异较大,Java API也需要与ES版本一致,这里选用ElasticSearch 5.5.2版本

下载ES对应版本

安装可视化插件

ElasticSearch可视化操作的插件比较多,Head、Sense等,这里为了方便调试,使用了Head Chrome 插件

插件下载

ES基本概念

为了方便理解ElasticSearch相关概念,我们可以与Mysql做一个比较

ElasticSearch Mysql
Index DataBase
Type Table
Document Row

Index

Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写

下面的命令可以查看当前节点的所有 Index。

GET 'http://localhost:9200/_cat/indices'

也可以在Head插件中 索引 一栏查看

Type

Document 可以分组,比如weather这个 Index 里面,可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。

不同的 Type 应该有相似的结构(schema),举例来说,id字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如products和logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。

下面的命令可以列出每个 Index 所包含的 Type。

GET 'http://localhost:9200/_mapping

Document

Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。

Document 使用 JSON 格式表示,同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。

 {
    "title": "战狼2",
    "publishDate": "2017-07-27",
    "content": "故事发生在非洲附近的大海上",
    "director": "吴京",
    "price": 38
}

SpringBoot集成

SpringBoot1.x 中提供了spring-boot-starter-data-elasticsearch 的类似JPA操作,但是目前对于ElasticSearch高版本的支持还不是很完善,多次集成失败后决定采用org.elasticsearch.client transport方式

引入pom依赖

        
            org.elasticsearch
            elasticsearch
            ${elasticsearch.version}
        

        
            org.elasticsearch.client
            transport
            ${elasticsearch.version}
        

        
            com.sun.jna
            jna
            3.0.9
        


配置application.properties

es.search.host = 127.0.0.1
es.search.port = 9300

Spring依赖注入

@Configuration
public class ElasticConfig {

    @Value("${es.search.host}")
    private String host;

    @Value("${es.search.port}")
    private Interger port;

    @Bean
    public TransportClient transportClient() throws UnknownHostException {
        //设置集群名称
        Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
        //创建client
        TransportClient client = new PreBuiltTransportClient(settings)
                .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port));
        return client;
    }

}

ES基本操作

索引创建

ES 接口实现

PUT 'http://localhost:9200/film

PUT 'http://localhost:9200/film/dongzuo

PUT 'http://localhost:9200/film/dongzuo/1
{
  "user": "张三",
  "title": "工程师",
  "desc": "数据库管理"
}

Java API 带数据创建,Java API 可以自动根据我们数据类型为我们设置mapping,下面创建了一个Index为为film, Type为dongzuo,并且插入了5条数据

        JsonArray jsonArray=new JsonArray();

        JsonObject jsonObject=new JsonObject();
        jsonObject.addProperty("title", "前任3:再见前任");
        jsonObject.addProperty("publishDate", "2017-12-29");
        jsonObject.addProperty("content", "一对好基友孟云(韩庚 饰)和余飞(郑恺 饰)跟女友都因为一点小事宣告分手,并且“拒绝挽回,死不认错”。两人在夜店、派对与交友软件上放飞人生第二春,大肆庆祝“黄金单身期”,从而引发了一系列好笑的故事。孟云与女友同甘共苦却难逃“五年之痒”,余飞与女友则棋逢敌手相爱相杀无绝期。然而现实的“打脸”却来得猝不及防:一对推拉纠结零往来,一对纠缠互怼全交代。两对恋人都将面对最终的选择:是再次相见?还是再也不见?");
        jsonObject.addProperty("director", "田羽生");
        jsonObject.addProperty("price", 35);
        jsonArray.add(jsonObject);

        JsonObject jsonObject2=new JsonObject();
        jsonObject2.addProperty("title", "机器之血");
        jsonObject2.addProperty("publishDate", "2017-12-29");
        jsonObject2.addProperty("content", "2007年,Dr.James在半岛军火商的支持下研究生化人。研究过程中,生化人安德烈发生基因突变大开杀戒,将半岛军火商杀害,并控制其组织,接管生化人的研究。Dr.James侥幸逃生,只好寻求警方的保护。特工林东(成龙 饰)不得以离开生命垂危的小女儿西西,接受证人保护任务...十三年后,一本科幻小说《机器之血》的出版引出了黑衣生化人组织,神秘骇客李森(罗志祥 饰)(被杀害的半岛军火商的儿子),以及隐姓埋名的林东,三股力量都开始接近一个“普通”女孩Nancy(欧阳娜娜 饰)的生活,想要得到她身上的秘密。而黑衣人幕后受伤隐藏多年的安德烈也再次出手,在多次缠斗之后终于抓走Nancy。林东和李森,不得不以身犯险一同前去解救,关键时刻却发现李森竟然是被杀害的半岛军火商的儿子,生化人的实验记录也落入了李森之手......");
        jsonObject2.addProperty("director", "张立嘉");
        jsonObject2.addProperty("price", 45);
        jsonArray.add(jsonObject2);

        JsonObject jsonObject3=new JsonObject();
        jsonObject3.addProperty("title", "星球大战8:最后的绝地武士");
        jsonObject3.addProperty("publishDate", "2018-01-05");
        jsonObject3.addProperty("content", "《星球大战:最后的绝地武士》承接前作《星球大战:原力觉醒》的剧情,讲述第一军团全面侵袭之下,蕾伊(黛西·雷德利 Daisy Ridley 饰)、芬恩(约翰·博耶加 John Boyega 饰)、波·达默龙(奥斯卡·伊萨克 Oscar Isaac 饰)三位年轻主角各自的抉 择和冒险故事。前作中觉醒强大原力的蕾伊独自寻访隐居的绝地大师卢克·天行者(马克·哈米尔 Mark Hamill 饰),在后者的指导下接受原力训练。芬恩接受了一项几乎不可能完成的任务,为此他不得不勇闯敌营,面对自己的过去。波·达默龙则要适应从战士向领袖的角色转换,这一过程中他也将接受一些血的教训。");
        jsonObject3.addProperty("director", "莱恩·约翰逊");
        jsonObject3.addProperty("price", 55);
        jsonArray.add(jsonObject3);

        JsonObject jsonObject4=new JsonObject();
        jsonObject4.addProperty("title", "羞羞的铁拳");
        jsonObject4.addProperty("publishDate", "2017-12-29");
        jsonObject4.addProperty("content", "靠打假拳混日子的艾迪生(艾伦 饰),本来和正义感十足的体育记者马小(马丽 饰)是一对冤家,没想到因为一场意外的电击,男女身体互换。性别错乱后,两人互坑互害,引发了拳坛的大地震,也揭开了假拳界的秘密,惹来一堆麻烦,最终两人在“卷莲门”副掌门张茱萸(沈腾 饰)的指点下,向恶势力挥起了羞羞的铁拳。");
        jsonObject4.addProperty("director", "宋阳 / 张吃鱼");
        jsonObject4.addProperty("price", 35);
        jsonArray.add(jsonObject4);

        JsonObject jsonObject5=new JsonObject();
        jsonObject5.addProperty("title", "战狼2");
        jsonObject5.addProperty("publishDate", "2017-07-27");
        jsonObject5.addProperty("content", "故事发生在非洲附近的大海上,主人公冷锋(吴京 饰)遭遇人生滑铁卢,被“开除军籍”,本想漂泊一生的他,正当他打算这么做的时候,一场突如其来的意外打破了他的计划,突然被卷入了一场非洲国家叛乱,本可以安全撤离,却因无法忘记曾经为军人的使命,孤身犯险冲回沦陷区,带领身陷屠杀中的同胞和难民,展开生死逃亡。随着斗争的持续,体内的狼性逐渐复苏,最终孤身闯入战乱区域,为同胞而战斗。");
        jsonObject5.addProperty("director", "吴京");
        jsonObject5.addProperty("price", 38);
        jsonArray.add(jsonObject5);

        for(int i=0;i

查看记录

ES 接口

获取Id为1的记录,如果Id不正确,就查不到数据,found字段就是false
Get 'http://localhost:9200/film/dongzuo/1
返回结果
{
    "_index": "film",
    "_type": "manhua",
    "_id": "1",
    "_version": 1,
    "found": true,
    "_source": {
        "user": "张三",
        "title": "工程师",
        "desc": "数据库管理"
    }
}

Java API 接口

根据id 获取文档
    GetResponse response=client.prepareGet("film", "dongzuo", "1").get();
    System.out.println(response.getSourceAsString());

删除记录

ES 接口

删除Id为1的记录
DELETE 'http://localhost:9200/film/dongzuo/1
返回结果
{
    "found": true,
    "_index": "film",
    "_type": "manhua",
    "_id": "1",
    "_version": 2,
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    }
}

Java API

根据Id 删除文档
DeleteResponse response=client.prepareDelete("film", "dongzuo", "1").get();

更新记录

更新Id为1的记录
POST 'http://localhost:9200/film/dongzuo/1
{
  "user": "李四",
  "title": "工程师",
  "desc": "数据库管理"
}

返回结果
{
    "found": true,
    "_index": "film",
    "_type": "manhua",
    "_id": "1",
    "_version": 2,
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    }
}

Java API

根据Id修改文档
    JsonObject jsonObject=new JsonObject();
    jsonObject.addProperty("user", "李四");
    jsonObject.addProperty("title", "工程师");
    jsonObject.addProperty("desc", "数据库管理");

    UpdateResponse response=client.prepareUpdate("film", "dongzuo", "1").setDoc(jsonObject.toString(), XContentType.JSON).get();

数据查询

条件查询

ES 接口

POST 'http://localhost:9200/accounts/person/_search'  
{
  "query" : { "match" : { "title" : "机器" }}
}

Java API


/**
  *查询title字段保护 “战”,content字段包含 “星球”,两个条件同时成立
  *
  */
    @Test
    public void searchMulti1(){
        SearchRequestBuilder srb = client.prepareSearch("film").setTypes("dongzuo");
        MatchPhraseQueryBuilder query1 = QueryBuilders.matchPhraseQuery("title", "战");
        MatchPhraseQueryBuilder query2 = QueryBuilders.matchPhraseQuery("content", "星球");
        SearchResponse sr = srb.setQuery(
                QueryBuilders.boolQuery()
                        .must(query1)
                        .must(query2))
                .execute().actionGet();
        SearchHits hits = sr.getHits();
        for (SearchHit hit : hits)
        {
            System.out.println(hit.getSourceAsString());
        }
    }

/**
  *  查询title字段保护 “战”,content字段不包含 “武士”,两个条件同时成立
  */
    @Test
    public void searchMulti2(){
        SearchRequestBuilder srb = client.prepareSearch("film").setTypes("dongzuo");
        MatchPhraseQueryBuilder query1 = QueryBuilders.matchPhraseQuery("title", "战");
        MatchPhraseQueryBuilder query2 = QueryBuilders.matchPhraseQuery("content", "武士");
        SearchResponse sr = srb.setQuery(
                QueryBuilders.boolQuery()
                        .must(query1)
                        .mustNot(query2))
                .execute().actionGet();
        SearchHits hits = sr.getHits();
        for (SearchHit hit : hits)
        {
            System.out.println(hit.getSourceAsString());
        }
    }

/**
  *  查询title字段包含 “战”或者 content字段包含 “军火”的记录
  */
    @Test
    public void searchMultiShould(){
        SearchRequestBuilder srb = client.prepareSearch("film").setTypes("dongzuo");
        MatchPhraseQueryBuilder query1 = QueryBuilders.matchPhraseQuery("title", "战");
        MatchPhraseQueryBuilder query2 = QueryBuilders.matchPhraseQuery("content", "军火");
        SearchResponse sr = srb.setQuery(
                QueryBuilders.boolQuery()
                        .should(query1)
                        .should(query2))
                .execute().actionGet();
        SearchHits hits = sr.getHits();
        for (SearchHit hit : hits)
        {
            System.out.println(hit.getSourceAsString());
        }
    }

    /**
     *  查询发布日期大于等于2017-12-19的记录
     */
    @Test
    public void searchRange()
    {
        SearchRequestBuilder srb = client.prepareSearch("film").setTypes("dongzuo");
        RangeQueryBuilder query1 = QueryBuilders.rangeQuery("publishDate").gte("2017-12-19");
        SearchResponse sr = srb.setQuery(
                QueryBuilders.boolQuery()
                        .must(query1))
                .execute().actionGet();
        SearchHits hits = sr.getHits();
        for (SearchHit hit : hits)
        {
            System.out.println(hit.getSourceAsString());
        }
    }

需要注意的是Java API中的查询是支持多个索引Index,多个Type同时查询

分页

ElasticSearch一次查询默认返回的是10条数据,我们可以通过size、from这两个参数指定进行实现分页。size表示取多少个记录,from表示偏移量

ES 接口


POST http://localhost:9200/film/dongzuo/
{
  "query": {
    "match": {
      "content": "故事"
    }
  },
  "from": 1,
  "size": 5
}

Java API

    @Test
    public void searchByPage()
    {
        SearchRequestBuilder srb = client.prepareSearch("film").setTypes("dongzuo");
        MatchAllQueryBuilder mqb = QueryBuilders.matchAllQuery();
        SearchResponse sr = srb.setQuery(mqb).setFrom(1).setSize(5).execute().actionGet();
        SearchHits hits = sr.getHits();
        for (SearchHit hit :hits)
        {
            System.out.println(hit.getSourceAsString());
        }
    }

排序

ES排序可以通过指定sort字段进行排序 ES接口

POST http://localhost:9200/film/dongzuo/_search
{
  "query": {
    "match": {
      "content": "故事"
    }
  },
  "from": 1,
  "size": 5,
  "sort": [
    {
      "publishDate": {
        "order": "desc"
      }
    }
  ]
}

Java API

    @Test
    public void searchByPageAndSort()
    {
        SearchRequestBuilder srb = client.prepareSearch("film").setTypes("dongzuo");
        MatchAllQueryBuilder mqb = QueryBuilders.matchAllQuery();
        //按照publishDate 进行降序排序
        SearchResponse sr = srb.setQuery(mqb).setFrom(1).setSize(20).addSort("publishDate", SortOrder.DESC).execute().actionGet();
        SearchHits hits = sr.getHits();
        for (SearchHit hit :hits)
        {
            System.out.println(hit.getSourceAsString());
        }
    }

分组统计

ElasticSearch的Java API操作中我们可以通过Aggregation 进行多个字段分组统计,如下面的代码,按照publishDate字段 对数据分组,然后调用sum对每一个分组内的price字段进行合计

    @Test
    public void groupByDate()
    {
        SearchRequestBuilder srb = client.prepareSearch("film").setTypes("dongzuo");
        TermsAggregationBuilder teamAgg = AggregationBuilders.terms("count").field("publishDate");
        SumAggregationBuilder sumAgg = AggregationBuilders.sum("sumPrice").field("price");
        teamAgg.subAggregation(sumAgg);
        srb.addAggregation(teamAgg);
        SearchResponse sr = srb.execute().actionGet();
        Terms termsGroup = sr.getAggregations().get("count");
        List buckets = termsGroup.getBuckets();

        for (Terms.Bucket b : buckets)
        {
            Aggregations aggregations = b.getAggregations();
            InternalSum sumPrices = aggregations.get("sumPrice");
            double value = sumPrices.getValue();
            System.out.println(value);
        }
    }

权重查询

ElasticSearch中多字段搜索时,为了更精准的匹配到我们的数据,我们可以给相关字段加上查询权重,boost. 这些词的文档获得更高的相关度评分 _score ,也就是说,它们会出现在结果集的更上面

    @Test
    public void weightSearchTest()
    {
        SearchRequestBuilder srb = client.prepareSearch("company").setTypes("person");
        MatchPhraseQueryBuilder mpq1 = QueryBuilders.matchPhraseQuery("title", "工程师");
        MatchPhraseQueryBuilder mpq2 = QueryBuilders.matchPhraseQuery("desc", "工程师").boost(0.1f);
        srb.setQuery(QueryBuilders.boolQuery().should(mpq1).should(mpq2)).setSize(3);

        SearchResponse sr = srb.execute().actionGet();
        SearchHits hits = sr.getHits();

        hits.forEach(searchHitFields ->
        {
            System.out.println(searchHitFields.getSourceAsString());
        });
    }

地理位置计算

ElasticSearch给我们提供了方便的地理位置信息计算API,我们可以对地理位置进行距离计算,远近排序

    @Test
    public void geoDiastanceTest() {
        SearchRequestBuilder srb = client.prepareSearch("hotel").setTypes("address");

        //查询距离39.929986, 116.395645 1000km范围内的数据
        GeoDistanceQueryBuilder gdr = QueryBuilders.geoDistanceQuery("location")
                .point(new GeoPoint(39.929986, 116.395645))
                .distance(1000, DistanceUnit.KILOMETERS);
        srb.setQuery(gdr);

        //按照距离远近进行排序
        GeoDistanceSortBuilder sort = new GeoDistanceSortBuilder("location",new GeoPoint(39.929986, 116.395645));
        sort.unit(DistanceUnit.KILOMETERS);//距离单位公里
        sort.order(SortOrder.ASC);
        sort.point(39.929986, 116.395645);//注意纬度在前,经度在后

        srb.addSort(sort);
        SearchResponse searchResponse = srb.execute().actionGet();

        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHists = hits.getHits();
        System.out.println("北京附近的城市(" + hits.getTotalHits() + "个):");
        for (SearchHit hit : searchHists) {
            String city = (String) hit.getSource().get("name");
            String title = (String) hit.getSource().get("desc");
            // 获取距离值,并保留两位小数点
            BigDecimal geoDis = new BigDecimal((Double) hit.getSortValues()[0]);
            Map hitMap = hit.getSource();

            hitMap.put("geoDistance", geoDis.setScale(2, BigDecimal.ROUND_HALF_DOWN));
            System.out.println(city + "距离北京" + hit.getSource().get("geoDistance") + DistanceUnit.KILOMETERS.toString() + "---" + title);
        }
    }

ik分词插件

安装

elasticsearch-analysis-ik 在ik的github上,我们可以找到对应ealstic版本的ik插件,进行下载安装

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.2/elasticsearch-analysis-ik-5.5.2.zip

分词查询

创建指定analysis的索引mapping,ik中常见的analyzer主要有ik_max_word、ik_smart 以中华人民共和国为例 ik_smart 切分为中华人民共和国 ik_max_word 切分为 中华人民共和国、中华人民、人民。。。

PUT localhost:9200/accounts

 {
  "mappings": {
    "person": {
      "properties": {
        "user": {
          "type": "text",
          "analyzer": "ik_max_word",
          "search_analyzer": "ik_max_word"
        },
        "title": {
          "type": "text",
          "analyzer": "ik_max_word",
          "search_analyzer": "ik_max_word"
        },
        "desc": {
          "type": "text",
          "analyzer": "ik_max_word",
          "search_analyzer": "ik_max_word"
        }
      }
    }
  }
}

Java API

    @Test
    public void searchPart() {
        SearchRequestBuilder srb = client.prepareSearch("company").setTypes("person");
        MatchQueryBuilder mqb = QueryBuilders.matchQuery("title", "资料worker");
        SearchResponse sr = srb.setQuery(mqb.analyzer(ANALYZER))
                .setFetchSource(new String[]{"desc"}, null)
                .execute()
                .actionGet();

        SearchHits hits = sr.getHits();
        for (SearchHit hit : hits) {
            System.out.println(hit.getSourceAsString());
        }
    }

    @Test
    public void searchMultiPart()
    {
        SearchRequestBuilder srb = client.prepareSearch("company").setTypes("person");
        MultiMatchQueryBuilder mqb = QueryBuilders.multiMatchQuery("java资料", "title", "desc");
        SearchResponse sr = srb.setQuery(mqb.analyzer(ANALYZER))
                .setFetchSource(new String[]{"desc","user","title"}, null)
                .execute()
                .actionGet();

        SearchHits hits = sr.getHits();
        for (SearchHit hit : hits) {
            System.out.println(hit.getSourceAsString());
        }
    }

同义词查询

配置同义词是为了能够检索一个词的时候相关词也能够检索到。关联词和同义词可以合二为一配置在这个文件里。 新建同义词文件:在Elasticsearch的confg目录下新建文件夹analysis并在其下创建文件synonyms.txt

向synonyms.txt中添加一下内容,注意文档编码格式为utf-8

中国,中华人民共和国,china,中华

创建index:自定义分词器和过滤器并引用IK分词器

PUT http://localhost:9200/paper

{
  "index": {
    "analysis": {
      "analyzer": {
        "by_smart": {
          "type": "custom",
          "tokenizer": "ik_smart",
          "filter": [
            "by_tfr",
            "by_sfr"
          ],
          "char_filter": [
            "by_cfr"
          ]
        },
        "by_max_word": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": [
            "by_tfr",
            "by_sfr"
          ],
          "char_filter": [
            "by_cfr"
          ]
        }
      },
      "filter": {
        "by_tfr": {
          "type": "stop",
          "stopwords": [
            " "
          ]
        },
        "by_sfr": {
          "type": "synonym",
          "synonyms_path": "analysis/synonyms.txt"
        }
      },
      "char_filter": {
        "by_cfr": {
          "type": "mapping",
          "mappings": [
            "| => |"
          ]
        }
      }
    }
  }
}

创建mapping

POST http://localhost:9200/paper/country

{
  "mappings": {
    "country": {
      "dynamic": true,
      "properties": {
        "name": {
          "type": "text",
          "analyzer": "ik_synonym"
        },
        "area": {
          "type": "text",
          "analyzer": "ik_synonym",
          "fielddata": true
        },
        "number": {
          "type": "long"
        },
        "category_id": {
          "type": "long"
        }
      }
    }
  }
}

分别插入中华、中华、中国。

PUT http://localhost:9200/paper/country
{
  "name": "中华",
  "area": "亚洲",
  "number": 1,
  "category_id": 1
}

搜索中华,将得到所有数据

POST http://localhost:9200/paper/country/_search
{
    "query":
        {
            "match" : 
            {
               "name" : "中华"     
            }
        }

}

返回结果

    {
    "took": 6,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 0.51623213,
        "hits": [{
            "_index": "paper",
            "_type": "country",
            "_id": "AWap1Ztwn7iNxjanvOmr",
            "_score": 0.51623213,
            "_source": {
                "name": "中华",
                "area": "亚洲",
                "number": 1,
                "category_id": 1
            }
        }, {
            "_index": "paper",
            "_type": "country",
            "_id": "AWap1XUrn7iNxjanvOmq",
            "_score": 0.25811607,
            "_source": {
                "name": "中国",
                "area": "亚洲",
                "number": 1,
                "category_id": 1
            }
        }]
    }
}

权重排序

在日常的业务需求中,我们通常存在一种情况,需要通过某个字段计算得出的值进行排序,再ElasticSearch的Java API中,提供了ScriptSortBuilder,让我们可以通过编写脚本代码来进行逻辑计算,并按照计算结果进行排序。

    @Test
    public void searchOrderByCal()
    {
        SearchRequestBuilder srb = client.prepareSearch("movie").setTypes("grade");
        MatchAllQueryBuilder mqb = QueryBuilders.matchAllQuery();
        //isStar =true 得分1 isPlayed =true得分1
        ScriptSortBuilder ssb = SortBuilders.scriptSort(new Script("doc['isStar'].value?doc['isPlayed'].value?2:1 : doc['isPlayed'].value?1:0"), ScriptSortBuilder.ScriptSortType.NUMBER).order(SortOrder.DESC);
        SearchResponse sr = srb.setQuery(mqb).addSort(ssb).execute().actionGet();
        sr.getHits().forEach(searchHitFields -> {
            System.out.println(searchHitFields.getSourceAsString());
        });
    }

你可能感兴趣的:(ElasticSearch5.x入门)