1.提出问题

1.mysql对于数据量大的时候查询执行效率低 elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容

2.专业名词解释

  1. Mysql:
  2. 分类: 关系型数据库
  3. 概述: mysql在存储数据时,数据和数据之间有一定的关联关系
  4. 存储介质: 存放在硬盘上
  5. 优点: 不会导致数据丢失
  6. 缺点: 执行效率低
  7. 硬盘 ---> 内存 ---> CPU
  8. 事务控制
  9. redis:
  10. 分类: 非关系型数据库
  11. 概述: 数据在存储时,数据和数据之间没有关联关系
  12. 存储介质: 内存
  13. 优点: 执行效率高
  14. 缺点: 可能会导致数据丢失
  15. ElasticSearch: 灵活的搜索
  16. 分类: 非关系型数据库
  17. 概述: ES专门用于对海量数据的搜索
  18. 存储介质: 内存
  19. 优点: 搜索效率高
  20. 缺点:数据量太少效率不高,维护成本高
正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程.

- 优点:
  - 可以给多个字段创建索引
  - 根据索引字段搜索、排序速度非常快
- 缺点:
  - 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

image.png

倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。

- 优点:
  - 根据词条搜索、模糊搜索时,速度非常快
- 缺点:
  - 只能给词条创建索引,而不是字段
  - 无法根据字段做排序


- 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息

- 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条

image.png

1.文档和字段:
                    1.elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中

                    2.Json文档中往往包含很多的字段(Field),类似于数据库中的列。

2.索引和映射:
                    1.索引(Index),就是相同类型的文档的集合。

                    2.映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。
- Mysql:擅长事务类型操作,可以确保数据的安全和一致性
- Elasticsearch:擅长海量数据的搜索、分析、计算

image.png

3.elasticsearch

Elasticsearch官网

1.Elasticsearch概述

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

2.ELK技术栈

:::info elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)
image.png :::

4.安装es、kibana(可视化界面)、分词器

安装链接

1.IK分词器

:::info

  • ik_smart:智能切分,粗粒度
  • ik_max_word:最细切分,细粒度 :::

    5.Kibana浏览器-索引库(类似数据库表,mapping映射就类似表的结构)&文档(每一条数据)

    1.mapping映射属性

    :::info mapping是对索引库中文档的约束,常见的mapping属性包括:

  • type:字段数据类型,常见的简单类型有:

    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
    • 数值:long、integer、short、byte、double、float、
    • 布尔:boolean
    • 日期:date
    • 对象:object
  • index:是否创建索引,默认为true
  • analyzer:使用哪种分词器
  • properties:该字段的子字段 :::

    2.索引库的CRUD

    1.创建索引库和映射

    PUT /索引库名称
    {
    "mappings": {
      "properties": {
        "字段名":{
          "type": "text",//数据类型
          "analyzer": "ik_smart"//分词器
        },
        "字段名2":{
          "type": "keyword",
          "index": "false"
        },
        "字段名3":{
          "properties": {
            "子字段": {
              "type": "keyword"
            }
          }
        }
      }
    }
    }
    

    2.查询索引库

    GET /索引库名
    

    3.修改索引库(一旦创建,无法修改mapping,只能追加字段

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

    4.删除索引库

    DELETE /索引库名
    

    3.文档的CRUD

    1.新增文档

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

    2.查询文档

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

    3.删除文档

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

    4.修改文档

    1.全量修改:直接覆盖原来的文档
    PUT /索引库名/_doc/文档id
    {
      "字段1": "值1",
      "字段2": "值2"
    }
    
    2.增量修改:修改文档中的部分字段
    POST /索引库名/_update/文档id
    {
      "doc": {
          "字段名": "新的值"
      }
    }
    

    6.JAVA代码(RestClient)-索引库&文档

    1.导入es的RestHighLevelClient依赖

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

    2.修改版本对应es

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

    3.初始化连接(业务中需要创建bean对象)&关闭连接

    ```java private RestHighLevelClient client;

// 创建连接 @BeforeEach void setUp() { this.client = new RestHighLevelClient(RestClient.builder( HttpHost.create(“http://192.168.248.222:9200“)

    /*RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(
            new HttpHost("localhost", 9200, "http"),
            new HttpHost("localhost", 9201, "http")));*/

));

}

// 关闭连接 @AfterEach void tearDown() throws IOException { this.client.close(); }

<a name="N7vxo"></a>
## 4.官网API
[官网api](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/29328734/1656245145834-6867368a-e911-4223-86d2-e4487159ad9d.png#clientId=u754360c3-2d15-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=468&id=u2b9c5fb4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=517&originWidth=796&originalType=binary&ratio=1&rotation=0&showTitle=false&size=141111&status=done&style=none&taskId=u376822cf-7bb5-4912-bbaf-220ed5ad6bd&title=&width=720.9056863211033)
<a name="bOMXQ"></a>
## 5.索引库操作CRUD
<a name="mUlJP"></a>
### 1.创建索引库
```java
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
// 创建索引库
@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);
}

