【微服务全家桶】-实用篇-4.2-ES-下篇

【微服务全家桶】-实用篇-4.2-ES-下篇


1 DSL查询语法

在这里插入图片描述

1.1 DSL Query基本语法

在这里插入图片描述

1.1.1 查询所有match_all

#查询所有
GET /hotel/_search
{
  "query": {
    "match_all": {}
  }
}

1.1.2 全文检索查询

1.1.2.1 match查询

在这里插入图片描述

# match查询
GET /hotel/_search
{
  "query": {
    "match": {
      "all": "外滩"
    }
  }
}

只有两个匹配的

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 4.9931827,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "60487",
        "_score" : 4.9931827,
        "_source" : {
          "address" : "黄浦路199号",
          "brand" : "君悦",
          "business" : "外滩地区",
          "city" : "上海",
          "id" : 60487,
          "location" : "31.245409, 121.492969",
          "name" : "上海外滩茂悦大酒店",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/2Swp2h1fdj9zCUKsk63BQvVgKLTo_w200_h200_c1_t0.jpg",
          "price" : 689,
          "score" : 44,
          "starName" : "五星级"
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "432335",
        "_score" : 4.2893023,
        "_source" : {
          "address" : "唐山路145号",
          "brand" : "7天酒店",
          "business" : "北外滩地区",
          "city" : "上海",
          "id" : 432335,
          "location" : "31.252585, 121.498753",
          "name" : "7天连锁酒店(上海北外滩国际客运中心地铁站店)",
          "pic" : "https://m2.tuniucdn.com/filebroker/cdn/res/c1/ba/c1baf64418437c56617f89840c6411ef_w200_h200_c1_t0.jpg",
          "price" : 249,
          "score" : 35,
          "starName" : "二钻"
        }
      }
    ]
  }
}

扩大到”外滩如家“

# match查询
GET /hotel/_search
{
  "query": {
    "match": {
      "all": "外滩"
    }
  }
}

先是准确匹配外滩如家,再是外滩然后如家

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 32,
      "relation" : "eq"
    },
    "max_score" : 4.9931827,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "60487",
        "_score" : 4.9931827,
        "_source" : {
          "address" : "黄浦路199号",
          "brand" : "君悦",
          "business" : "外滩地区",
          "city" : "上海",
          "id" : 60487,
          "location" : "31.245409, 121.492969",
          "name" : "上海外滩茂悦大酒店",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/2Swp2h1fdj9zCUKsk63BQvVgKLTo_w200_h200_c1_t0.jpg",
          "price" : 689,
          "score" : 44,
          "starName" : "五星级"
        }
      },
。。。
1.1.2.2 mult_match

允许同时查询多个字段,查询字段越多,性能越差

在这里插入图片描述

# match查询
GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "外滩如家",
      "fields": ["brand","name","business"]
    }
  }
}
{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 32,
      "relation" : "eq"
    },
    "max_score" : 4.620212,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "434082",
        "_score" : 4.620212,
        "_source" : {
          "address" : "复兴东路260号",
          "brand" : "如家",
          "business" : "豫园地区",
          "city" : "上海",
          "id" : 434082,
          "location" : "31.220706, 121.498769",
          "name" : "如家酒店·neo(上海外滩城隍庙小南门地铁站店)",
          "pic" : "https://m.tuniucdn.com/fb2/t1/G6/M00/52/B6/Cii-U13eXLGIdHFzAAIG-5cEwDEAAGRfQNNIV0AAgcT627_w200_h200_c1_t0.jpg",
          "price" : 392,
          "score" : 44,
          "starName" : "二钻"
        }
      },
。。。

1.1.3 精确查询

精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有:

1.1.3.1 term查询

在这里插入图片描述

#term查询
GET /hotel/_search
{
  "query":{
    "term": {
      "city": {
        "value": "上海"
      }
    }
  }
}

有83个精确匹配的

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 83,
      "relation" : "eq"
    },
    "max_score" : 0.88342106,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "36934",
        "_score" : 0.88342106,
        "_source" : {
          "address" : "静安交通路40号",
          "brand" : "7天酒店",
          "business" : "四川北路商业区",
          "city" : "上海",
          "id" : 36934,
          "location" : "31.251433, 121.47522",
          "name" : "7天连锁酒店(上海宝山路地铁站店)",
          "pic" : "https://m.tuniucdn.com/fb2/t1/G1/M00/3E/40/Cii9EVkyLrKIXo1vAAHgrxo_pUcAALcKQLD688AAeDH564_w200_h200_c1_t0.jpg",
          "price" : 336,
          "score" : 37,
          "starName" : "二钻"
        }
      },
。。。
1.1.3.2 range查询

在这里插入图片描述

gte 大于等于 lte小于等于

#range查询
GET /hotel/_search
{
  "query":{
    "range": {
      "price": {
        "gte": 350,
        "lte": 360
      }
    }
  }
}

查询价格大于等于350小于等于360

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "395434",
        "_score" : 1.0,
        "_source" : {
          "address" : "东三环北路东方路1号",
          "brand" : "希尔顿",
          "business" : "燕莎/朝阳公园商业区",
          "city" : "北京",
          "id" : 395434,
          "location" : "39.952703, 116.462387",
          "name" : "北京希尔顿酒店",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/3fwNbKGhk6XCrkdVyxwhC5uGpLVy_w200_h200_c1_t0.jpg",
          "price" : 350,
          "score" : 45,
          "starName" : "五星级"
        }
      },
。。。

1.1.4 地理查询

1.1.4.1 geo_bounding_box查询

在这里插入图片描述

1.1.4.2 geo_distance查询

在这里插入图片描述

#geo_bounding_box查询
GET /hotel/_search
{
  "query": {
    "geo_distance":{
      "distance":"5km",
      "location":"31.21,121.5"
    }
  }
}

5km范围内只有13家

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 13,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "56201",
        "_score" : 1.0,
        "_source" : {
          "address" : "东方路838号",
          "brand" : "万怡",
          "business" : "浦东陆家嘴金融贸易区",
          "city" : "上海",
          "id" : 56201,
          "location" : "31.226031, 121.525801",
          "name" : "上海齐鲁万怡大酒店",
          "pic" : "https://m.tuniucdn.com/fb2/t1/G6/M00/52/B6/Cii-TF3eXKeIJeN7AASiKHbTtx4AAGRegDSBzMABKJA111_w200_h200_c1_t0.jpg",
          "price" : 873,
          "score" : 44,
          "starName" : "四星级"
        }
      },
。。。

1.1.5 复合查询

在这里插入图片描述

1.1.5.1 相关性算分

在这里插入图片描述

BM25不会受词频影响更大

在这里插入图片描述

在这里插入图片描述

1.1.5.2 Function score Query

在这里插入图片描述

案例

在这里插入图片描述

#function_score查询
GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match":{"all":"上海外滩"}},
              "functions": [
        {
          "filter": {"term":{"brand":"7天酒店"}}, 
          "weight":"100"
        }
      ],
      "boost_mode":"sum"
    }
  }  
}
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 104.2893,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "432335",
        "_score" : 104.2893,
        "_source" : {
          "address" : "唐山路145号",
          "brand" : "7天酒店",
          "business" : "北外滩地区",
          "city" : "上海",
          "id" : 432335,
          "location" : "31.252585, 121.498753",
          "name" : "7天连锁酒店(上海北外滩国际客运中心地铁站店)",
          "pic" : "https://m2.tuniucdn.com/filebroker/cdn/res/c1/ba/c1baf64418437c56617f89840c6411ef_w200_h200_c1_t0.jpg",
          "price" : 249,
          "score" : 35,
          "starName" : "二钻"
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "60487",
        "_score" : 5.9931827,
        "_source" : {
          "address" : "黄浦路199号",
          "brand" : "君悦",
          "business" : "外滩地区",
          "city" : "上海",
          "id" : 60487,
          "location" : "31.245409, 121.492969",
          "name" : "上海外滩茂悦大酒店",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/2Swp2h1fdj9zCUKsk63BQvVgKLTo_w200_h200_c1_t0.jpg",
          "price" : 689,
          "score" : 44,
          "starName" : "五星级"
        }
      },
