ElasticSearch

学这个之前先来说一下ElasticSearch也是一个数据库,我们连上mysql,redis和ElasticSearch也就三个数据库了,接下来我们对比一下这三个数据库:

Mysql:
    分类: 关系型数据库
    概述: mysql在存储数据时,数据和数据之间有一定的关联关系
    存储介质: 存放在硬盘上
    优点: 不会导致数据丢失
    缺点: 执行效率低
    硬盘 --->  内存  ---> CPU
    事务控制,这也是他没被替换掉的原因
redis:
    分类: 非关系型数据库
    概述: 数据在存储时,数据和数据之间没有关联关系
    存储介质: 内存
    优点: 执行效率高
    缺点: 可能会导致数据丢失
ElasticSearch: 灵活的搜索
    分类: 非关系型数据库
    概述: ES专门用于对海量数据的搜索
    存储介质: 内存
    优点: 搜索效率高

数据库排行查询网址:https://db-engines.com/ 

搜索引擎-ElasticSearch

1、面临问题

    在模糊查询中,由于索引失效,mysql的查询效率太过低下。

2、解决方案-ElasticSearch

2.1、简介

【elasticsearch】

    elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容.

【ELK】

    elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

【Lucene】

    elasticsearch底层是基于lucene来实现的。
​
    Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。    官网:https://lucene.apache.org/ 

2.2、优缺点

【优点】

    检索数据非常快

【缺点】

     处理数据少的时候就不如sql了,因为他要倒叙排序再去分词,然后再去查找,如果数据量少的话不如sql数据库

2.3、运行原理-倒排索引

【正向索引】

    正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程.
    优点:
        可以给多个字段创建索引
        根据索引字段搜索、排序速度非常快
    缺点:
        根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

【倒排索引】

    倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
    优点:
        根据词条搜索、模糊搜索时,速度非常快
    缺点:
        只能给词条创建索引,而不是字段
        无法根据字段做排序

2.4、相关名词

【索引库】

    索引(Index),就是相同类型的文档的集合。类似于mysql中的表

【mapping映射】

    映射(mapping),是索引中文档的字段约束信息,类似mysql表的结构约束。

【文档】

    文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式

【词条】

    词条(Term),对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。

2.5、Kibana

    可视化工具,在浏览器输入DSL语句即可查看ES中的内容。

3、使用步骤

3.1、Kibana浏览器操作-索引库&文档

【索引库操作】

【创建索引库】

PUT /索引库名称
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

【查看索引库】

GET /索引库名

【删除索引库】

DELETE /索引库名

【修改索引库(追加)】   这个也可以修改也可以新增,如果有这个索引就是先删除再从新新增直接覆盖,如果没有那么就新增

PUT /索引库名/_mapping
{
 "properties":{
   "新字段":{
       "type":"数据类型(integer)"
        }
  }
}   

【文档操作】

【新增文档】

POST /索引库名/_doc/文档id(不给会随机生成id)
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
   }
}

【查看文档】

GET /索引库名/_doc/文档id

【删除文档】

DELETE /索引库名/_doc/文档id

【修改文档】

【全量修改】

PUT /索引库名/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2"
}

【增量修改】

POST /索引库名/_update/文档id
{
    "doc": {
        "字段名": "新的值"
    }
}       

3.2、java代码操作-索引库&文档

【导入依赖】

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

【与版本对应】

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

【初始化连接&关闭连接】

private RestHighLevelClient client; 
​
// 创建连接
@BeforeEach
void setUp() {
    this.client = new RestHighLevelClient(RestClient.builder(
            HttpHost.create("http://192.168.248.222:9200")
    ));
}
​
// 关闭连接
@AfterEach
void tearDown() throws IOException {
    this.client.close();
}

【索引库操作】

【创建索引库】

// 创建索引库
@Test
public void createIndex() throws IOException {
    // 1.创建请求语义对象
    CreateIndexRequest request = new CreateIndexRequest("hotel");
    // 添加 source
    request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
    // 2.发送请求
    CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
    // 3.解析响应
    boolean flag = response.isAcknowledged();
    // 打印结果
    System.out.println(flag);
}

【删除索引库】

