elasticsearch 使用

目录

RestAPI 简介

1.前提工作  --  创建索引库

2.RestClient的简单使用

创建索引库

删除索引库

索引库是否存在

创建文档

删除文档

修改文档

批量操作文档

match_all(全部查询)

match查询

term(精准查询)

range(范围查询)

geo(坐标查询)

functionScore(算分函数)

bool(布尔查询-多条件)

排序、分页

高亮

聚合

Bucket聚合(分组)

Metric聚合函数(min、max、avg)

词条(汉字 & 拼音)自动补全

SpringBoot 整合 EleasticSearch 8.0.X 弃用high-level-client


RestAPI 简介

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:Elasticsearch Clients | Elastic

1.前提工作  --  创建索引库

我们根据一个mysql的数据库表来创建对应的索引库

CREATE TABLE `tb_hotel` (
  `id` bigint(20) NOT NULL COMMENT '酒店id',
  `name` varchar(255) NOT NULL COMMENT '酒店名称;例:7天酒店',
  `address` varchar(255) NOT NULL COMMENT '酒店地址;例:航头路',
  `price` int(10) NOT NULL COMMENT '酒店价格;例:329',
  `score` int(2) NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
  `brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
  `city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
  `star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1钻到5钻',
  `business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹桥',
  `latitude` varchar(32) NOT NULL COMMENT '纬度;例:31.2497',
  `longitude` varchar(32) NOT NULL COMMENT '经度;例:120.3925',
  `pic` varchar(255) DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

创建的索引库如下:

PUT /hotel
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword",
        "copy_to": "all"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

几个特殊字段说明:

  • location:地理坐标,里面包含精度、纬度

  • all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索

地理坐标说明:

 copy_to说明:

2.RestClient的简单使用

在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。

分为三步:

1)引入es的RestHighLevelClient依赖:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

2)因为SpringBoot默认的ES版是7.6.2,所以我们需要覆盖默认的ES版本:

<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

3)初始化RestHighLevelClient:

初始化的代码如下:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
        HttpHost.create("http://192.168.150.101:9200")
));

创建索引库

代码分为三步:

  • 1)创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。

  • 2)添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。

  • 3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。

code:

    @Test
    void createHotelIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("hotel");
        request.source(MAPPING_TEMPLATE, XContentType.JSON);
        client.indices().create(request, RequestOptions.DEFAULT);
    }

public class HotelConstants {
    public static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"address\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"score\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"starName\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"business\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"location\":{\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "      \"pic\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"all\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

删除索引库

删除索引库的DSL语句非常简单

DELETE /hotel
@Test
void testDeleteHotelIndex() throws IOException {
    // 1.创建Request对象
    DeleteIndexRequest request = new DeleteIndexRequest("hotel");
    // 2.发送请求
    client.indices().delete(request, RequestOptions.DEFAULT);
}

索引库是否存在

判断索引库是否存在,本质就是查询,对应的DSL是:

GET /hotel
@Test
void testExistsHotelIndex() throws IOException {
    // 1.创建Request对象
    GetIndexRequest request = new GetIndexRequest("hotel");
    // 2.发送请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    // 3.输出
    System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}

创建文档

创建文档的DSL语句如下:

POST /{索引库名}/_doc/1
{
    "name": "Jack",
    "age": 21
}

 code:

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
    }
}
    @Test
    void addHotelDoc() throws IOException {
        Hotel hotel = hotelService.getById(36934L);
        HotelDoc doc= new HotelDoc(hotel);
        String json = JSON.toJSONString(doc);
        
        IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
        request.source(json, XContentType.JSON);
        client.index(request, RequestOptions.DEFAULT);
    }

Hotel在数据库中经纬度字段是分开的,es中经纬度是一个字段location,geo_point,所以用HotelDoc和es的属性做对应

删除文档

删除的DSL为是这样的:

DELETE /hotel/_doc/{id}
@Test
void testDeleteDocument() throws IOException {
    // 1.准备Request
    DeleteRequest request = new DeleteRequest("hotel", "61083");
    // 2.发送请求
    client.delete(request, RequestOptions.DEFAULT);
}