1.1.5.3 BooleanQuery

在这里插入图片描述

在这里插入图片描述

案例

在这里插入图片描述

2 搜索结果处理

2.1 排序

elasticsearch支持对搜索结果排序,默认是根据相关度算分(score)来排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

在这里插入图片描述

2.1.1 评价降序,价格升序

在这里插入图片描述

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "score": {
        "order": "desc"
      }
    },
    {
      "price": {
        "order": "asc"
      }
    }
  ]
}
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 201,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "2060618247",
        "_score" : null,
        "_source" : {
          "address" : "粤海街道后海社区后海第二统建楼商业裙楼第二层B",
          "brand" : "汉庭",
          "business" : "海岸城/后海",
          "city" : "深圳",
          "id" : 2060618247,
          "location" : "22.507276, 113.931251",
          "name" : "汉庭酒店(深圳海岸城店)",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/TBoXdgEx5Yjc2HobeC3fPWWnSJi_w200_h200_c1_t0.jpg",
          "price" : 562,
          "score" : 49,
          "starName" : "二钻"
        },
        "sort" : [
          49,
          562
        ]
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "1951709780",
        "_score" : null,
        "_source" : {
          "address" : "福海街道宝安大道 6259号",
          "brand" : "万怡",
          "business" : "深圳国际会展中心商圈",
          "city" : "深圳",
          "id" : 1951709780,
          "location" : "22.678611, 113.805695",
          "name" : "深圳同泰万怡酒店",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/3oUfktphxMAWq9hUxD9uqdjRdZGB_w200_h200_c1_t0.jpg",
          "price" : 617,
          "score" : 48,
          "starName" : "五钻"
        },
        "sort" : [
          48,
          617
        ]
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "2056105938",
        "_score" : null,
        "_source" : {
          "address" : "新华东街289号2号楼",
          "brand" : "希尔顿",
          "business" : "果园环岛/通州区",
          "city" : "北京",
          "id" : 2056105938,
          "location" : "39.908805, 116.659748",
          "name" : "北京通州北投希尔顿酒店",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/NGKdpec3tZJNUUNWJ5pd67Cp5AY_w200_h200_c1_t0.png",
          "price" : 1068,
          "score" : 48,
          "starName" : "五钻"
        },
        "sort" : [
          48,
          1068
        ]
      },
      。。。

2.1.2 位置坐标升序排序

找到 121.61,31.03距离的酒店,升序排序

在这里插入图片描述

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance": {
        "location": "31.03,121.61",
        "order": "asc",
        "unit":"km"
      }
    }
  ]
}
{
  "took" : 43,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 201,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "2056298828",
        "_score" : null,
        "_source" : {
          "address" : "沪南公路7688弄1号",
          "brand" : "万豪",
          "business" : "南汇/野生动物园",
          "city" : "上海",
          "id" : 2056298828,
          "location" : "31.030053, 121.662943",
          "name" : "上海中优城市万豪酒店",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/2gBATEyysyQWmw3wZL863HGdqjaq_w200_h200_c1_t0.jpg",
          "price" : 1200,
          "score" : 45,
          "starName" : "五钻"
        },
        "sort" : [
          5.044557687681683
        ]
      },

sort就是距离的远近

2.2 分页

在这里插入图片描述

#分页
GET /hotel/_search
{
  "query":{
    "match_all": {}
  },
  "from": 0,
  "size":2,
  "sort": [
    {
      "price": {
        "order": "asc"
      }
    }
  ]
}
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 201,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "197837109",
        "_score" : null,
        "_source" : {
          "address" : "布吉镇深惠路龙珠商城",
          "brand" : "如家",
          "business" : "布吉/深圳东站",
          "city" : "深圳",
          "id" : 197837109,
          "location" : "22.602482, 114.123284",
          "name" : "如家酒店·neo(深圳龙岗大道布吉地铁站店)",
          "pic" : "https://m.tuniucdn.com/fb2/t1/G6/M00/25/58/Cii-TF3PFZOIA7jwAAKInGFN4xgAAEVbAGeP4AAAoi0485_w200_h200_c1_t0.jpg",
          "price" : 127,
          "score" : 43,
          "starName" : "二钻"
        },
        "sort" : [
          127
        ]
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "2316304",
        "_score" : null,
        "_source" : {
          "address" : "龙岗街道龙岗墟社区龙平东路62号",
          "brand" : "如家",
          "business" : "龙岗中心区/大运新城",
          "city" : "深圳",
          "id" : 2316304,
          "location" : "22.730828, 114.278337",
          "name" : "如家酒店(深圳双龙地铁站店)",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/4AzEoQ44awd1D2g95a6XDtJf3dkw_w200_h200_c1_t0.jpg",
          "price" : 135,
          "score" : 45,
          "starName" : "二钻"
        },
        "sort" : [
          135
        ]
      }
    ]
  }
}

2.2.1 深度分页

在这里插入图片描述

在这里插入图片描述

2.3 高亮

在这里插入图片描述

#高亮
GET /hotel/_search
{
  "query":{
    "match": 
    {
      "all":"如家"
    }
  },
  "highlight": 
  {
    "fields": 
    {
      "name": {
        "require_field_match": "false"
      }
    }
  }
}

在es中,高亮必须用match进行匹配,且默认情况下,es搜索字段必须与高亮字段一致才会高亮,故"require_field_match": "false"

{
  "took" : 89,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 30,
      "relation" : "eq"
    },
    "max_score" : 2.4133554,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "200215365",
        "_score" : 2.4133554,
        "_source" : {
          "address" : "虹梅路2971号",
          "brand" : "如家",
          "business" : "虹桥地区",
          "city" : "上海",
          "id" : 200215365,
          "location" : "31.180968, 121.392415",
          "name" : "如家酒店(上海虹桥漕河泾古北店)",
          "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/2WPfVp6auQkYoHzAdSbxwHAtQFfa_w200_h200_c1_t0.jpg",
          "price" : 189,
          "score" : 44,
          "starName" : "二钻"
        },
        "highlight" : {
          "name" : [
            "<em>如家</em>酒店(上海虹桥漕河泾古北店)"
          ]
        }
      },

3 RestClient查询文档

3.1 快速入门

在这里插入图片描述

    @Test
    void testMatchAll() throws IOException {
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.matchAllQuery());
        //2.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.解析结果
        SearchHits searchHits= response.getHits();
        //3.1 打印总记录数
        long total = searchHits.getTotalHits().value;
        //3.2 保存结果数组
        SearchHit[] hits = searchHits.getHits();
        //3.3 遍历结果
        for (SearchHit hit : hits) {
            String json= hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }

在这里插入图片描述

打印结果