// 删除索引库
@Test
public void deleteIndex() throws IOException {
    // 1.创建请求语义对象
    DeleteIndexRequest request = new DeleteIndexRequest("hotel");
    // 2.发送请求
    AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
    // 3.解析响应
    boolean flag = response.isAcknowledged();
    // 打印结果
    System.out.println(flag);
}

【查看索引库是否存在】

// 查看索引库是否存在
@Test
public void existsIndex() throws IOException {
    // 1.创建请求语义对象
    GetIndexRequest request = new GetIndexRequest("hotel");
    // 2.发送请求
    boolean flag = client.indices().exists(request, RequestOptions.DEFAULT);
    // 3.解析响应
    // 打印结果
    System.out.println(flag);
}

【文档操作】

【添加文档数据】

@Autowired
private IHotelService hotelService; 
​
// 添加文档数据
@Test
public void add() throws IOException {
    // 1.根据id获取数据
    Hotel hotel = hotelService.getById(36934L);
    // 2.转换成 ES 对应的数据
    HotelDoc hotelDoc = new HotelDoc(hotel);
    // 3.转换成JSON
    String hotelDocJson = JSON.toJSONString(hotelDoc);
    // 4.创建请求语义对象   这个语义对象就是相当于es中的put 
    //    所以如果我们es库中有这个字段的数据那么就删除再新增就完成了修改,如果没有这个数据
    //              那么就会直接新增    就是全量修改          
    IndexRequest request = new IndexRequest("hotel");
    request.id(hotel.getId() + "");
    request.source(hotelDocJson, XContentType.JSON);
    // 5.发送请求
    IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    // 6.解析响应结果
    DocWriteResponse.Result result = response.getResult();
    System.out.println(result);
}

【查询文档数据】

// 查询文档数据
@Test
public void select() throws IOException {
    // 1.创建请求语义对象
    GetRequest request = new GetRequest("hotel","36934");
    // 2.发送请求
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    // 3.解析响应结果
    String json = response.getSourceAsString();
    HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    System.out.println(hotelDoc);
}

【修改文档数据】

// 修改文档数据   这个修改文档是增量修改,可以根据给定字段给定值去修改
@Test
public void update() throws IOException {
    // 1.创建请求语义对象
    UpdateRequest request = new UpdateRequest("hotel","36934");
    request.doc(
            "name", "7天连锁酒店(上海宝山路地铁站店)"
    );
    // 2.发送请求
    UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
    // 3.解析响应结果
    DocWriteResponse.Result result = response.getResult();
    System.out.println(result);
}

【删除文档数据】

// 删除文档数据
@Test
public void delete() throws IOException {
    // 1.创建请求语义对象
    DeleteRequest request = new DeleteRequest("hotel","36934");
    // 2.发送请求
    DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
    // 3.解析响应结果
    DocWriteResponse.Result result = response.getResult();
    System.out.println(result);
}

【批量添加】

@Autowired
private IHotelService hotelService;
​
// 批量添加文档数据
@Test
public void add() throws IOException {
    // 1.批量查询酒店数据
    List<Hotel> hotels = hotelService.list();
    // 2.创建请求语义对象
    BulkRequest request = new BulkRequest();
    for (Hotel hotel : hotels) {
        // 转换格式
        HotelDoc hotelDoc = new HotelDoc(hotel);
        String hotelDocJson = JSON.toJSONString(hotelDoc);
        IndexRequest indexRequest = new IndexRequest("hotel");
        indexRequest.id(hotel.getId() + "");
        indexRequest.source(hotelDocJson, XContentType.JSON);
        request.add(indexRequest);
    }
    // 5.发送请求
    BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
    // 6.解析响应结果
    RestStatus status = response.status();
    System.out.println(status);
}

3.3、Kibana浏览器-查询&结果处理

【查询所有】

GET /索引库名/_search
{
 "query": {
   "match_all": {
    }
  }
}

【全文检索查询】

【多字段】

GET /索引库名/_search
{
"query": {
  "multi_match": {
     "query": "内容",
     "fields": ["字段1", "字段2"]
  }
 }
}

【单字段】

GET /hotel/_search
{
  "query": {
    "match": {
      "字段": "值"
    }
  }
}

【精确查询】

【term查询】