2.删除索引库

// 删除索引库
@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);
}

3.查看索引库是否存在

// 查看索引库是否存在
@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);
}

6.文档操作CRUD

1.添加文档数据

@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.创建请求语义对象
    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);
}

2.查询文档数据

// 查询文档数据
@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);
}

3.修改文档数据

// 修改文档数据
@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);
}

4.删除文档数据

// 删除文档数据
@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);
}

5.批量添加文档数据

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

7.Kibana浏览器-查询&结果处理

image.png

1.查询所有

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

2.全文检索查询

1.multi_match:根据多个字段查询,参与查询字段越多,查询性能越差

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

2.match:根据一个字段查询

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

3.精确查询

1.term:根据词条精确值查询

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

2.range:根据值的范围查询

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

4.地理坐标查询

查经纬度

1.矩形范围查询

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

2.附近/圆形距离查询

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

5.复合查询

1.fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名

:::info function score 查询中包含四部分内容:

  • 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
  • 过滤条件:filter部分,符合该条件的文档才会重新算分
  • 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
    • weight:函数结果是常量
    • field_value_factor:以文档中的某个字段值作为函数结果
    • random_score:以随机数作为函数结果
    • script_score:自定义算分函数算法
  • 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
    • multiply:相乘
    • replace:用function score替换query score
    • 其它,例如:sum、avg、max、min ::: ```yaml
  • 过滤条件:决定哪些文档的算分被修改
  • 算分函数:决定函数算分的算法
  • 运算模式:决定最终算分结果

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

<a name="rGYkJ"></a>
### 相关性算分
![image.png](https://cdn.nlark.com/yuque/0/2022/png/29328734/1656251668959-fd9561de-c5e6-4516-ab06-86a73f1658c2.png#clientId=u46c6bc22-13a0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=705&id=u5484b96b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=778&originWidth=1023&originalType=binary&ratio=1&rotation=0&showTitle=false&size=119279&status=done&style=none&taskId=uce2fde26-4e7e-427d-9b10-49e197050e5&title=&width=926.490599380011)
<a name="VMC6b"></a>
### 2.bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索(参与**打分的字段越多,查询的性能也越差**)
:::info
bool查询逻辑

- must:必须匹配的条件,可以理解为“与”****
- should:选择性匹配的条件,可以理解为“或”
- must_not:必须不匹配的条件,不参与打分
- filter:必须匹配的条件,不参与打分 ***
:::
```yaml
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
            }
          }
        }
      ]
    }
  }
}

6.排序(不算分)

1.普通字段

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

2.地理坐标

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

7.分页

:::info

  • from:从第几个文档开始
  • size:总共查询几个文档 :::

    1.基本分页

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

    2.深度分页

    :::info 当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求。

针对深度分页,ES提供了两种解决方案,官方文档

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  • scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。

分页查询的常见实现方案以及优缺点:

  • from + size:
    • 优点:支持随机翻页
    • 缺点:深度分页问题,默认查询上限(from + size)是10000
    • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索