HotelDoc(id=36934, name=7天连锁酒店(上海宝山路地铁站店), address=静安交通路40号, price=336, score=37, brand=7天酒店, city=上海, starName=二钻, business=四川北路商业区, location=31.251433, 121.47522, pic=https://m.tuniucdn.com/fb2/t1/G1/M00/3E/40/Cii9EVkyLrKIXo1vAAHgrxo_pUcAALcKQLD688AAeDH564_w200_h200_c1_t0.jpg)
HotelDoc(id=38609, name=速8酒店(上海赤峰路店), address=广灵二路126号, price=249, score=35, brand=速8, city=上海, starName=二钻, business=四川北路商业区, location=31.282444, 121.479385, pic=https://m.tuniucdn.com/fb2/t1/G2/M00/DF/96/Cii-TFkx0ImIQZeiAAITil0LM7cAALCYwKXHQ4AAhOi377_w200_h200_c1_t0.jpg)
HotelDoc(id=38665, name=速8酒店上海中山北路兰田路店, address=兰田路38号, price=226, score=35, brand=速8, city=上海, starName=二钻, business=长风公园地区, location=31.244288, 121.422419, pic=https://m.tuniucdn.com/fb2/t1/G2/M00/EF/86/Cii-Tlk2mV2IMZ-_AAEucgG3dx4AALaawEjiycAAS6K083_w200_h200_c1_t0.jpg)
。。。

request.source().query(QueryBuilders.matchAllQuery());

source提供了对查询结果的处理:query,sort,highligt,from,size这些

在这里插入图片描述

QueryBuilders提供了各种各样的查询:match、match all、boolquery、term、range这些

在这里插入图片描述

抽取静态方法handleResponse来处理响应结果

    private static void handleResponse(SearchResponse response) {
        //3.解析结果
        SearchHits searchHits= response.getHits();
        //3.1 打印总记录数
        long total = searchHits.getTotalHits().value;
        System.out.println("总记录数:"+total);
        //3.2 保存结果数组
        SearchHit[] hits = searchHits.getHits();
        //3.3 遍历结果
        for (SearchHit hit : hits) {
            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);
        }
    }

3.2 match、term…

3.2.1 全文查询

在这里插入图片描述

    @Test
    void testMatch() throws IOException {
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        //单字段匹配
        request.source().query(QueryBuilders.matchQuery("all","如家"));
        //多字段匹配
        //request.source().query(QueryBuilders.multiMatchQuery("如家","name","brand","business"));

        //2.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.解析结果
        handleResponse(response);
    }
总记录数:30
HotelDoc(id=200215365, name=如家酒店(上海虹桥漕河泾古北店), address=虹梅路2971号, price=189, score=44, brand=如家, city=上海, starName=二钻, business=虹桥地区, location=31.180968, 121.392415, pic=https://m.tuniucdn.com/fb3/s1/2n9c/2WPfVp6auQkYoHzAdSbxwHAtQFfa_w200_h200_c1_t0.jpg)
HotelDoc(id=234719711, name=如家酒店·neo(北京朝阳北路十里堡地铁站店), address=朝阳北路八里庄南里26号, price=378, score=47, brand=如家, city=北京, starName=二钻, business=国贸地区, location=39.922472, 116.501118, pic=https://m.tuniucdn.com/fb3/s1/2n9c/2rHdXNCmycnUxw99AniFC25ZDSfJ_w200_h200_c1_t0.jpg)
HotelDoc(id=434082, name=如家酒店·neo(上海外滩城隍庙小南门地铁站店), address=复兴东路260号, price=392, score=44, brand=如家, city=上海, starName=二钻, business=豫园地区, location=31.220706, 121.498769, pic=https://m.tuniucdn.com/fb2/t1/G6/M00/52/B6/Cii-U13eXLGIdHFzAAIG-5cEwDEAAGRfQNNIV0AAgcT627_w200_h200_c1_t0.jpg)

3.2.2 精确查询

在这里插入图片描述

    //精确查询
    @Test
    void testTermRange() throws IOException {
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        //词条查询
        request.source().query(QueryBuilders.termQuery("city","杭州"));
        //范围查询
        request.source().query(QueryBuilders.rangeQuery("price").gte(200).lte(300));

        //2.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.解析结果
        handleResponse(response);
    }
总记录数:33
HotelDoc(id=38609, name=速8酒店(上海赤峰路店), address=广灵二路126号, price=249, score=35, brand=速8, city=上海, starName=二钻, business=四川北路商业区, location=31.282444, 121.479385, pic=https://m.tuniucdn.com/fb2/t1/G2/M00/DF/96/Cii-TFkx0ImIQZeiAAITil0LM7cAALCYwKXHQ4AAhOi377_w200_h200_c1_t0.jpg)
HotelDoc(id=38665, name=速8酒店上海中山北路兰田路店, address=兰田路38号, price=226, score=35, brand=速8, city=上海, starName=二钻, business=长风公园地区, location=31.244288, 121.422419, pic=https://m.tuniucdn.com/fb2/t1/G2/M00/EF/86/Cii-Tlk2mV2IMZ-_AAEucgG3dx4AALaawEjiycAAS6K083_w200_h200_c1_t0.jpg)
HotelDoc(id=38812, name=7天连锁酒店(上海漕溪路地铁站店), address=徐汇龙华西路315弄58号, price=298, score=37, brand=7天酒店, city=上海, starName=二钻, business=八万人体育场地区, location=31.174377, 121.442875, pic=https://m.tuniucdn.com/fb2/t1/G2/M00/E0/0E/Cii-TlkyIr2IEWNoAAHQYv7i5CkAALD-QP2iJwAAdB6245_w200_h200_c1_t0.jpg)

3.2.3 复合查询

    //复合查询
    @Test
    void testCompose() throws IOException {
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        //布尔查询
        request.source().query(QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("name","如家"))
                .mustNot(QueryBuilders.rangeQuery("price").gte(400))
                .filter(QueryBuilders.geoDistanceQuery("location")
                        .point(31.03,121.61)
                        .distance("50km")))
                .sort("price", SortOrder.ASC);

        //2.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.解析结果
        handleResponse(response);
    }
总记录数:10
HotelDoc(id=541619, name=如家酒店(上海莘庄地铁站龙之梦商业广场店), address=莘庄镇莘浜路172号, price=149, score=44, brand=如家, city=上海, starName=二钻, business=莘庄工业区, location=31.105797, 121.37755, pic=https://m.tuniucdn.com/fb3/s1/2n9c/3mKs3jETvJDj3dDdkRB9UyLLvPna_w200_h200_c1_t0.jpg)
HotelDoc(id=608374, name=如家酒店(上海浦东机场龙东大道合庆店), address=东川公路5863号, price=160, score=45, brand=如家, city=上海, starName=二钻, business=浦东机场核心区, location=31.237662, 121.718556, pic=https://m.tuniucdn.com/fb3/s1/2n9c/LUYxGGV4pzjKeN5a69K4deU8JD8_w200_h200_c1_t0.jpg)
HotelDoc(id=485775, name=如家酒店(上海闵行华东师范大学吴泾店), address=吴泾镇宝秀路977号, price=161, score=45, brand=如家, city=上海, starName=二钻, business=交大/闵行经济开发区, location=31.047135, 121.46224, pic=https://m.tuniucdn.com/fb3/s1/2n9c/V8pz15CkiMX5xYJRmbbp5zkKWJ8_w200_h200_c1_t0.jpg)

3.3 排序分页

在这里插入图片描述

    //分页查询
    @Test
    void testPage() throws IOException {
        int page=1;
        int size=3;
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        //词条查询
        request.source().query(QueryBuilders.termQuery("city","上海"))
                .from((page-1)*size).size(size);

        //2.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.解析结果
        handleResponse(response);
    }
总记录数:83
HotelDoc(id=36934, name=7天连锁酒店(上海宝山路地铁站店), address=静安交通路40号, price=336, score=37, brand=7天酒店, city=上海, starName=二钻, business=四川北路商业区, location=31.251433, 121.47522, pic=https://m.tuniucdn.com/fb2/t1/G1/M00/3E/40/Cii9EVkyLrKIXo1vAAHgrxo_pUcAALcKQLD688AAeDH564_w200_h200_c1_t0.jpg)
HotelDoc(id=38609, name=速8酒店(上海赤峰路店), address=广灵二路126号, price=249, score=35, brand=速8, city=上海, starName=二钻, business=四川北路商业区, location=31.282444, 121.479385, pic=https://m.tuniucdn.com/fb2/t1/G2/M00/DF/96/Cii-TFkx0ImIQZeiAAITil0LM7cAALCYwKXHQ4AAhOi377_w200_h200_c1_t0.jpg)
HotelDoc(id=38665, name=速8酒店上海中山北路兰田路店, address=兰田路38号, price=226, score=35, brand=速8, city=上海, starName=二钻, business=长风公园地区, location=31.244288, 121.422419, pic=https://m.tuniucdn.com/fb2/t1/G2/M00/EF/86/Cii-Tlk2mV2IMZ-_AAEucgG3dx4AALaawEjiycAAS6K083_w200_h200_c1_t0.jpg)

3.4 高亮

在这里插入图片描述

在这里插入图片描述

    //高亮
    @Test
    void testHighlight() throws IOException {
        int page=1;
        int size=3;
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        //词条查询
        request.source().query(QueryBuilders.matchQuery("all","如家"))
                .from((page-1)*size).size(size)
                .highlighter(new HighlightBuilder().field("name")
                        .requireFieldMatch(false));

        //2.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.解析结果-高亮解析需要重新解析
        SearchHits searchHits= response.getHits();
        SearchHit[] hits = searchHits.getHits();
        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);
        }
    }

4 黑马旅游

使用时使用localhost:8089访问端口

4.1 完成搜索和分页

在这里插入图片描述

在这里插入图片描述

4.1.1 定义实体类接收前端参数

在这里插入图片描述

在pojo中定义RequestParams

@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
}

4.1.2 定义controller接收前端参数

在这里插入图片描述

先在pojo中创建实体类PageResult

@Data
public class PageResult {
    private Long total;
    private List<HotelDoc> hotels;

    public PageResult() {
        this.total = 0L;
        this.hotels = new ArrayList<>();
    }

    public PageResult(Long total, List<HotelDoc> hotels) {
        this.total = total;
        this.hotels = hotels;
    }
}

再创建controller

@RestController
@RequestMapping("/hotel")
public class HotelController {

    @Autowired
    private IHotelService hotelService;

    @PostMapping("/list")
    //json风格用@RequestBody
    public PageResult search(@RequestBody RequestParams params) {
        return hotelService.search(params);
    }
    
}

再实现 hotelService.search,因为后面发送请求会用到this.client = new RestHighLevelClient,因此把他作为一个Bean注入到启动类中是最好的

@MapperScan("cn.itcast.hotel.mapper")
@SpringBootApplication
public class HotelDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(HotelDemoApplication.class, args);
    }

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        return new RestHighLevelClient(RestClient.builder(
                                HttpHost.create("http://192.168.204.129:9200")));
    }
}

然后在IhotelService的实现类hotelService中可以直接注入RestHighLevelClient

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    private static PageResult handleResponse(SearchResponse response) {
        //3.解析结果
        SearchHits searchHits= response.getHits();
        //3.1 打印总记录数
        long total = searchHits.getTotalHits().value;
        //3.2 保存结果数组
        SearchHit[] hits = searchHits.getHits();
        List<HotelDoc> hotelDocs = new ArrayList<>();
        //3.3 遍历结果
        for (SearchHit hit : hits) {
            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);
                }
            }
            hotelDocs.add(hotelDoc);
        }
        return new PageResult(total,hotelDocs);
    }

    @Override
    //全文检索
    public PageResult search(RequestParams params) {
        try {
            //1.准备Request
            SearchRequest request = new SearchRequest("hotel");
            //2.准备DSL
            //2.1 query
            String key= params.getKey();
            if(key==null||"".equals(key)){
                request.source().query(QueryBuilders.matchAllQuery());
            }
            else{
                request.source().query(QueryBuilders.matchQuery("all",key));
            }
            //2.2 分页
            int page = params.getPage();
            int size = params.getSize();
            request.source().from((page-1)*size).size(size);
            //2.3 排序
            String sortBy = params.getSortBy();
            if(sortBy!=null&&!"default".equals(sortBy)){
                if("price".equals(sortBy)){
                    request.source().sort(sortBy, SortOrder.ASC);
                }
                else{
                    request.source().sort(sortBy, SortOrder.DESC);
                }
            }
            //3.发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4.解析结果
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

4.2 添加过滤功能

在这里插入图片描述

4.2.1 扩展RequestParams类

@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String brand;
    private String starName;
    private String minPrice;
    private String maxPrice;
    private String city;

}

4.2.2 修改search方法添加过滤条件

在这里插入图片描述

    //构建基本查询
    private static void buildBasicQuery(RequestParams params, SearchRequest request) {
        //2.1 query
        //构建BoolQuery
        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&&!"".equals(params.getCity())){
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        //品牌
        if(params.getBrand()!=null&&!"".equals(params.getBrand())){
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        //星级
        if(params.getStarName()!=null&&!"".equals(params.getStarName())){
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        //价格
        if(params.getMinPrice()!=null&&!"".equals(params.getMinPrice())){
            boolQuery.filter(QueryBuilders.rangeQuery("price")
                    .gte(params.getMinPrice())
                    .lte(params.getMaxPrice()));
        }
        request.source().query(boolQuery);
    }

    @Override
    //全文检索
    public PageResult search(RequestParams params) {
        try {
            //1.准备Request
            SearchRequest request = new SearchRequest("hotel");
            //2.准备DSL
            buildBasicQuery(params, request);

            //2.2 分页
            int page = params.getPage();
            int size = params.getSize();
            request.source().from((page-1)*size).size(size);
            //2.3 排序
            String sortBy = params.getSortBy();
            if(sortBy!=null&&!"default".equals(sortBy)){
                if("price".equals(sortBy)){
                    request.source().sort(sortBy, SortOrder.ASC);
                }
                else{
                    request.source().sort(sortBy, SortOrder.DESC);
                }
            }
            //3.发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4.解析结果
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

4.3 搜索附近酒店

在这里插入图片描述

在这里插入图片描述

4.3.1 扩展RequestParams类

@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String brand;
    private String starName;
    private String minPrice;
    private String maxPrice;
    private String city;
    private String location;
}

4.3.2 修改排序条件

    @Override
    //全文检索
    public PageResult search(RequestParams params) {
        try {
            //1.准备Request
            SearchRequest request = new SearchRequest("hotel");
            //2.准备DSL
            buildBasicQuery(params, request);

            //2.2 分页
            int page = params.getPage();
            int size = params.getSize();
            request.source().from((page-1)*size).size(size);
            //2.3 排序
            //地理排序,unit单位
            if(params.getLocation()!=null&&!"".equals(params.getLocation())){
                request.source().sort(SortBuilders
                        .geoDistanceSort("location",new GeoPoint(params.getLocation()))
                        .order(SortOrder.ASC)
                        .unit(DistanceUnit.KILOMETERS));
            }
            String sortBy = params.getSortBy();
            if(sortBy!=null&&!"default".equals(sortBy)){
                if("price".equals(sortBy)){
                    request.source().sort(sortBy, SortOrder.ASC);
                }
                else{
                    request.source().sort(sortBy, SortOrder.DESC);
                }
            }
            //3.发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4.解析结果
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

还想要拿到具体举例,就要修改结果解析

            //获取距离
            Object[] sortValues = hit.getSortValues();
            if(sortValues!=null&&sortValues.length>0){
                hotelDoc.setDistance((Double) sortValues[0]);
            }
    //响应结果处理
    private static PageResult handleResponse(SearchResponse response) {
        //3.解析结果
        SearchHits searchHits= response.getHits();
        //3.1 打印总记录数
        long total = searchHits.getTotalHits().value;
        //3.2 保存结果数组
        SearchHit[] hits = searchHits.getHits();
        List<HotelDoc> hotelDocs = new ArrayList<>();
        //3.3 遍历结果
        for (SearchHit hit : hits) {
            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);
                }
            }
            //获取距离
            Object[] sortValues = hit.getSortValues();
            if(sortValues!=null&&sortValues.length>0){
                hotelDoc.setDistance((Double) sortValues[0]);
            }

            hotelDocs.add(hotelDoc);
        }
        return new PageResult(total,hotelDocs);
    }

我们返回的结果是HotelDoc,所以要新添加一个distance属性

@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;

默认按距离进行排序

在这里插入图片描述

4.4 广告置顶

在这里插入图片描述

4.4.1 添加isAD字段

扩展HotelDoc类

@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;

4.4.2 随机为酒店添加isAD字段

POST /hotel/_update/2051661320
{
  "doc":{
    "isAD":true
  }
}

POST /hotel/_update/2048042240
{
  "doc":{
    "isAD":true
  }
}
POST /hotel/_update/1997830708
{
  "doc":{
    "isAD":true
  }
}
POST /hotel/_update/1734220581
{
  "doc":{
    "isAD":true
  }
}

4.4.3 修改search方法

function_score

在这里插入图片描述

        //算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                        boolQuery,
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        QueryBuilders.termQuery("isAD", true),
                                        ScoreFunctionBuilders.weightFactorFunction(1000)
                                )
                        }).boostMode(CombineFunction.SUM);
    //构建基本查询
    private static void buildBasicQuery(RequestParams params, SearchRequest request) {
        //原始查询
        //2.1 query
        //构建BoolQuery
        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&&!"".equals(params.getCity())){
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        //品牌
        if(params.getBrand()!=null&&!"".equals(params.getBrand())){
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        //星级
        if(params.getStarName()!=null&&!"".equals(params.getStarName())){
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        //价格
        if(params.getMinPrice()!=null&&!"".equals(params.getMinPrice())){
            boolQuery.filter(QueryBuilders.rangeQuery("price")
                    .gte(params.getMinPrice())
                    .lte(params.getMaxPrice()));
        }

        //算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                        boolQuery,
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        QueryBuilders.termQuery("isAD", true),
                                        ScoreFunctionBuilders.weightFactorFunction(1000)
                                )
                        }).boostMode(CombineFunction.SUM);
        
        request.source().query(functionScoreQuery);
    }

5 深入es

5.1 数据聚合

在这里插入图片描述

参与聚合的字段必须是:keyword、数值、日期、布尔

5.1.1 Bucket聚合

在这里插入图片描述

POST /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAggs": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}

因为不想看文档所以hits的数组是空

doc_count是品牌里的酒店个数,默认按统计数量排序

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 201,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brandAggs" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 96,
      "buckets" : [
        {
          "key" : "7天酒店",
          "doc_count" : 30
        },
        {
          "key" : "如家",
          "doc_count" : 30
        },
        {
          "key" : "皇冠假日",
          "doc_count" : 17
        },
        {
          "key" : "速8",
          "doc_count" : 15
        },
        {
          "key" : "万怡",
          "doc_count" : 13
        }
      ]
    }
  }
}