GET /索引库名/_search
{
  "query": {
    "term": {
      "字段": {
        "value": "值"
      }
    }
  }
}

【range查询】

GET /索引库名/_search
{
  "query": {
    "range": {
      "字段": {
        "gte": 10, // 这里的gte代表大于等于,gt则代表大于
        "lte": 20 // lte代表小于等于,lt则代表小于
      }
    }
  }
}

【地理坐标查询】

【矩形范围查询】

GET /索引库名/_search
{
  "query": {
    "geo_bounding_box": {
      "字段": {
        "top_left": { // 左上点
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": { // 右下点
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}

【附近/距离查询】

GET /索引库名/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "字段": "31.21,121.5" // 圆心
    }
  }
}

【复合查询】

【算分函数查询】

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
// 原始查询条件 根据BM25算法进行算分 
        "match": {
        "all": "外滩"
        }
      },
      "functions": [
        {
// 过滤条件 符合条件重新算分
          "filter": {
              "term": {
                   "brand": "如家"
              }
        },
//算分函数 weight是常量值
          "weight": 10
        }
      ],
//加权模式  默认是multiply
      "boost_mode": "sum"
    }
  }
}

【布尔查询】

GET /hotel/_search
{
  "query": {
    "bool": {
//必须匹配
      "must": [
        {
          "match": {
            "all": "上海"
          }
        }
      ],
//选择匹配
      "should": [
        {
          "term": {
            "brand": {
              "value": "皇冠假日"
            }
          }
        },
        {
          "term": {
            "brand": "华美达"
          }
        }
      ],
//必须不匹配 不参与算分
      "must_not": [
        {
          "range": {
            "price": {
              "lte": 400
            }
          }
        }
      ],
//必须匹配 不参与算分
      "filter": [
        {
          "geo_distance": {
            "distance": "10km",
            "location": {
              "lat": 31.21,
              "lon": 121.5
            }
          }
        }
      ]
    }
  }
}

【排序】

【普通字段】

GET /索引库名/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "字段": "desc"  // 排序字段、排序方式ASC、DESC
    }
  ]
}

【地理坐标】

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance": {
        "地理坐标相关字段": {
          "lat": 31.21,
          "lon": 121.5
        },
      "order":"asc",
//单位
      "unit":"km"
      }
    }
  ]
}

【分页】

【基本分页】

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}

【深度分页】

【高亮】

GET /hotel/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
    }
  },
  "highlight": {
    "fields": { // 指定要高亮的字段
      "FIELD": {
        "pre_tags": "<em>",  // 用来标记高亮字段的前置标签
        "post_tags": "</em>", // 用来标记高亮字段的后置标签
        "require_field_match": "false"
      }
    }
  }
}

3.4、java代码操作-查询&结果处理

/**
 * 提示:
 * term和match的区别:
 * term匹配的是比如我们输入的是 如家 那么就根据如家去查   输入  如家aaa   那么就根据如家aaa查
 * match匹配的是比如我们输入的是 如家 那么就根据如家去查   输入  如家aaa   那么就根据如家和aaa去查   具体怎么分词根据我们的分词器来规定
 * 总结:区别就是一个会查询的时候会分词一个不会
 */

【查询所有】

// 查询所有--match all

    /**
     * 查询方式:matchAll
     * 查询所有
     * 对应的es语句:
     * GET /hotel/_search{
     * "query":{
     * "match_all":{}
     * }
     * }
     */
    @Test
    public void testSearch() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.matchAllQuery());
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println("hit = " + hit);
        }
    }

【全文检索查询】

// 全文检索查询--match

 /**
     * 查询方式: match
     * 查询单个字段,也是可以进行分词的这个字段,前提是这个字段支持分词
     * 对应es语句:
     * GET /hotel/_search
     * {
     * "query":{
     * "match":{
     * "name": "如家"
     * }
     * }
     * }
     * 如果name改成brand,并且根据  如   字查,那么肯定查不出来,因为brand是keyword,如果根据brand查询   内容是  如家   那么就可以了,因为brand整个字段是如家的是纯在的
     */
    @Test
    public void testMatch() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.matchQuery("name", "如家"));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println("hit = " + hit);
        }
    }

多条件查询  mutil_match