修改文档

修改我们讲过两种方式:

  • 全量修改:本质是先根据id删除,再新增

  • 增量修改:修改文档中的指定字段值

在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:

  • 如果新增时,ID已经存在,则修改

  • 如果新增时,ID不存在,则新增

这里是增量修改

    @Test
    void updateHotelDoc() throws IOException {
        UpdateRequest request = new UpdateRequest("hotel","36934");
        request.doc(
                "price", 333,
                "starName","三钻"
        );
        client.update(request, RequestOptions.DEFAULT);
    }

批量操作文档

批量处理BulkRequest,其本质就是将多个普通的CRUD请求组合在一起发送。

其中提供了一个add方法,用来添加其他请求:

可以看到,能添加的请求包括:

  • IndexRequest,也就是新增

  • UpdateRequest,也就是修改

  • DeleteRequest,也就是删除

因此Bulk中添加了多个IndexRequest,就是批量新增功能了。:

    @Test
    void bulkHotelDoc() throws IOException {
        List<Hotel> list = hotelService.list();

        BulkRequest request = new BulkRequest();
        for (Hotel hotel : list) {
            HotelDoc doc = new HotelDoc(hotel);
            request.add(
                    new IndexRequest("hotel")
                            .id(doc.getId().toString())
                            .source(JSON.toJSONString(doc), XContentType.JSON));
        }

        client.bulk(request, RequestOptions.DEFAULT);
    }

match_all(全部查询)

@Test
void testMatchAll() throws IOException {
    // 1.准备Request
    SearchRequest request = new SearchRequest("hotel");
    // 2.准备DSL
    request.source()
        .query(QueryBuilders.matchAllQuery());
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    // 4.解析响应
    handleResponse(response);
}

private void handleResponse(SearchResponse response) {
    // 4.解析响应
    SearchHits searchHits = response.getHits();
    // 4.1.获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到" + total + "条数据");
    // 4.2.文档数组
    SearchHit[] hits = searchHits.getHits();
    // 4.3.遍历
    for (SearchHit hit : hits) {
        // 获取文档source
        String json = hit.getSourceAsString();
        // 反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        System.out.println("hotelDoc = " + hotelDoc);
    }
}

match查询

    @Test
    void searchMatch() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.matchQuery("all", "如家"));
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        printSearchResponse(searchResponse);
    }
    @Test
    void searchMatchMulti() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.multiMatchQuery("外滩", "business", "name"));
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        printSearchResponse(searchResponse);
    }

term(精准查询)

  • term:词条精确匹配

    @Test
    void searchTerm() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.termQuery("city", "上海"));
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        printSearchResponse(searchResponse);
    }

range(范围查询)

    @Test
    void searchRange() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.rangeQuery("price").lte(200));
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        printSearchResponse(searchResponse);
    }

geo(坐标查询)

    @Test
    void searchGeo() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(
                QueryBuilders.geoDistanceQuery("location")
                        .point(31.21, 121.5)
                        .distance(15, DistanceUnit.KILOMETERS));
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        printSearchResponse(searchResponse);
    }

functionScore(算分函数)

    @Test
    void searchFunctionScore() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        QueryBuilder queryBuilder = QueryBuilders.matchQuery("all", "外滩");

        FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[1];

        ScoreFunctionBuilder<WeightBuilder> scoreFunctionBuilder = new WeightBuilder();
        scoreFunctionBuilder.setWeight(10);

        QueryBuilder termQuery = QueryBuilders.termQuery("brand", "如家");
        FunctionScoreQueryBuilder.FilterFunctionBuilder category = new FunctionScoreQueryBuilder.FilterFunctionBuilder(termQuery, scoreFunctionBuilder);
        filterFunctionBuilders[0] = category;

        request.source().query(
                QueryBuilders.functionScoreQuery(queryBuilder,
                        filterFunctionBuilders)
//                        .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
                        .boostMode(CombineFunction.SUM)
        );
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        printSearchResponse(searchResponse);
    }