深度分页:

  • after search: 使用search_after必须要设置from=0。

    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:只能向后逐页查询,不支持随机翻页
    • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
  • scroll:

    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:会有额外内存消耗,并且搜索结果是非实时的
    • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。 :::

      8.高亮(网页搜索的字段高亮显示为)

      :::info 高亮显示的实现分为两步:
  • 1)给文档中的所有关键字都添加一个标签,例如标签

  • 2)页面给标签编写CSS样式 :::

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

    8.java代码-查询&结果处理

    image.png

    1.查询所有

    // 查询所有--match all
    @Test
    public void matchAll() throws IOException {
      // 1.创建 SearchRequest 对象
      SearchRequest request = new SearchRequest("hotel");
      // 2.组织 DSL 参数
      request.source()
              .query(QueryBuilders.matchAllQuery());
      // 3.发送请求
      SearchResponse response = client.search(request, RequestOptions.DEFAULT);
      // 4.解析响应结果
      handleResponse(response);
    }
    

    2.全文检索查询

    // 全文检索查询--match
    @Test
    public void match() throws IOException {
      // 1.创建 SearchRequest 对象
      SearchRequest request = new SearchRequest("hotel");
      //分词查询一个字段
      request.source().query(QueryBuilders.matchQuery("name","如家"));
      //分词查询多个字段
      request.source().query(QueryBuilders.multiMatchQuery("如家酒店","name",brand));
      // 3.发送请求
      SearchResponse response = client.search(request, RequestOptions.DEFAULT);
      // 4.解析响应结果
      handleResponse(response);
    }
    

    3.精确查询

    1.term查询

    // 精确查询--term
    @Test
    public void term() throws IOException {
      // 1.创建 SearchRequest 对象
      SearchRequest request = new SearchRequest("hotel");
      // 2.组织 DSL 参数
      request.source()
              .query(QueryBuilders.termQuery("city","杭州"));
      // 3.发送请求
      SearchResponse response = client.search(request, RequestOptions.DEFAULT);
      // 4.解析响应结果
      handleResponse(response);
    }
    

    2.range查询

    // 范围查询--range
    @Test
    public void range() throws IOException {
      // 1.创建 SearchRequest 对象
      SearchRequest request = new SearchRequest("hotel");
      // 2.组织 DSL 参数
      request.source()
              .query(QueryBuilders.rangeQuery("price").gte(500).lte(1000));
      // 3.发送请求
      SearchResponse response = client.search(request, RequestOptions.DEFAULT);
      // 4.解析响应结果
      handleResponse(response);
    }
    

    4.复合查询

    1.布尔查询

    // 布尔查询--bool
    @Test
    public void bool() throws IOException {
      // 1.创建 SearchRequest 对象
      SearchRequest request = new SearchRequest("hotel");
      // 2.组织 bool  查询参数
      BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
      //必须满足的条件
      boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
      //过滤条件
      boolQuery.filter(QueryBuilders.rangeQuery("price").lt(1000));
      // 3.组织 DSL 参数
      request.source()
              .query(boolQuery);
      // 4.发送请求
      SearchResponse response = client.search(request, RequestOptions.DEFAULT);
      // 5.解析响应结果
      handleResponse(response);
    }
    

    5.排序分页

    // 排序、分页--sort from size
    @Test
    public void sortAndPage() throws IOException {
      // 1.创建 SearchRequest 对象
      SearchRequest request = new SearchRequest("hotel");
      // 2.组织 DSL 参数
      // 查询所有
      request.source().query(QueryBuilders.matchAllQuery());
      // 前 20条
      request.source().from(0).size(20);
      // 按价格升序
      request.source().sort("price", SortOrder.ASC);
      // 3.发送请求
      SearchResponse response = client.search(request, RequestOptions.DEFAULT);
      // 4.解析响应结果
      handleResponse(response);
    }
    

    6.高亮

    // 高亮--highlights
    @Test
    public void highlights() throws IOException {
      // 1.创建 SearchRequest 对象
      SearchRequest request = new SearchRequest("hotel");
      // 2.组织 DSL 参数
      // 查询所有带有如家
      request.source().query(QueryBuilders.matchQuery("all","如家"));
      // 高亮处理
      request.source().highlighter(new HighlightBuilder()
              .field("name")
              .requireFieldMatch(false));
      // 3.发送请求
      SearchResponse response = client.search(request, RequestOptions.DEFAULT);
      // 4.解析响应结果 -- 高亮特殊处理
      handleResponse(response);
    }
    

    解析

    image.png

    7.周边位置排序

    ```java public PageResult search(RequestParams params) { try {

      // 1.准备Request
      SearchRequest request = new SearchRequest("hotel");
      // 2.准备DSL
      // 2.1.query
      buildBasicQuery(params, request);
    
      // 2.2.分页
      int page = params.getPage();
      int size = params.getSize();
      request.source().from((page - 1) * size).size(size);
    
    // 获取前端发送的地理位置
    String location = params.getLocation();
    //地理位置位置排序
    if (location != null && !location.equals("")) {
        request.source().sort(SortBuilders
                              .geoDistanceSort("location", new GeoPoint(location))
                              .order(SortOrder.ASC)
                              .unit(DistanceUnit.KILOMETERS)
                             );
    }


    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    return handleResponse(response);
} catch (IOException e) {
    throw new RuntimeException(e);
}

}

<a name="zPAVL"></a>
### 解析距离
![image.png](https://cdn.nlark.com/yuque/0/2022/png/29328734/1656394229925-b0c12e56-3f08-499d-85a1-d3d33d166d6e.png#clientId=uc6bd3c41-fd87-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=783&id=eJtAa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=865&originWidth=1029&originalType=binary&ratio=1&rotation=0&showTitle=false&size=316474&status=done&style=none&taskId=u438c0446-a28b-4079-adf7-ca7034de45b&title=&width=931.9245618397177)
<a name="iYOep"></a>
## 8.响应结果解析
```java
// 响应结果解析
private xxxx 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);
            }

        //获取距离排序值
        // 得到json
        Object[] sortValues = hit.getSortValues();
        if(SortValues!=null && SortValues.length>0){
            Object[] sortValues = sortValues[0];
             hotelDoc.setDistance(sortValues);
        }
        }
        // 打印结果
       //System.out.println(hotelDoc);
        //返回结果
        return xxxx;
    }
}

实现搜索分页,结果标签过滤,周边距离排序,竞价排名样例