在这里插入图片描述

POST /hotel/_search
{
  "size": 0,
  "aggs": {
    "brandAggs": {
      "terms": {
        "field": "brand",
        "order": {
          "_count": "asc"
        }, 
        "size": 5
      }
    }
  }
}
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 201,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brandAggs" : {
      "doc_count_error_upper_bound" : -1,
      "sum_other_doc_count" : 180,
      "buckets" : [
        {
          "key" : "万丽",
          "doc_count" : 2
        },
        {
          "key" : "丽笙",
          "doc_count" : 2
        },
        {
          "key" : "君悦",
          "doc_count" : 4
        },
        {
          "key" : "豪生",
          "doc_count" : 6
        },
        {
          "key" : "维也纳",
          "doc_count" : 7
        }
      ]
    }
  }
}

在这里插入图片描述

POST /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 150,
        "lte": 200
      }
    }
  }, 
  "size": 0,
  "aggs": {
    "brandAggs": {
      "terms": {
        "field": "brand",
        "order": {
          "_count": "desc"
        }, 
        "size": 5
      }
    }
  }
}
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 12,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brandAggs" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "如家",
          "doc_count" : 9
        },
        {
          "key" : "速8",
          "doc_count" : 2
        },
        {
          "key" : "汉庭",
          "doc_count" : 1
        }
      ]
    }
  }
}