或者:

 bool 查询 搭配 算分函数

 // 1.构建BooleanQuery
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // 关键字搜索
    String key = params.getKey();
    if (key == null || "".equals(key)) {
        boolQuery.must(QueryBuilders.matchAllQuery());
    } else {
        boolQuery.must(QueryBuilders.matchQuery("all", key));
    }
    // 城市条件
    if (params.getCity() != null && !params.getCity().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    }
    // 品牌条件
    if (params.getBrand() != null && !params.getBrand().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    }
    // 星级条件
    if (params.getStarName() != null && !params.getStarName().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    }
    // 价格
    if (params.getMinPrice() != null && params.getMaxPrice() != null) {
        boolQuery.filter(QueryBuilders
                         .rangeQuery("price")
                         .gte(params.getMinPrice())
                         .lte(params.getMaxPrice())
                        );
    }

    // 2.算分控制
    FunctionScoreQueryBuilder functionScoreQuery =
        QueryBuilders.functionScoreQuery(
        // 原始查询,相关性算分的查询
        boolQuery,
        // function score的数组
        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
            // 其中的一个function score 元素
            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                // 过滤条件
                QueryBuilders.termQuery("isAD", true),
                // 算分函数
                ScoreFunctionBuilders.weightFactorFunction(10)
            ).boostMode(CombineFunction.SUM)
        });
    request.source().query(functionScoreQuery);

bool(布尔查询-多条件)

布尔查询是用must、must_not、filter等方式组合其它查询,代码示例如下:

 可以看到,API与其它查询的差别同样是在查询条件的构建,QueryBuilders,结果解析等其他代码完全不变。

    @Test
    void searchBool() throws IOException {
        SearchRequest request = new SearchRequest("hotel");

        QueryBuilder mustBuilder = QueryBuilders.matchQuery("name", "如家");
        QueryBuilder mustNotBuilder = QueryBuilders.rangeQuery("price").gt(400);
        QueryBuilder geoBuilder = QueryBuilders.geoDistanceQuery("location")
                .point(31.21, 121.5).distance(10, DistanceUnit.KILOMETERS);


        request.source().query(
                QueryBuilders.boolQuery()
                        .must(mustBuilder)
                        .mustNot(mustNotBuilder)
                        .filter(geoBuilder));
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        printSearchResponse(searchResponse);
    }

排序、分页

搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。

@Test
void testPageAndSort() throws IOException {
    // 页码,每页大小
    int page = 1, size = 5;

    // 1.准备Request
    SearchRequest request = new SearchRequest("hotel");
    // 2.准备DSL
    // 2.1.query
    request.source().query(QueryBuilders.matchAllQuery());
    // 2.2.排序 sort
    request.source().sort("price", SortOrder.ASC);
    // 2.3.分页 from、size
    request.source().from((page - 1) * size).size(5);
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);

}

 距离排序

 request.source().sort(SortBuilders
         .geoDistanceSort("location", new GeoPoint(location))
         .order(SortOrder.ASC)
         .unit(DistanceUnit.KILOMETERS)
        );

高亮

代码解读:

  • 第一步:从结果中获取source。hit.getSourceAsString(),这部分是非高亮结果,json字符串。还需要反序列为HotelDoc对象

  • 第二步:获取高亮结果。hit.getHighlightFields(),返回值是一个Map,key是高亮字段名称,值是HighlightField对象,代表高亮值

  • 第三步:从map中根据高亮字段名称,获取高亮字段值对象HighlightField

  • 第四步:从HighlightField中获取Fragments,并且转为字符串。这部分就是真正的高亮字符串了

  • 第五步:用高亮的结果替换HotelDoc中的非高亮结果