/**
     * 查询方式: multi_match
     * 多条件查询,相当于是sql中的and查询,这里面查的是name里面有  如 字的,因为name是可以分词的,那么就可以查询出来,
     * 但是brand是不能分词的,只能查全部的,所以brand没有满足的,所以查不出来,除非brand的整个字段放进去然后再去看有没有分词name匹配上的,如果有就命中
     * 对应es语句:
     * GET /hotel/_search
     * {
     * "query": {
     * "mutil_match": {
     * "query": "如",
     * "fields": ["name","brand"]
     * }
     * }
     * }
     */
    @Test
    public void testMatch_Multiple() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source()
                .query(QueryBuilders.multiMatchQuery("如", "name", "brand"));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println("hit = " + hit);
        }
    }

【精确查询】

【term查询】

// 精确查询--term

/**
     * 精准查询:term
     * term匹配的是比如我们输入的是 如家 那么就根据如家去查   输入  如家aaa   那么就根据  如家aaa  查
     * 语法es:
     * GET /hotel/_search
     * {
     * "query":{
     * "term": {
     * "name": {
     * value: ""如家""
     * }
     * }
     * }
     * }
     */
    @SneakyThrows
    @Test
    public void testTerm() {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.termQuery("name", "如家"));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println("hit = " + hit);
        }
    }

【range查询】

// 范围查询--range

/**
     * 范围查询:range
     * 对应es语法:
     * GET /hotel/_search
     * {
     * "query":{
     * "range":{
     * "price": {
     * "gte": 500
     * }
     * }
     * }
     * }
     */
    @Test
    public void testRange() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().query(QueryBuilders.rangeQuery("price").gte("500").lte("800"));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String hotelJson = hit.getSourceAsString();
            System.out.println("hotelJson = " + hotelJson);
        }
    }

【复合查询】

【布尔查询】

// 布尔查询--bool

 /**
     * bool查询
     * 对应es语句:
     * GET /hotel/_search
     * {
     * "query":{
     * "bool": {
     * "must":[
     * {
     * "match": {
     * "name": "如家"
     * }
     * },
     * {
     * "match":{
     * "id": "415600"
     * }
     * },
     * {
     * "range":{
     * "price":{
     * "lte": 415600
     * }
     * }
     * }
     * ]
     * }
     * }
     * }
     * 如果想要多个字段查询,那么就直接在语义对象上添加must就可以
     */
    @Test
    public void testBool() throws IOException {
        SearchRequest request = new SearchRequest("hotel");

        // 2.准备DSL
        // 2.1.准备BooleanQuery
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //2.2.添加must
        boolQueryBuilder.must(QueryBuilders.termQuery("name", "如家"));
        boolQueryBuilder.must(QueryBuilders.termQuery("id", "415600"));
        //2.3.添加range
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(300));
        //3.发送请求
        request.source().query(boolQueryBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println("hit = " + hit.getSourceAsString());
        }
    }

【排序分页】

// 排序、分页--sort from size
处于性能考虑,es规定from+size<=10000
 /**
     * form  size  排序分页查询
     * 对应es语句:
     * GET /hotel/_search
     * {
     *     "query":{
     *         “match_all”:{}
     *     },
     *     "sort":[
     *        {
     *            "id":{
     *                 "order":"desc"
     *            }
     *        }
     *     ]
     *     "from": 0,
     *     size: 2
     *
     * }
     */
    @Test
    public void testFormAndSize() throws IOException {
        //搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。
        SearchRequest request = new SearchRequest("hotel");
        //设置分页,搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。
        request.source().from(0).size(2);
        request.source().sort("id", SortOrder.DESC);
        //设置查询条件   这里是查询全部
        request.source().query(QueryBuilders.matchAllQuery());
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            System.out.println("json = " + json);
        }
    }

【高亮】

// 高亮--highlights