在这里插入图片描述

5.1.2 Metric聚合

在这里插入图片描述

#metric聚合
POST /hotel/_search
{
  "size":0,
  "aggs": {
    "brandAggs": {
      "terms": {
        "field": "brand",
        "size": 5,
        "order": {
          "score_stats.avg": "desc"
        }
      },
      "aggs":{
        "score_stats":{
          "stats": {
            "field": "score"
          }
        }
      }
    }
  }
}

对stats的结果进行排序,在bucket中进行排序,stats的结果通过.来对属性进行访问,score_stats.avg

{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 201,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brandAggs" : {
      "doc_count_error_upper_bound" : -1,
      "sum_other_doc_count" : 166,
      "buckets" : [
        {
          "key" : "万丽",
          "doc_count" : 2,
          "score_stats" : {
            "count" : 2,
            "min" : 46.0,
            "max" : 47.0,
            "avg" : 46.5,
            "sum" : 93.0
          }
        },
        {
          "key" : "凯悦",
          "doc_count" : 8,
          "score_stats" : {
            "count" : 8,
            "min" : 45.0,
            "max" : 47.0,
            "avg" : 46.25,
            "sum" : 370.0
          }
        },
        {
          "key" : "和颐",
          "doc_count" : 12,
          "score_stats" : {
            "count" : 12,
            "min" : 44.0,
            "max" : 47.0,
            "avg" : 46.083333333333336,
            "sum" : 553.0
          }
        },
        {
          "key" : "丽笙",
          "doc_count" : 2,
          "score_stats" : {
            "count" : 2,
            "min" : 46.0,
            "max" : 46.0,
            "avg" : 46.0,
            "sum" : 92.0
          }
        },
        {
          "key" : "喜来登",
          "doc_count" : 11,
          "score_stats" : {
            "count" : 11,
            "min" : 44.0,
            "max" : 48.0,
            "avg" : 46.0,
            "sum" : 506.0
          }
        }
      ]
    }
  }
}

5.1.3 RestClient实现聚合

在这里插入图片描述

    //聚合查询
    @Test
    void testAgg() throws IOException {
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        //2.准备DSL
        request.source().size(0);
        request.source().aggregation(AggregationBuilders.terms("brandAgg")
                .field("brand")
                .size(5));
        //3.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4.解析结果
        handleResponse(response);
    }

解析结果

        Aggregations aggregations = response.getAggregations();
        if(aggregations!=null){
            System.out.println("聚合结果:");
            //获取指定名称的聚合
            Terms brandTerms = aggregations.get("brandAgg");
            //获取桶
            List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
            //遍历桶
            for (Terms.Bucket bucket : buckets) {
                System.out.println(bucket.getKeyAsString()+":"+bucket.getDocCount());
            }
        }
    private static void handleResponse(SearchResponse response) {
        //3.解析结果
        SearchHits searchHits= response.getHits();
        //3.1 打印总记录数
        long total = searchHits.getTotalHits().value;
        System.out.println("总记录数:"+total);
        //3.2 保存结果数组
        SearchHit[] hits = searchHits.getHits();
        if (hits.length > 0) {
            //3.3 遍历结果
            for (SearchHit hit : hits) {
                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);
            }
        }
        Aggregations aggregations = response.getAggregations();
        if(aggregations!=null){
            System.out.println("聚合结果:");
            //获取指定名称的聚合
            Terms brandTerms = aggregations.get("brandAgg");
            //获取桶
            List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
            //遍历桶
            for (Terms.Bucket bucket : buckets) {
                System.out.println(bucket.getKeyAsString()+":"+bucket.getDocCount());
            }
        }
    }