private void handleResponse(SearchResponse response) {
    // 4.解析响应
    SearchHits searchHits = response.getHits();
    // 4.1.获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到" + total + "条数据");
    // 4.2.文档数组
    SearchHit[] hits = searchHits.getHits();
    // 4.3.遍历
    for (SearchHit hit : hits) {
        // 获取文档source
        String json = hit.getSourceAsString();
        // 反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        // 获取高亮结果
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        if (!CollectionUtils.isEmpty(highlightFields)) {
            // 根据字段名获取高亮结果
            HighlightField highlightField = highlightFields.get("name");
            if (highlightField != null) {
                // 获取高亮值
                String name = highlightField.getFragments()[0].string();
                // 覆盖非高亮结果
                hotelDoc.setName(name);
            }
        }
        System.out.println("hotelDoc = " + hotelDoc);
    }
}

聚合

Bucket聚合(分组)

    void searchAggs() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        //不返回文档数据
        request.source().size(0);
        request.source().aggregation(
                AggregationBuilders
                        .terms("brandAgg")
                        .field("brand")
                        .size(20)
                .order(BucketOrder.aggregation("_count",true))
        );
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        Aggregations aggregations = searchResponse.getAggregations();
        Terms brandTerms = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            System.out.println(bucket.getKeyAsString() + " : " + bucket.getDocCount());
        }
}

 获取多个聚合结果:

GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 2
      }
    },    
    "cityAgg": {
      "terms": {
        "field": "city",
        "size": 2
      }
    }
  }
}

    request.source().aggregation(AggregationBuilders
                                 .terms("brandAgg")
                                 .field("brand")
                                 .size(2)
                                );
    request.source().aggregation(AggregationBuilders
                                 .terms("cityAgg")
                                 .field("city")
                                 .size(2)
                                );

Metric聚合函数(min、max、avg)

现在我们需要对桶内的酒店做运算,获取每个品牌的用户评分的min、max、avg等值。

        SearchRequest request = new SearchRequest("hotel");
        request.source().size(0);
        request.source().aggregation(
                AggregationBuilders
                        .terms("brandAgg")
                        .field("brand")
                        .size(20)
                        .order(BucketOrder.aggregation("score_stats.avg",false))
                        .subAggregation(
                                AggregationBuilders
                                        .stats("score_stats")
                                        .field("score")
                        )
        );
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);

结果:

    @Test
    void searchAggsMetric() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().size(0);
        request.source().aggregation(
                AggregationBuilders
                        .terms("brandAgg")
                        .field("brand")
                        .size(20)
                        .order(BucketOrder.aggregation("score_stats.avg",false))
                        .subAggregation(
                                AggregationBuilders
                                        .stats("score_stats")
                                        .field("score")
                        )
        );
        SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
        Aggregations aggregations = searchResponse.getAggregations();
        Terms brandTerms = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            Aggregations bucketAggs = bucket.getAggregations();
            Stats stats = bucketAggs.get("score_stats");
            System.out.println(bucket.getKeyAsString() +" : " + JSON.toJSONString(stats));
        }
    }

词条(汉字 & 拼音)自动补全

目标根据酒店品牌 和 商圈的关键字,或者拼音,补全关联到的内容。

此处以(keyword)精准词语为例

1. 支持拼音关联搜索,则需要安装拼音分词器。

拼音分词器的安装请参考:elasticsearch 安装(docker)_naki_bb的博客-CSDN博客

不需要拼音补全的课参考 elasticsearch DSL命令_naki_bb的博客-CSDN博客的 自动补全

2.词条补全必须要有一个字段类型为suggestion。

索引库DSL语句如下

// 酒店数据索引库
PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "text_anlyzer": {
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
          "tokenizer": "keyword",
          "filter": "py"
        }
      },
      "filter": {
        "py": {
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart"
      },
      "suggestion":{
          "type": "completion",
          "analyzer": "completion_analyzer"
      }
    }
  }
}

实体类:

HotelDoc中要添加一个字段,用来做自动补全,内容可以是酒店品牌、商圈等信息。按照自动补全字段的要求,最好是这些字段的数组。

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private Object distance;
    private Boolean isAD;
    private List<String> suggestion;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
        // 组装suggestion
        if(this.business.contains("/")){
            // business有多个值,需要切割
            String[] arr = this.business.split("/");
            // 添加元素
            this.suggestion = new ArrayList<>();
            this.suggestion.add(this.brand);
            Collections.addAll(this.suggestion, arr);
        }else {
            this.suggestion = Arrays.asList(this.brand, this.business);
        }
    }
}