/**
     * 高亮
     */
    @Test
    public void testHightField() throws IOException {
        //搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。
        SearchRequest request = new SearchRequest("hotel");
        //设置分页,搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。
        request.source().from(0).size(2);
        request.source().sort("id", SortOrder.DESC);
        //这里可以插叙多个字段,如果是多个字段那么就应该下边getFragments()[0]设置需要的那个字段了
        request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
        //设置查询条件   这里是查询全部
        request.source().query(QueryBuilders.matchQuery("name","如家"));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println("hit = " + hit.getSourceAsString());
            //我们es的高亮显示是hit中是完整的,但是他高亮的地方是另外展示的
            //下边的map是得到了一个高亮的集合,我们上边设置的我们我们的高亮字段只有name,那么我i们就获取
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!highlightFields.isEmpty()) {
                String name = highlightFields.get("name").getFragments()[0].toString();
                System.out.println("name = " + name);
            }

        }
    }

【响应结果解析】

// 响应结果解析
private void handleResponse(SearchResponse response) {
    // 4.解析响应结果
    SearchHits searchHits = response.getHits();
    // 查询总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("查询总条数:" + total);
    // 查询结果数组
    SearchHit[] hits = searchHits.getHits();
    // 遍历
    for (SearchHit hit : hits) {
        // 得到json
        String hotelDocJson = hit.getSourceAsString();
        // 转换成Java对象
        HotelDoc hotelDoc = JSON.parseObject(hotelDocJson, 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.5、Kibana浏览器-聚合

GET /hotel/_search
{
 "query": {//限定聚合条件
    "range": {
      "price": {
        "lte": 200 // 只对200元以下的文档聚合
      }
    }
  },
  "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
  "aggs": { // 定义聚合
    "brandAgg": { //给聚合起个名字
      "terms": { // 聚合的类型,按照品牌值聚合,所以选择term
        "field": "brand", // 参与聚合的字段
        "order": {
          "_count": "asc" // 按照_count升序排列
        },
        "size": 20 // 希望获取的聚合结果数量
      },
      "aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
        "score_stats": { // 聚合名称
          "stats": { // 聚合类型,这里stats可以计算min、max、avg等
            "field": "score" // 聚合字段,这里是score
          }
        }
      }
    }
  }
}

3.6、java代码操作-聚合

【聚合查询】

// 聚合查询--aggregation

/**
     * 根据品牌分桶
     * 对应es语句:
     * GET /hotel/_search
     * {
     *     "aggs":{
     *         "brandAgg":{
     *             "terms":{
     *                "field": "brand",
     *                "size": 4
     *             }
     *         }
     *     }
     *
     * }
     */
    @Test
    public void testAgg1() throws IOException {
        //创建语义对象
        SearchRequest request = new SearchRequest("hotel");
        //准备DEL语句,这句话是使用AggregationBuilders创建一个名称为brandAgg的桶,按照brand字段分捅,查询出来前四个
        request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(4));
        //发送给ES请求得到响应
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //根据响应得到桶指定brandAgg捅的信息,等号前边表示使用什么格式的查询就是用什么接收,上边是使用了terms所以这里就是用Terms接收
        Terms brandAgg = response.getAggregations().get("brandAgg");
        //得到里面捅的集合
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            //得到key和doc_count
            System.out.println(bucket.getKeyAsString()+" : "+bucket.getDocCount());
        }
    }

【响应结果解析】

// 响应结果解析
private void handleResponse(SearchResponse response) {
    // 4.解析聚合响应结果
    Aggregations aggregations = response.getAggregations();
    // 根据名称获得聚合结果
    Terms brandTerms = aggregations.get("brand_agg");
    // 获取桶
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    // 遍历
    for (Terms.Bucket bucket : buckets) {
        // 获取品牌信息
        String brandName = bucket.getKeyAsString();
        long count = bucket.getDocCount();
        System.out.println(brandName+":"+count);
    }
}