对聚合结果进行stats

    @Test
    void testStats() throws IOException {
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        //2.准备DSL
        request.source().size(0);
        request.source().aggregation(AggregationBuilders.terms("brandAgg")
                .field("brand")
                .size(5).subAggregation(AggregationBuilders.stats("score_stats").field("score")));
        //3.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4.解析结果
        handleResponse(response);
    }

解析结果

        Aggregations aggregations = response.getAggregations();
        if(aggregations!=null){
            System.out.println("聚合结果:");
            //获取指定名称的聚合
            Terms brandTerms = aggregations.get("brandAgg");
            //获取桶
            List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
            //遍历桶
            for (Terms.Bucket bucket : buckets) {
                System.out.println(bucket.getKeyAsString()+":"+bucket.getDocCount());
                Stats stats = bucket.getAggregations().get("score_stats");
                if(stats!=null){
                    System.out.println("数目:"+stats.getCount());
                    System.out.println("最大值:"+stats.getMax());
                    System.out.println("最小值:"+stats.getMin());
                    System.out.println("平均值:"+stats.getAvg());
                    System.out.println("总和:"+stats.getSum());
                }
            }
        }

5.1.4 案例

在这里插入图片描述

实现

    @Override
    public Map<String, List<String>> filters() {
        //1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        //2.准备DSL
        request.source().size(0);
        request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand"));
        request.source().aggregation(AggregationBuilders.terms("starNameAgg").field("starName"));
        request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city"));

        //3.发送请求
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //4.解析结果
            Aggregations aggregations = response.getAggregations();
            Map<String, List<String>> filters = new HashMap<>();
            Terms cityAgg = aggregations.get("cityAgg");
            if(cityAgg!=null){
                List<? extends Terms.Bucket> buckets = cityAgg.getBuckets();
                List<String> cities = new ArrayList<>();
                for (Terms.Bucket bucket : buckets) {
                    cities.add(bucket.getKeyAsString());
                }
                filters.put("城市",cities);
            }
            Terms brandAgg = aggregations.get("brandAgg");
            if(brandAgg!=null){
                List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
                List<String> brands = new ArrayList<>();
                for (Terms.Bucket bucket : buckets) {
                    brands.add(bucket.getKeyAsString());
                }
                filters.put("品牌",brands);
            }
            Terms starNameAgg = aggregations.get("starNameAgg");
            if(starNameAgg!=null){
                List<? extends Terms.Bucket> buckets = starNameAgg.getBuckets();
                List<String> starNames = new ArrayList<>();
                for (Terms.Bucket bucket : buckets) {
                    starNames.add(bucket.getKeyAsString());
                }
                filters.put("星级",starNames);
            }
            return filters;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

在单元测试中测试一下

@SpringBootTest
class HotelDemoApplicationTests {

    @Autowired
    private IHotelService hotelService;

    @Test
    void contextLoads() {
        Map<String, List<String>> filters = hotelService.filters();
        System.out.println(filters);
    }

}

输出成功

{品牌=[7天酒店, 如家, 皇冠假日, 速8, 万怡, 华美达, 和颐, 万豪, 喜来登, 希尔顿], 星级=[二钻, 五钻, 四钻, 五星级, 三钻, 四星级], 城市=[上海, 北京, 深圳]}

在这里插入图片描述

    @Override
    public Map<String, List<String>> filters(RequestParams params) {
        //1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        //2.准备DSL
        //2.1 设置query信息
        buildBasicQuery(params, request);
        request.source().size(0);
        request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand"));
        request.source().aggregation(AggregationBuilders.terms("starNameAgg").field("starName"));
        request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city"));

        //3.发送请求
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //4.解析结果
            Aggregations aggregations = response.getAggregations();
            Map<String, List<String>> filters = new HashMap<>();
            Terms cityAgg = aggregations.get("cityAgg");
            if(cityAgg!=null){
                List<? extends Terms.Bucket> buckets = cityAgg.getBuckets();
                List<String> cities = new ArrayList<>();
                for (Terms.Bucket bucket : buckets) {
                    cities.add(bucket.getKeyAsString());
                }
                filters.put("城市",cities);
            }
            Terms brandAgg = aggregations.get("brandAgg");
            if(brandAgg!=null){
                List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
                List<String> brands = new ArrayList<>();
                for (Terms.Bucket bucket : buckets) {
                    brands.add(bucket.getKeyAsString());
                }
                filters.put("品牌",brands);
            }
            Terms starNameAgg = aggregations.get("starNameAgg");
            if(starNameAgg!=null){
                List<? extends Terms.Bucket> buckets = starNameAgg.getBuckets();
                List<String> starNames = new ArrayList<>();
                for (Terms.Bucket bucket : buckets) {
                    starNames.add(bucket.getKeyAsString());
                }
                filters.put("星级",starNames);
            }
            return filters;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

filters与list的基础查询条件一致,保持动态过滤

在这里插入图片描述

在这里插入图片描述

5.2 自动补全

在这里插入图片描述

py上传

在这里插入图片描述

测试

POST /_analyze
{
  "text":["如家酒店真棒啊"],
  "analyzer": "pinyin"
}
{
  "tokens" : [
    {
      "token" : "ru",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "rjjdzba",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "jia",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "jiu",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "dian",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "zhen",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 4
    },
    {
      "token" : "bang",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 5
    },
    {
      "token" : "a",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 6
    }
  ]
}

5.2.1 自定义分词器

在这里插入图片描述

在这里插入图片描述

自定义分词器在mapping映射时才有用

// 自定义拼音分词器
PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": { 
          "tokenizer": "ik_max_word",
          "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": {
      "name":{
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}
POST /test/_analyze
{
  "text":["如家酒店真棒啊"],
  "analyzer": "my_analyzer"
}
{
  "tokens" : [
    {
      "token" : "如家",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "rujia",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "rj",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "酒店",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "jiudian",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "jd",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "真棒",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "zhenbang",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "zb",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "啊",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "a",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "CN_CHAR",
      "position" : 3
    }
  ]
}

5.2.2 健壮分词器

POST /test/_doc/1
{
  "id": 1,
  "name": "狮子"
}
POST /test/_doc/2
{
  "id": 2,
  "name": "虱子"
}

GET /test/_search
{
  "query": {
    "match": {
      "name": "掉入狮子笼咋办"
    }
  }
}
{
  "took" : 25,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.33425623,
    "hits" : [
      {
        "_index" : "test",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.33425623,
        "_source" : {
          "id" : 1,
          "name" : "狮子"
        }
      },
      {
        "_index" : "test",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.3085442,
        "_source" : {
          "id" : 2,
          "name" : "虱子"
        }
      }
    ]
  }
}

都搜到狮子、虱子

在这里插入图片描述

在这里插入图片描述

// 自定义拼音分词器
PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": { 
          "tokenizer": "ik_max_word",
          "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": {
      "name":{
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

这回只能搜到狮子了

{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.9530773,
    "hits" : [
      {
        "_index" : "test",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.9530773,
        "_source" : {
          "id" : 1,
          "name" : "狮子"
        }
      }
    ]
  }
}

5.2.3 completion suggester查询

在这里插入图片描述

插入数组,既可以通过S进行补全,也可以通过W进行补全

创建自动补全的索引库

PUT test2
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"
      }
    }
  }
}

插入数据

POST test2/_doc
{
  "title": ["Sony", "WH-1000XM3"]
}
POST test2/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test2/_doc
{
  "title": ["Nintendo", "switch"]
}

自动补全查询