重新导入数据功能,可以看到新的酒店数据中包含了suggestion:

 API 示例

 结果解析示例:

 案例的实现如下:

@Override
public List<String> getSuggestions(String prefix) {
    try {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        request.source().suggest(new SuggestBuilder().addSuggestion(
            "suggestions",
            SuggestBuilders.completionSuggestion("suggestion")
            .prefix(prefix)
            .skipDuplicates(true)
            .size(10)
        ));
        // 3.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        Suggest suggest = response.getSuggest();
        // 4.1.根据补全查询名称,获取补全结果
        CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
        // 4.2.获取options
        List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
        // 4.3.遍历
        List<String> list = new ArrayList<>(options.size());
        for (CompletionSuggestion.Entry.Option option : options) {
            String text = option.getText().toString();
            list.add(text);
        }
        return list;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

SpringBoot 整合 EleasticSearch 8.0.X 弃用high-level-client

请参考

springboot集成elasticsearch 8.0以上版本_weixin_46501729的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Elasticsearch是一个分布式搜索和分析引擎,它提供了强大的全文搜索功能和实时数据分析能力。下面是一些常用的Elasticsearch使用方式和操作示例: 1. 索引数据:在Elasticsearch中,你需要将数据索引到一个或多个索引中。索引是一种结构化的数据存储方式,类似于数据库中的表。你可以使用Elasticsearch提供的API(如REST API或Elasticsearch客户端库)来索引数据。 ```java IndexRequest request = new IndexRequest("my_index"); // 指定索引名称 request.id("1"); // 指定文档ID request.source("field1", "value1", "field2", "value2"); // 指定字段和对应的值 IndexResponse response = client.index(request, RequestOptions.DEFAULT); ``` 2. 搜索数据:使用Elasticsearch进行全文搜索非常强大和灵活。你可以构建各种查询条件来匹配文档,并按照相关性进行排序和过滤。 ```java SearchRequest request = new SearchRequest("my_index"); // 指定索引名称 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.query(QueryBuilders.matchQuery("field", "value")); // 匹配字段和值 request.source(sourceBuilder); SearchResponse response = client.search(request, RequestOptions.DEFAULT); SearchHits hits = response.getHits(); for (SearchHit hit : hits) { String id = hit.getId(); Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 处理搜索结果 } ``` 3. 聚合和分析:Elasticsearch提供了丰富的聚合功能,用于对数据进行分组、计数、求和等操作,以便进行数据分析和可视化。 ```java SearchRequest request = new SearchRequest("my_index"); // 指定索引名称 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_field").field("field"); // 按字段进行分组 sourceBuilder.aggregation(aggregation); request.source(sourceBuilder); SearchResponse response = client.search(request, RequestOptions.DEFAULT); Terms terms = response.getAggregations().get("by_field"); for (Terms.Bucket bucket : terms.getBuckets()) { String key = bucket.getKeyAsString(); long docCount = bucket.getDocCount(); // 处理聚合结果 } ``` 4. 更新和删除数据:如果你需要更新或删除索引中的文档,可以使用相应的API进行操作。 ```java UpdateRequest request = new UpdateRequest("my_index", "1"); // 指定索引名称和文档ID request.doc("field", "new_value"); // 更新字段的值 UpdateResponse response = client.update(request, RequestOptions.DEFAULT); ``` ```java DeleteRequest request = new DeleteRequest("my_index", "1"); // 指定索引名称和文档ID DeleteResponse response = client.delete(request, RequestOptions.DEFAULT); ``` 这些只是Elasticsearch的一些基本使用方式和操作示例,实际使用中还有更多的功能和配置选项可供探索。你可以参考Elasticsearch的官方文档和API参考来深入了解和使用Elasticsearch。祝你使用Elasticsearch愉快!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值