分桶案例代码

 /**
     * 显示品牌  星级   价格
     */
    @Override
    public Map<String, List<String>> filtersQuery(RequestParams params) throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        //这个是让我们的数据不显示
        request.source().size(0);
        //判断key是不是空,如果不为空那么就添加搜索条件
        if (!Strings.isNullOrEmpty(params.getKey())) {
            request.source().query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchQuery("all", params.getKey())));
        }
        //判断价格的范围是不是空,如果不为空那么就添加搜索条件
        if (!Strings.isNullOrEmpty(params.getMinPrice()+"") && !Strings.isNullOrEmpty(params.getMaxPrice()+"")) {
            request.source().query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice())));
        }
        //添加聚合函数,一共分了三个捅
        request.source()
                //设置捅名为cityAdds   ,分桶的字段按照city字段来分桶
                .aggregation(AggregationBuilders.terms("cityAdds").field("city"))
                //设置捅名为brandAggs   ,分桶的字段按照brand字段来分桶
                .aggregation(AggregationBuilders.terms("brandAggs").field("brand"))
                //设置捅名为starAgg   ,分桶的字段按照starName字段来分桶
                .aggregation(AggregationBuilders.terms("starAgg").field("starName"));
        //发送请求给es
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //获取捅的数据集合
        List<String> cityAdds = getAggByName(response, "cityAdds");
        List<String> brandAggs = getAggByName(response, "brandAggs");
        List<String> starAgg = getAggByName(response, "starAgg");
        HashMap<String, List<String>> map = new HashMap<>();
        map.put("brand", brandAggs);
        map.put("city", cityAdds);
        map.put("starName", starAgg);
        //返回给前端
        return map;
    }

上边分桶案例解析代码:
 

  /**
     * 提取方法,将响应结果的分桶提取出来key
     */
    public List<String> getAggByName(SearchResponse response, String aggName) {
        //创建集合用来收集key
        List<String> keyList = new ArrayList<>();
        //得到整个捅的对象
        Aggregations aggregations = response.getAggregations();
        //得带指定名称的捅
        Terms aggTerms = aggregations.get(aggName);
        List<? extends Terms.Bucket> buckets = aggTerms.getBuckets();
        buckets.forEach(bucket -> keyList.add(bucket.getKeyAsString()));
        return keyList;
    }

使用拼音分词和自动补齐

在我们的settings设置的时候我们具体的有些设置太多了,接下来是官方的一个图片,有一些配置可以参考一下:

使用拼音分词器的话需要我们安装拼音分词器,具体怎么安装 可以百度
例如使用后代码例子为:

POST /_analyze
{
  "text": "如家酒店还不错",
  "analyzer": "pinyin"
}

 

 自定义分词器

 

默认的拼音分词器会将每个汉字单独分为拼音,而我们希望的是每个词条形成一组拼音,需要对拼音分词器做个性化定制,形成自定义分词器。

elasticsearch中分词器(analyzer)的组成包含三部分:

  • character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符

  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart

  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等

 

如上图,也就是说我们会先过滤,将一些符号什么的替换,然后再对过滤后的内容进行分词,分完词以后再对我们分词后的词语进行拼音分词,这样我们就可以得到中文的分词和拼音的分词 

但是如果我们在使用的时候在搜索的时候用拼音+中文分词,那么就会出现如下一个例子:
当我们文档中有    文档id为1:name=“狮子”   文档2的id为2:name=“虱子”    
当我们搜索时搜索  name=“狮子”  这时候我们的分词就是   狮子  sz   shizi    当通过狮子查的时候查出来了文档1,但是接下来通过sz和shizi查那么就是文档2也就满足了,这样就导致了我们命名查询的狮子但是虱子也被查出来了。这是不好的,那么我们就设置当我们查询的时候就不使用拼音分词器,直接使用 ik  分词器,这样在输入狮子那么就没用拼音分词了,当我们输入的拼音,那么直接根据我们输入的拼音去匹配拼音分词

如何使用拼音分词器?

  • ①下载pinyin分词器

  • ②解压并放到elasticsearch的plugin目录

  • ③重启即可

如何自定义分词器?

  • ①创建索引库时,在settings中配置,可以包含三部分

  • ②character filter

  • ③tokenizer

  • ④filter

拼音分词器注意事项?

  • 为了避免搜索到同音字,搜索时不要使用拼音分词器

自动补全

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型。

  • 字段的内容一般是用来补全的多个词条形成的数组。

 completion: 被其修饰的字段可以自动补全 

 自动补全示例:
# 自动补全查询
GET /test/_search
{
  "suggest": {
    "title_suggest": {
      "text": "n", // 关键字
      "completion": {
        "field": "title", // 补全查询的字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

 学习案例Java自动补全代码示例:
 

/**
     * 通过搜索得到联想次词
     *
     * @param key 搜索框的内容
     * @return
     */
    @Override
    public List<String> getSuggest(String key) {
        //设置我们的自动补全函数名称
        String suggestName = "getSuggest";
        SearchRequest request = new SearchRequest("hotel");
        //这个请求的条件,把我们的自动补全字段添加上,然后是前缀为传过来的key的就查出来
        request.source().suggest(new SuggestBuilder().addSuggestion(
                                                           suggestName, SuggestBuilders.completionSuggestion("suggestion")
                                                                   .prefix(key)
                                                                   .skipDuplicates(true)
                                                                   .size(10)));
        //发送请求
        SearchResponse response = null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //拆关键集合用来收集并且传给前端
        List<String> result = new ArrayList();
        if (!Objects.isNull(response)) {
            //解析获取数据     suggestName是我们的函数名称在上边写了   String suggestName = "getSuggest";
            CompletionSuggestion suggestion = response.getSuggest().getSuggestion(suggestName);
            List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();
            result = options.stream().map(value -> value.getText().string()).collect(Collectors.toList());
        }
        return result;
    }

对应上边Java的ES的代码如下:

GET /hotel/_search
{
  "suggest": {
    "sugg_test": {
      "text": "ru",
      "completion": {
        "field": "suggestion"
      }
    }
  }

结果:
 {
  "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" : {
    "sugg_test" : [
      {
        "text" : "ru",
        "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" : [
                "如家",
                "传媒大学",
                "管庄"
              ]
            }
          },
          {
            "text" : "如家",
            "_index" : "hotel",
            "_type" : "_doc",
            "_id" : "416121",
            "_score" : 1.0,
            "_source" : {
              "address" : "莲花池东路120-2号6层",
              "brand" : "如家",
              "business" : "北京西站/丽泽商务区",
              "city" : "北京",
              "id" : 416121,
              "location" : "39.896449, 116.317382",
              "name" : "如家酒店(北京西客站北广场店)",
              "pic" : "https://m.tuniucdn.com/fb3/s1/2n9c/42DTRnKbiYoiGFVzrV9ZJUxNbvRo_w200_h200_c1_t0.jpg",
              "price" : 275,
              "score" : 43,
              "starName" : "二钻",
              "suggestion" : [
                "如家",
                "北京西站",
                "丽泽商务区"
              ]
            }
          },
          {
            "text" : "如家",
            "_index" : "hotel",
            "_type" : "_doc",
            "_id" : "433576",
            "_score" : 1.0,
            "_source" : {
              "address" : "南京东路480号保安坊内",
              "brand" : "如家",
              "business" : "人民广场",
              "city" : "上海",
              "id" : 433576,
              "location" : "31.236454, 121.480948",
              "name" : "如家酒店(上海南京路步行街店)",
              "pic" : "https://m.tuniucdn.com/fb2/t1/G6/M00/52/BA/Cii-U13eXVaIQmdaAAWxgzdXXxEAAGRrgNIOkoABbGb143_w200_h200_c1_t0.jpg",
              "price" : 379,
              "score" : 44,
              "starName" : "二钻",
              "suggestion" : [
                "如家",
                "人民广场"
              ]
            }
          },
          {
            "text" : "如家",
            "_index" : "hotel",
            "_type" : "_doc",
            "_id" : "434082",
            "_score" : 1.0,
            "_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" : "二钻",
              "suggestion" : [
                "如家",
                "豫园"
              ]
            }
          },
          {
            "text" : "如家",
            "_index" : "hotel",
            "_type" : "_doc",
            "_id" : "441836",
            "_score" : 1.0,
            "_source" : {
              "address" : "西坝河东里36号",
              "brand" : "如家",
              "business" : "国展中心",
              "city" : "北京",
              "id" : 441836,
              "location" : "39.966238, 116.450142",
              "name" : "如家酒店(北京国展三元桥店)",
              "pic" : "https://m.tuniucdn.com/fb2/t1/G6/M00/52/39/Cii-TF3eRTGITp1UAAYIilRD7skAAGLngIuAnQABgii479_w200_h200_c1_t0.png",
              "price" : 458,
              "score" : 47,
              "starName" : "二钻",
              "suggestion" : [
                "如家",
                "国展中心"
              ]
            }
          }
        ]
      }
    ]
  }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值