POST /test2/_search
{
  "suggest": {
    "title_suggest": {
      "text": "s", 
      "completion": {
        "field": "title", 
        "skip_duplicates": true, 
        "size": 10 
      }
    }
  }
}
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "suggest" : {
    "title_suggest" : [
      {
        "text" : "s",
        "offset" : 0,
        "length" : 1,
        "options" : [
          {
            "text" : "SK-II",
            "_index" : "test2",
            "_type" : "_doc",
            "_id" : "IhB-840Bh5MLZyMepJuZ",
            "_score" : 1.0,
            "_source" : {
              "title" : [
                "SK-II",
                "PITERA"
              ]
            }
          },
          {
            "text" : "Sony",
            "_index" : "test2",
            "_type" : "_doc",
            "_id" : "IRB-840Bh5MLZyMenJtW",
            "_score" : 1.0,
            "_source" : {
              "title" : [
                "Sony",
                "WH-1000XM3"
              ]
            }
          },
          {
            "text" : "switch",
            "_index" : "test2",
            "_type" : "_doc",
            "_id" : "IxB-840Bh5MLZyMerptN",
            "_score" : 1.0,
            "_source" : {
              "title" : [
                "Nintendo",
                "switch"
              ]
            }
          }
        ]
      }
    ]
  }
}

5.2.4 酒店数据自动补全DSL

在这里插入图片描述

创建hotel索引库结构,completion_analyzer是用于补全阶段的分词器,自动补全的词语作为整体出现,所以是keyword,之后用拼音处理

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"
      }
    }
  }
}

因为hotel中多了suggestion的字段,因此在poji中的HotelDoc也要添加

private List<String> suggestion;

因为我们希望在搜索品牌或者商圈的时候,有自动补全,因此在构造函数时,将其放入,对于存在多个商圈的,用、/分割

        if(this.business.contains("/")||this.business.contains("、")){
            this.suggestion=new ArrayList<>();
            this.suggestion.add(this.brand);
            //需要切割
            String arr1[]=this.business.split("[、/]");
            Collections.addAll(this.suggestion, arr1);
        }
        else{
            this.suggestion= Arrays.asList(this.brand, this.business);
        }

重新运行单元测试,批处理

    @Test
    void testBatchInsertIndexDocument2() throws IOException {

        //1.创建BulkRequest对象
        BulkRequest request = new BulkRequest();
        //2.准备数据,添加到BulkRequest对象中
        //查询所有酒店数据
        hotelService.list().forEach(hotel -> {
            //转换为文档类型
            HotelDoc hotelDoc = new HotelDoc(hotel);
            //3.添加到BulkRequest对象中
            request.add(new IndexRequest("hotel")
                    .id(hotel.getId().toString())
                    .source(JSON.toJSONString(hotelDoc), XContentType.JSON));
        });
        //4.发起请求
        client.bulk(request, RequestOptions.DEFAULT);
    }

添加完成后

GET /hotel/_search
{
  "query": {
    "match_all": {}
  }
}

搜索到,字段suggestion多了

"hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "36934",
        "_score" : 1.0,
        "_source" : {
          "address" : "静安交通路40号",
          "brand" : "7天酒店",
          "business" : "四川北路商业区",
          "city" : "上海",
          "id" : 36934,
          "location" : "31.251433, 121.47522",
          "name" : "7天连锁酒店(上海宝山路地铁站店)",
          "pic" : "https://m.tuniucdn.com/fb2/t1/G1/M00/3E/40/Cii9EVkyLrKIXo1vAAHgrxo_pUcAALcKQLD688AAeDH564_w200_h200_c1_t0.jpg",
          "price" : 336,
          "score" : 37,
          "starName" : "二钻",
          "suggestion" : [
            "7天酒店",
            "四川北路商业区"
          ]
        }
      },

测试自动补全,不跳过重复的

#测试自动补全
POST /hotel/_search
{
  "suggest": {
    "title_suggest": {
      "text": "rj", 
      "completion": {
        "field": "suggestion", 
        "skip_duplicates": false, 
        "size": 10 
      }
    }
  }
}
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "suggest" : {
    "title_suggest" : [
      {
        "text" : "rj",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "如家",
            "_index" : "hotel",
            "_type" : "_doc",
            "_id" : "415600",
            "_score" : 1.0,
            "_source" : {
              "address" : "三间房乡褡裢坡村青年沟西侧558号",
              "brand" : "如家",
              "business" : "传媒大学/管庄地区",
              "city" : "北京",
              "id" : 415600,
              "location" : "39.923212, 116.560023",
              "name" : "如家酒店(北京朝阳北路传媒大学褡裢坡地铁站店)",
              "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/3NezpxNZWQMdNXibwbMkQuAZjDyJ_w200_h200_c1_t0.jpg",
              "price" : 259,
              "score" : 47,
              "starName" : "二钻",
              "suggestion" : [
                "如家",
                "传媒大学",
                "管庄地区"
              ]
            }
          },

5.2.5 酒店数据RestAPI解析

在这里插入图片描述

    //自动补全
    @Test
    void testSuggestion() throws IOException {
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        //2.准备DSL
        request.source().suggest(new SuggestBuilder().addSuggestion(
                "title_suggest",
                SuggestBuilders.completionSuggestion("suggestion")
                        .prefix("sj")
                        .size(10)
                        .skipDuplicates(true)));
        //3.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4.解析结果
        handleResponse(response);
    }

对解析结果进行解析

在这里插入图片描述

        Suggest suggestions = response.getSuggest();
        if(suggestions!=null){
            System.out.println("建议结果:");
            CompletionSuggestion titleSuggest = suggestions.getSuggestion("title_suggest");
            for(CompletionSuggestion.Entry.Option option :titleSuggest.getOptions()){
                String text = option.getText().string();
                System.out.println(text);
            }
        }

5.2.6 酒店数据自动补全

自动补全的请求,请求方式是GET

在这里插入图片描述

在这里插入图片描述

    @GetMapping("/suggestion")
    public List<String> getSuggetion(@RequestParam("key") String prefix) {
        return hotelService.getSuggetion(prefix);
    }

解析返回

    @Override
    public List<String> getSuggetion(String prefix) {
        //1.准备Request对象
        SearchRequest request = new SearchRequest("hotel");
        List<String> suggestionsList = new ArrayList<>();
        try {
            //2.准备DSL
            request.source().suggest(new SuggestBuilder().addSuggestion(
                    "title_suggest",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix(prefix)
                            .size(10)
                            .skipDuplicates(true)));
            //3.发起请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //4.解析结果
            Suggest suggestions = response.getSuggest();
            if(suggestions!=null){
                CompletionSuggestion titleSuggest = suggestions.getSuggestion("title_suggest");
                for(CompletionSuggestion.Entry.Option option :titleSuggest.getOptions()){
                    String text = option.getText().string();
                    suggestionsList.add(text);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return suggestionsList;
    }

在这里插入图片描述

在这里插入图片描述

5.3 数据同步

在这里插入图片描述

在这里插入图片描述

方案一较为简单,但是增加了耦合度,耗时大大增强。

在这里插入图片描述

方案二进行了解耦,但更依赖于MQ的可靠度

在这里插入图片描述

方案三依靠spring的binlog,完全进行解耦,引入新的中间件,实现比较复杂

在这里插入图片描述

5.3.1 利用mq使mysql和es同步

在这里插入图片描述

导入hotel-admin,修改application.yaml,并启动访问

server:
  port: 8099
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/heima?useSSL=false
    username: root
    password: 123sjbsjb
    driver-class-name: com.mysql.cj.jdbc.Driver
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  type-aliases-package: cn.itcast.hotel.pojo

测试其CRUD正常

在这里插入图片描述

5.3.2 设置队列

增和改在es中属于一类操作,故消息队列分为两类:增/改、删

所以hotel-demo作为消费者,hotel-admin作为生产者

在这里插入图片描述

5.3.2.1 引入依赖

因为hotel-demo作为消费者,所以要在hotel-demo引入mq的依赖,即引入spring的AMQP依赖

        <!--spring amqp -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
5.3.2.2 配置AMQP

还需要在application.yaml中配置AMQP-----》rabbitmq:

server:
  port: 8089
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/heima?useSSL=false
    username: root
    password: 123sjbsjb
    driver-class-name: com.mysql.cj.jdbc.Driver
  rabbitmq:
    host: 192.168.204.129
    port: 5672
    virtual-host: /
    username: itcast
    password: 123321


logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  type-aliases-package: cn.itcast.hotel.pojo
5.3.2.3 声明队列交换机

交换机常量设置在常量区constants里,这里采用Direct Exchange方式的消息队列

在这里插入图片描述

//Direct exchange
public class MqConstants {
    // 定义交换机
    public static final String HOTEL_EXCHANGE = "hotel.topic";
    // 监听新增和修改的队列
    public static final String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
    // 监听删除的队列
    public static final String HOTEL_DELETE_QUEUE = "hotel.delete.queue";

    //RoutingKey是发送者指定的路由键,而BindingKey是接收者指定的路由键

    // 新增和修改的路由键
    public static final String HOTEL_INSERT_KEY = "hotel.insert";
    // 删除的路由键
    public static final String HOTEL_DELETE_KEY="hotel.delete";
}

既可以采用bean方式也可以采用注解方式,创建SPringRabbitListener,一定要加@Configuration,设置为配置类

@Configuration
public class SpringRabbitListener {
    @RabbitListener(bindings =@QueueBinding(
            value=@Queue(name=HOTEL_INSERT_QUEUE),
            exchange=@Exchange(name=HOTEL_EXCHANGE,type= ExchangeTypes.DIRECT),
            key={HOTEL_INSERT_KEY}
    ))
    public void listenInsertQueue(String message){
        //消息监听动作

    }

    @RabbitListener(bindings =@QueueBinding(
            value=@Queue(name=HOTEL_DELETE_QUEUE),
            exchange=@Exchange(name=HOTEL_EXCHANGE,type= ExchangeTypes.DIRECT),
            key={HOTEL_DELETE_KEY}
    ))
    public void listenDeleteQueue(String message){
        //消息监听动作
    }
}
5.3.2.4 在admin中完成消息发送

在admin引入AMQP依赖

<!--spring amqp -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

同样需要配置AMQP的地址

server:
  port: 8089
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/heima?useSSL=false
    username: root
    password: 123sjbsjb
    driver-class-name: com.mysql.cj.jdbc.Driver
  rabbitmq:
    host: 192.168.204.129
    port: 5672
    virtual-host: /
    username: itcast
    password: 123321

消息发送是在增删改的时候发送消息,所以是在admin的controller中,首先要在controller中注入RabbitTamplate

@RestController
@RequestMapping("hotel")
public class HotelController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private IHotelService hotelService;

增加的时候发送消息

    @PostMapping
    public void saveHotel(@RequestBody Hotel hotel){
        hotelService.save(hotel);
        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_INSERT_KEY, hotel.getId());
    }

因为mq是基于内存的,因此消息发个id就可以了

其他crud一样

    @PutMapping()
    public void updateById(@RequestBody Hotel hotel){
        if (hotel.getId() == null) {
            throw new InvalidParameterException("id不能为空");
        }
        hotelService.updateById(hotel);
        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_INSERT_KEY, hotel.getId());
    }

    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") Long id) {
        hotelService.removeById(id);
        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_DELETE_KEY, id);
    }
5.3.2.5 demo监听消息

在hotel-demo中监听消息,并更新es中数据,在5.3.2.3中完成监听操作

@Component
public class SpringRabbitListener {
    @Autowired
    private IHotelService hotelService;

    @RabbitListener(bindings =@QueueBinding(
            value=@Queue(name=HOTEL_INSERT_QUEUE),
            exchange=@Exchange(name=HOTEL_EXCHANGE,type= ExchangeTypes.DIRECT),
            key={HOTEL_INSERT_KEY}
    ))
    //监听新增和修改的队列的动作
    public void listenInsertQueue(Long id){
        hotelService.insertById(id);

    }

    @RabbitListener(bindings =@QueueBinding(
            value=@Queue(name=HOTEL_DELETE_QUEUE),
            exchange=@Exchange(name=HOTEL_EXCHANGE,type= ExchangeTypes.DIRECT),
            key={HOTEL_DELETE_KEY}
    ))
    //监听删除的队列的动作
    public void listenDeleteQueue(Long id){
        hotelService.deleteById(id);
    }
}

在IHotelService中声明,在其实现类中完成实现

    @Override
    public void insertById(Long id) {
        try {
            //根据ID查询
            Hotel hotel = getById(id);
            //转换为文档类型
            HotelDoc hotelDoc = new HotelDoc(hotel);

            //1.创建Request对象
            IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
            //2.准备JSON文档
            request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            //3.发起请求
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void deleteById(Long id) {
        try {
            //1.创建Request对象
            DeleteRequest request = new DeleteRequest("hotel",id.toString());
            //2.发起请求
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

测试成功

5.4 es集群

在这里插入图片描述

5.4.1 搭建es集群

见CSDN中其他文章

5.4.2 集群职责及脑裂

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5.4.3 分布式新增和查询流程

POST 请求http://192.168.204.129:9200/itcast/_doc/1(/3/5)

在这里插入图片描述

{
    "title": "试着插入一条id=1/3/5"
}

查询文档

{
    "query": {
        "match_all":{}
    }
}
{
    "took": 171,
    "timed_out": false,
    "_shards": {
        "total": 3,
        "successful": 3,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 3,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "itcast",
                "_type": "_doc",
                "_id": "5",
                "_score": 1.0,
                "_source": {
                    "title": "试着插入一条id=5"
                }
            },
            {
                "_index": "itcast",
                "_type": "_doc",
                "_id": "3",
                "_score": 1.0,
                "_source": {
                    "title": "试着插入一条id=3"
                }
            },
            {
                "_index": "itcast",
                "_type": "_doc",
                "_id": "1",
                "_score": 1.0,
                "_source": {
                    "title": "试着插入一条id=1"
                }
            }
        ]
    }
}

在9201也查到3条,9202也查到三条

可以通过explain来看到查询结果到底在哪里

{
    "query": {
        "match_all":{}
    },
    "explain":true
}
    "hits": {
        "total": {
            "value": 3,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_shard": "[itcast][0]",
                "_node": "p2axigL_Q_q9RmgOGB7nZQ",
                "_index": "itcast",
                "_type": "_doc",
                "_id": "5",
                "_score": 1.0,
                "_source": {
                    "title": "试着插入一条id=5"
                },
                "_explanation": {
                    "value": 1.0,
                    "description": "*:*",
                    "details": []
                }
            },
            {
                "_shard": "[itcast][1]",
                "_node": "vVKkHh2KSROW66yCwl4v8g",
                "_index": "itcast",
                "_type": "_doc",
                "_id": "3",
                "_score": 1.0,
                "_source": {
                    "title": "试着插入一条id=3"
                },
                "_explanation": {
                    "value": 1.0,
                    "description": "*:*",
                    "details": []
                }
            },

在这里插入图片描述
在这里插入图片描述

分布式查询

在这里插入图片描述

5.4.4 故障转移

在这里插入图片描述

在这里插入图片描述

停到主节点

docker-compose stop es01

在这里插入图片描述

在这里插入图片描述

重新启动es01

docker-compose start es01

在这里插入图片描述

在这里插入图片描述

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AAA码农宝哥.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值