当前位置:   article > 正文

Elasticsearch分布式搜索

Elasticsearch分布式搜索

目录

实用篇-ES-环境搭建

1. 什么是elasticsearch

​2. 倒排索引

3. elasticsearch对比mysql

​4. 安装elasticsearch

5. 安装kibana

6. 安装IK分词器

7. IK分词器的词典扩展和停用

实用篇-ES-DSL操作文档

1. mapping属性

2. 创建索引库

3. 查询、修改、删除索引库

4. 新增、查询、删除文档

5. 修改文档

实用篇-ES-RestClient操作文档

1. RestClient案例准备

2. hotel数据结构分析

​3. 初始化RestClient

4. 创建索引库

5. 删除和判断索引库

6. 新增文档

7. 查询文档

8. 修改文档

9. 删除文档

10. 批量导入文档

实用篇-ES-DSL查询文档

1. DSL基本语法

2. 全文检索查询

3. 精确查询

4. 地理查询

5. 相关性算分

6. 函数算分查询

7. 布尔查询

8. 搜索结果处理-排序

9. 搜索结果处理-分页

10. 搜索结果处理-高亮

11. 搜索结果处理-总结

实用篇-ES-RestClient查询文档

1. 快速入门

2. match的三种查询

3. 解析代码的抽取

4. term、range精确查询

5. bool复合查询

6. geo_distance地理查询

7. 排序和分页

8. 高亮显示

实用篇-ES-黑马旅游案例

1. 环境准备-docker

2. 环境准备-elasticsearch

3. 环境准备-mysql

4. 环境准备-项目导入

5. 环境准备-同步数据

6. 搜索、分页

7. 条件过滤

8. 我附近的酒店

9. 广告置顶

10. 高亮显示

实用篇-ES-数据聚合

1. 聚合的分类

2. DSL实现Bucket聚合

3. DSL实现Metrics聚合

4. RestClient实现聚合

5. 多条件聚合

6. hm-带过滤条件的聚合

实用篇-ES-自动补全

1. 安装拼音分词器

2. 自定义分词器

3. 解决自定义分词器的问题

4. DSL实现自动补全查询

5. hm-修改酒店索引库数据结构

6. RestAPI实现自动补全查询

7. hm-搜索框自动补全查询

实用篇-ES-数据同步

1. 同步方案分析

2. hm-导入酒店管理项目

3. hm-声明队列和交换机

​4. hm-消息发送

5. hm-消息接收

6. hm-测试数据同步功能

实用篇-ES-es集群

1. 集群结构介绍

2. 搭建es集群

​3. 集群状态监控

4. 创建索引库

5. 集群职责及脑裂

​6. 新增和查询文档

7. 故障转移


实用篇-ES-环境搭建

ES是elasticsearch的简称。我在SpringBoot学习 '数据层解决方案' 的时候,写过一次ES笔记,可以结合一起看一下。
之前在SpringBoot里面写的相关ES笔记是基于Windows的,现在我们是基于docker容器来使用,需要你们提前准备好自己的docker容器以及掌握docker操作
常见的分布式搜索的技术,如下

  • 1、Elasticsearch: 开源的分布式搜索引擎
  • 2、Splunk: 商业项目,收费
  • 3、Solr: Apache的开源搜索引擎

随着业务发展,数据量越来越庞大,传统的MySQL数据库难以满足我们的需求,所以在微服务架构下,一般都会用到一种分布式搜索的技术,下面我们会学分布式搜索中最流行的一种,也就是elasticsearch的用法。包括学习elasticsearch的概念、安装、使用。其中学习elasticsearch的使用的时候,主要通过两个方面,一方面是elasticsearch对于索引库(类似于数据库,把数据导入进索引库,导入的数据就是所谓的文档,我们要实现文档的增删改查)的操作,另一方面我们还会学习elasticsearch官方提供的Restful的API(也就是Java客户端),来更方便的操作elasticsearch


1. 什么是elasticsearch


elasticsearch(读 yī læ sī tǐ kě sè chǐ)
kibana (读 kī bā nǎ)
elasticsearch是一款非常强大的开源搜索引擎技术,可以帮助我们从海量数据中快速找到需要的内容
1、elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。我们主要学习这个,elasticsearch底层实现是基于Lucene技术
2、Kibana是数据可视化的组件,也就是展示搜索出来的数据。elasticsearch的相关技术,了解即可
3、Logstash、Beats是负责数据抓取的组件。elasticsearch的相关技术,了解即可


Lucene是一个Java语言的搜索引擎类库(其实就是一个jar包),是Apache公司的顶级项目,由DougCutting于1999年研发
Lucene官网: https://lucene.apache.org
Lucene的优势
1、易扩展
2、高性能 (基于倒排索引)
Lucene的缺点
1、只限于Java语言开发
2、学习曲线陡峭,也就是API复杂不利于学习
3、不支持水平扩展,只负责如何实现搜索,不支持高并发、集群扩展
由于Lucene的缺点,诞生出了elasticsearch,与Lucene相比,elasticsearch(基于Lucene,且Compass是elasticsearch的前身)具有以下优点
1、支持分布式,可水平扩展
2、提供Restful接口,可被任何语言调用
elasticsearch的核心技术是倒排索引,下面会学


2. 倒排索引


传统数据库(例如MySQL)采用正向索引,例如给下表(tb_goods)中的id创建索引


elasticsearch采用倒排索引,例如给下表(tb_goods)中的id创建索引


总结
1、正向索引: 基于文档id来创建索引。查询词条时必须先找到文档,而后判断是否包含词条
2、倒排索引: 对文档内容进行分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条去查询文档id,然后获取到文档


3. elasticsearch对比mysql


elasticsearch
elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。注意elasticsearch的文档是以json形式存储的,也就是说,我们把数据(也叫文档)存储进elasticsearch时,这些文档数据就会自动被序列化为json格式,然后才存储进elasticsearch
elasticsearch的索引: 相同类型的文档的集合。索引和映射的概念,如下图


下面的表格是介绍elasticsearch中的各个概念以及含义,看的时候重点看第二、三列,第一列是为了让你更理解第二列的意思,所以在第一列拿MySQL的概念来做匹配。例如elasticsearch的Index表示索引也就是文档的集合,就相当于MySQL的Table(也就是表)

MySQL

Elasticsearch

说明

Table

Index

索引(index),就是文档的集合,类似数据库的表(table)

Row

Document

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

Column

Field

字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)

Schema

Mapping

Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)

SQL

DSL

DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

我们下面会学习映射的创建,以及文档的增删改查。这些操作在MySQL里面是通过SQL语句实现,但我们在elasticsearch中,会使用的是DSL语句来操作。
在elasticsearch中,当我们写好DSL语句,要通过http请求发给elasticsearch,elasticsearch才会响应,原因是在elasticsearch对外暴露的是Restful接口
上面基本都是在讲elasticsearch,那么是不是elasticsearch已经完全代码MySQL,答案并不是,两者擅长的事情不一样,如下

  • 1、MySQL: 擅长事务类型的操作,可以确保数据的安全和一致性。一般用于增删改
  • 2、Elasticsearch: 擅长海量数据的搜索、分析、计算。一般用于查询
  • 两者是互补关系,不是替代关系,因此在业务系统架构中,两者都会存在,让用户在MySQL里面增删改数据,然后MySQL把数据同步给elasticsearch,用户要查询的时候,就在elasticsearch里面进行查询


4. 安装elasticsearch


elasticsearch(读 yī læ sī tǐ kě sè chǐ)。注意elasticsearch、kibana、IK分词器,这三者通常是一起使用的
注意: 我们学习elasticsearch是基于docker容器来使用,需要你们提前准备好自己的docker容器以及掌握docker操作。elasticsearch一般都是搭配kibana(下节会学如何安装)来使用,kibana的作用是让我们非常方便的去编写elasticsearch中的DSL语句,从而去操作elasticsearch
【安装elasticsearch,简称es】
第一步: 创建网络。因为我们还需要部署kibana容器,因此需要让es和kibana容器互联

  1. systemctl start docker # 启动docker服务
  2. docker network create es-net #创建一个网络,名字是es-net


第二步: 加载es镜像。采用elasticsearch的7.12.1版本的镜像,这个镜像体积有800多MB,所以需要在Windows上下载链接安装包,下载下来是一个es的镜像tar包,然后传到CentOS7的/root目录

es.tar下载: https://cowtransfer.com/s/c84ac851b9ba44
kibana.tar下载: https://cowtransfer.com/s/a76d8339d7ba4d


第三步: 把在CentOS7的/root目录的es镜像,导入到docker

docker load -i es.tar

docker load -i  kibana.tar

docker images


第四步: 创建并运行es容器,容器名称就叫es。在docker(也叫Docker大容器、Docker主机、宿主机),根据es镜像来创建es容器

  1. docker run -d \
  2. --name es \
  3. -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  4. -e "discovery.type=single-node" \
  5. -v es-data:/usr/share/elasticsearch/data \
  6. -v es-plugins:/usr/share/elasticsearch/plugins \
  7. --privileged \
  8. --network es-net \
  9. -p 9200:9200 \
  10. -p 9300:9300 \
  11. elasticsearch:7.12.1


命令解释:
●-e "cluster.name=es-docker-cluster":设置集群名称
●-e "http.host=0.0.0.0":监听的地址,可以外网访问
●-e "ES_JAVA_OPTS=-Xms512m -Xmx512m":内存大小,不能低于512
●-e "discovery.type=single-node":运行模式,例如非集群模式
●-v es-data:/usr/share/elasticsearch/data:挂载数据卷,绑定es的数据目录
●-v es-logs:/usr/share/elasticsearch/logs:挂载数据卷,绑定es的日志目录
●-v es-plugins:/usr/share/elasticsearch/plugins:挂载数据卷,绑定es的插件目录
●--privileged:授予数据卷访问权
●--network es-net :加入一个名为es-net的网络中
●-p 9200:9200:端口映射配置,向外暴露的http请求端口,用于用户访问
●-p 9300:9300:端口映射配置,是es容器各个节点之间互相访问的端口,由于我们是单节点部署,所以用不到
●elasticsearch:7.12.1: 镜像名称,要把哪个镜像创建为容器,注意带版本号


然后,在浏览器中输入:http://你的ip地址:9200 即可看到elasticsearch的响应结果

http://192.168.200.231:9200/


5. 安装kibana


注意,是跟上一节的 '4. 安装elasticsearch' 一起操作,也就是说同一个实验。注意elasticsearch、kibana、IK分词器,这三者通常是一起使用的
kibana (读 kī bā nǎ)的作用: 让我们非常方便的去编写elasticsearch中的DSL语句,从而去操作elasticsearch(读 yī læ sī tǐ kě sè chǐ)
第一步: 确保docker是启动的

    # 启动docker服务

systemctl start docker


第二步: 加载kibana镜像。这个镜像体积有1.04G,所以需要在Windows上下载链接安装包,下载下来是一个es的镜像tar包,然后传到CentOS7的/root目录

es镜像: https://cowtransfer.com/s/1c16f55edf2341


第三步: 把在CentOS7的/root目录的kibana镜像,导入到docker

docker load -i kibana.tar


第四步: 创建并运行kibana容器,容器名称就叫kibana。在docker(也叫Docker大容器、Docker主机、宿主机),根据kibana镜像来创建kibana容器

  1. docker run -d \
  2. --name kibana \
  3. -e ELASTICSEARCH_HOSTS=http://es:9200 \
  4. --network=es-net \
  5. -p 5601:5601 \
  6. kibana:7.12.1

# --name: 指定容器的名字,例如kibana
# --network es-net: 加入一个名为es-net的网络中,与elasticsearch在同一个网络中
# -e ELASTICSEARCH_HOSTS: 由于kibana和es会被我们设置在同一个网络,所以这里的kibana可以通过容器名直接访问es,es的容器名我们在上一节设置的是es
# -e ELASTICSEARCH_HOSTS: 设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
# -p 5601:5601: 端口映射配置,向外暴露的http请求端口,用于用户访问 


第五步: kibana启动一般比较慢,需要多等待一会,可以通过命令

docker logs -f kibana

 #查看运行日志,当查看到下面的日志,说明成功 


第六步: 测试。在浏览器中输入:http://你的ip地址:5601 即可看到elasticsearch的响应结果


注意,我们在浏览器写DSL语句的时候,是带有提示功能的,非常好用


6. 安装IK分词器


IK分词器官网: https://github.com/medcl/elasticsearch-analysis-ik。注意elasticsearch、kibana、IK分词器,这三者通常是一起使用的
es在创建倒排索引时,需要对文档进行分词。在搜索时,需要对用户输入的内容进行分词。但默认的分词规则不支持中文处理,默认是只支持对英文进行分词,但是在正常业务中,我们需要处理的文档大多是中文,所以我们需要对中文进行分词,所以就需要安装IK分词器
为了直观的体现,es的分词规则不支持英文,我们可以做下面的小演示如下

  1. #测试分词器
  2. POST /_analyze
  3. {
  4. "text": "我正在学习安装IK分词器",
  5. "analyzer": "english"
  6. }


上图,就算分词器名称改成chinese或standard,对于中文的分词也是一字一分。解决: IK分词器。下面开始具体的安装IK分词器的操作
第一步: 我们在 '4. 安装elasticsearch' 创建elasticsearch容器时,指定了数据卷目录,其中有个数据卷指定了自定义名称为es-plugins,表示存放插件的数据卷
我们使用inspect命令把es-plugins数据卷的路径信息查询出来

docker volume inspect es-plugins


第二步: 下载ik.zip压缩包到Windows,下载后解压出来是ik文件夹
根据上面查询出来的es-plugins数据卷的路径,把ik文件夹上传到CentOS7的 /var/lib/docker/volumes/es-plugins/_data 目录

cd /var/lib/docker/volumes/es-plugins/_data


第三步: 重启elasticsearch容器,我们在 '4. 安装elasticsearch' 创建elasticsearch容器时,指定了自定义容器名称为es

  1. # 重启elasticsearch容器
  2. docker restart es


第四步: 查看elasticsearch容器的启动日志

docker logs -f es


第五步: 确保elasticsearch、kibana已正常运行

  1. docker restart es #启动elasticsearch容器
  2. docker restart kibana #启动kibana容器


第五步: 测试。在浏览器中输入:http://你的ip地址:5601 即可看到elasticsearch的响应结果
IK分词器包含两种模式:

  • ●ik_smart:最少切分,根据语义分词,正常分词
  • ●ik_max_word:最细切分,也是根据语义分词,分的词语更多,更细


7. IK分词器的词典扩展和停用


Ik分词器的分词,底层是一个字典,在字典里面会有各种各样的词语,当ik分词器需要对分词文本进行分词时,ik分词器就会拿着这个文本(乱拆成多个词或词语),一个个去字典里面匹配,如果能匹配到,证明某个词(乱拆成多个词或词语)是词,就把这个证明后的词分出来,作为一个词
第一个问题: 字典的分词效果是有限的,只能对日常生活中常见的语义相关的词,进行分词,由于字典的词汇量少,所以我们需要对字典进行扩展。
第二个问题: 字典的分词效果往往存在违禁词,我们不希望IK分词器能匹配并成功把词典里的违禁词作为分词,解决: 禁用某些敏感词条
解决:
1、要拓展或禁用ik分词器的词库,只需要修改一个分词器目录中的config目录中的IKAnalyzer.cfg.xml文件,如下

  1. cd /var/lib/docker/volumes/es-plugins/_data/ik/config
  2. vi IKAnalyzer.cfg.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
  3. <properties>
  4. <comment>IK Analyzer 扩展配置</comment>
  5. <!--用户可以在这里配置自己的扩展字典-->
  6. <entry key="ext_dict">ext.dic</entry>
  7. <!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典-->
  8. <entry key="ext_stopwords">stopword.dic</entry>
  9. </properties>


2、在config目录新建myext.dic文件,写入自己想要的特定词,也就是扩展词。新建mystopword.dic文件,写入自己想要禁用的特定词,也就是不参与分词的词

cd /var/lib/docker/volumes/es-plugins/_data/ik/config
  1. touch myext.dic
  2. vi myext.dic
  1. touch mystopword.dic
  2. vi mystopword.dic


3、重新启动elasticsearch、kibana

  1. docker restart es #启动elasticsearch容器
  2. docker restart kibana #启动kibana容器

4、测试。在浏览器中输入:http://你的ip地址:5601 即可看到elasticsearch的响应结果

http://192.168.200.231:5601

IK分词器包含两种模式:

  • ●ik_smart:最少切分,根据语义分词,正常分词
  • ●ik_max_word:最细切分,也是根据语义分词,分的词语更多,更细


根据上图,确实可以根据我们指定的扩展词进行分析,违禁词也确实被禁用没有被分词


实用篇-ES-DSL操作文档


1. mapping属性


mapping属性的官方文档: https://elastic.co/guide/en/elasticsearch/reference/current/index.html


下面的表格是介绍elasticsearch中的各个概念以及含义,看的时候重点看第二、三列,第一列是为了让你更理解第二列的意思,所以在第一列拿MySQL的概念来做匹配。例如elasticsearch的Index表示索引也就是文档的集合,就相当于MySQL的Table(也就是表)

MySQL

Elasticsearch

说明

Table

Index

索引(index),就是文档的集合,类似数据库的表(table)

Row

Document

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

Column

Field

字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)

Schema

Mapping

Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)

SQL

DSL

DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

mapping是对索引库中文档(es中的文档是json风格)的约束,常见的mapping属性包括如下
●type: 字段数据类型
○字符串(分两种): text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址等不可分词的词语)
○数值: long、integer、short、byte、double、float
○布尔: boolean
○日期: date
○对象:object
●index: 是否创建倒排索引,默认为true(也就是可参与分词搜索),改成false的话,别人就搜索不到你
●analyzer: 分词器,当字段类型是text时必须指定分词器。如果字段类型是keyword,那么不需要指定分词器
●properties: 子字段,也就是属性和子属性


2. 创建索引库

ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下

  1. PUT /索引库名称
  2. {
  3. "mappings": {//映射
  4. "properties": {//字段
  5. "字段名":{
  6. "type": "text",
  7. "analyzer": "ik_smart"
  8. },
  9. "字段名2":{
  10. "type": "keyword",
  11. "index": false //false表示这个字段不参与搜索,该字段不会创建为倒排索引,false不加双引号
  12. },
  13. "字段名3":{
  14. "properties": {//这个就是子字段
  15. "子字段": {
  16. "type": "keyword"
  17. }
  18. }
  19. },
  20. // ...略
  21. }
  22. }
  23. }


具体操作: 首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #启动kibana容器


第一步: 浏览器访问 http://你的ip地址:5601 。输入如下,注意把注释删掉

http://192.168.200.231:5601

  1. # 创建索引库,名字自定义,例如huanfqc
  2. PUT /huanfqc
  3. {
  4. "mappings": {
  5. "properties": {
  6. "xxinfo": {
  7. "type": "text", //文本类型,可以被分词器分词
  8. "analyzer": "ik_smart" //必须指定分词器
  9. },
  10. "xxemail": {
  11. "type": "keyword", //精确值类型,不可被分词器分词,本身就是最简的
  12. "index": false //不参与搜索,用户不能通过搜索搜到xxemail字段
  13. },
  14. "name": {
  15. "type": "object", //对象类型
  16. "properties": { //父字段
  17. "firstName": { //子字段
  18. "type": "keyword", //精确值类型,不可被分词器分词,本身就是最简的
  19. "index": true //参与搜索,用户通过可搜索到firstName字段
  20. },
  21. "lastName": { //子字段
  22. "type": "keyword", //精确值类型,不可被分词器分词,本身就是最简的
  23. "index": true //参与搜索,用户通过可搜索到lastName字段
  24. }
  25. }
  26. }
  27. }
  28. }
  29. }
  1. # 创建索引库,名字自定义,例如huanfqc
  2. PUT /huanfqc
  3. {
  4. "mappings": {
  5. "properties": {
  6. "xxinfo": {
  7. "type": "text",
  8. "analyzer": "ik_smart"
  9. },
  10. "xxemail": {
  11. "type": "keyword",
  12. "index": false
  13. },
  14. "name": {
  15. "type": "object",
  16. "properties": {
  17. "firstName": {
  18. "type": "keyword",
  19. "index": true
  20. },
  21. "lastName": {
  22. "type": "keyword",
  23. "index": true
  24. }
  25. }
  26. }
  27. }
  28. }
  29. }


3. 查询、修改、删除索引库


具体操作: 首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #启动kibana容器


1、查询索引库语法

GET /索引库名


2、往索引库添加新字段,注意: 索引库是无法被修改的,但是可以添加新字段(不能和已有的重复,否则报错)

  1. PUT /索引库名/_mapping
  2. {
  3. "properties": {
  4. "新字段名":{
  5. "type": "integer"
  6. }
  7. }
  8. }
  9. //例如如下
  10. PUT /huanfqc/_mapping
  11. {
  12. "properties": {
  13. "age": {
  14. "type": "integer"
  15. }
  16. }
  17. }


3、删除索引库语法

DELETE /索引库名


4. 新增、查询、删除文档


具体操作: 首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作。并且已经创建了名为huanfqc的索引库
1、新增文档的DSL语法,其实就是告诉kibana,我们要把文档添加到es的哪个索引库,如果省略文档id的话,es会默认随机生成一个,建议自己指定文档id

  1. POST /索引库名/_doc/文档id
  2. {
  3. "字段1": "值1",
  4. "字段2": "值2",
  5. "字段3": {
  6. "子属性1": "值3",
  7. "子属性2": "值4"
  8. },
  9. // ...
  10. }
  1. #创建文档
  2. POST /huanfqc/_doc/1
  3. {
  4. "xxinfo":"焕发@青春-学Java",
  5. "email": "123@huanfqc.cn",
  6. "name":{
  7. "firstName":"张",
  8. "lastName":"三"
  9. }
  10. }


2、查询文档。语法: GET /索引库名/_doc/文档id 。例如如下

  1. #查询文档
  2. GET /huanfqc/_doc/1


3、删除文档。语法: DELETE/索引库名/_doc/文档id 。例如如下

  1. #删除文档
  2. DELETE /huanfqc/_doc/1


5. 修改文档


具体操作: 首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作。并且已经创建了名为huanfqc的索引库、文档id为1的文档

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #启动kibana容器


方式一: 全量修改,会删除旧文档,添加新文档。修改文档的DSL语法,如下
注意: 这种操作是直接用新值覆盖掉旧的,如果只put一个字段那么其它字段就没了,所以,你不想修改的字段也要原样写出来,不然就没了
注意: 如果你写的文档id或字段不存在的话,本来是修改操作,结果就变成新增操作

  1. #修改文档
  2. PUT /索引库名/_doc/文档id
  3. {
  4. "字段1": "值1",
  5. "字段2": "值2",
  6. // ... 略
  7. }
  1. #修改文档
  2. PUT /huanfqc/_doc/1
  3. {
  4. "xxinfo":"修改你了-焕发@青春-学Java",
  5. "email": "123@huanfqc.cn",
  6. "name":{
  7. "firstName":"修改你了-张",
  8. "lastName":"三",
  9. "xxupdate":"我还加了一个"
  10. }
  11. }


方式二: 增量修改。修改指定字段的值
注意: 如果你写的文档id或字段不存在的话,本来是修改操作,结果就变成新增操作

  1. #修改文档
  2. POST /索引库名/_update/文档id
  3. {
  4. "doc": {
  5. "要修改的字段名": "新的值",
  6. }
  7. }
  1. #修改文档
  2. POST /huanfqc/_update/1
  3. {
  4. "doc": {
  5. "firstName": "修改-法外狂徒张三"
  6. }
  7. }


实用篇-ES-RestClient操作文档


下面的全部内容都是连续的,请不要跳过某一小节


1. RestClient案例准备


对es概念不熟悉的话,先去看上面的 '实用篇-ES-索引库和文档',不建议基础不牢就直接往下学
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求来发送给ES。
官方文档地址: https://www.elastic.co/guide/en/elasticsearch/client/index.html


下面就使用java程序进行操作es,不再像上面那样使用浏览器页面进行操作es
在下面会逐步完成一个案例: 下载提供的hotel-demo.zip压缩包,解压后是hotel-demo文件夹,是一个java项目工程文件,按照条件创建索引库,索引库名为hotel,mapping属性根据数据库结构定义。还要下载一个tb_hotel.sql文件,作为数据库数据

hotel-demo.zip下载:https://cowtransfer.com/s/36ac0a9f9d9043
tb_hotel.sql下载: https://cowtransfer.com/s/716f049850a849


第一步: 打开database软件,把tb_hotel.sql文件导入进你的数据库

  1. create database if not exists elasticsearch;
  2. use elasticsearch;


第二步: 把下载好的hotel-demo.zip压缩包解压,得到hotel-demo文件夹,在idea打开hotel-demo


第三步: 修改application.yml文件,配置正确的数据库信息


2. hotel数据结构分析


        在es中,mapping要考虑的问题: 字段名、数据类型、是否参与搜索、是否分词、如果分词那么分词器是什么。

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. #docker restart kibana #启动kibana容器


        我们刚刚在mysql导入了tb_hotel.sql,里面有很多数据,我们需要基于这些数据结构,去分析并尝试编写对应的es的mapping映射
        先看mysql中的数据类型(已有),如下

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


        根据mysql的数据类型等信息,编写es(没有,自己对着上面的sql写的)。注意经纬度在es里面是geo_point类型,且经纬度是写在一起的

  1. # 酒店的mapping
  2. PUT /hotel
  3. {
  4. "mappings": {
  5. "properties": {
  6. "id":{
  7. "type": "keyword",
  8. "index": true
  9. },
  10. "name":{
  11. "type": "text",
  12. "analyzer": "ik_max_word"
  13. },
  14. "address":{
  15. "type": "keyword",
  16. "index": false
  17. },
  18. "price":{
  19. "type": "float",
  20. "index": true
  21. },
  22. "score":{
  23. "type": "float",
  24. "index": true
  25. },
  26. "brand":{
  27. "type": "keyword",
  28. "index": true
  29. },
  30. "city":{
  31. "type": "keyword",
  32. "index": true
  33. },
  34. "business":{
  35. "type": "keyword",
  36. "index": true
  37. },
  38. "xxlocation":{
  39. "type": "geo_point",
  40. "index": true
  41. },
  42. "pic":{
  43. "type": "keyword",
  44. "index": false
  45. }
  46. }
  47. }
  48. }


3. 初始化RestClient


操作主要是在idea的hotel-demo项目进行,hotel-demo项目(不是springcloud项目,只是springboot项目)是前面 '1. RestClient案例准备',跳过的可回去补
第一步: 在hotel-demo项目的pom.xml添加如下

  1. <elasticsearch.version>7.12.1</elasticsearch.version>
  2. <!--引入es的RestHighLevelClient,版本要跟你Centos7里面部署的es版本一致-->
  3. <dependency>
  4. <groupId>org.elasticsearch.client</groupId>
  5. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  6. <version>7.12.1</version>
  7. </dependency>


第二步: 在hotel-demo项目的src/test/java/cn.itcast.hotel目录新建HotelIndexTest类,写入如下

  1. package cn.itcast.hotel;
  2. import org.apache.http.HttpHost;
  3. import org.elasticsearch.client.RestClient;
  4. import org.elasticsearch.client.RestHighLevelClient;
  5. import org.junit.jupiter.api.AfterEach;
  6. import org.junit.jupiter.api.BeforeEach;
  7. import org.junit.jupiter.api.Test;
  8. import java.io.IOException;
  9. public class HotelIndexTest {
  10. private RestHighLevelClient xxclient;
  11. @BeforeEach
  12. //该注解表示一开始就完成RestHighLevelClient对象的初始化
  13. void setUp() {
  14. this.xxclient = new RestHighLevelClient(RestClient.builder(
  15. //指定你Centos7部署的es的主机地址
  16. HttpHost.create("http://192.168.200.231:9200")
  17. ));
  18. }
  19. @AfterEach
  20. //该注解表示销毁,当对象运行完之后,就销毁这个对象
  21. void tearDown() throws IOException {
  22. this.xxclient.close();
  23. }
  24. @Test
  25. //现在才是测试代码,对象已经在上面初始化并且有销毁的步骤了,下面直接打印
  26. void yytestInit() {
  27. System.out.println(xxclient);
  28. }
  29. }


第三步: 确保下面的服务你都在Centos7里面启动了

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器


第四步: 运行HotelIndexTest类yytestInit方法


4. 创建索引库


不是通过kibana的浏览器控制台,通过DSL语句来进行操作es,在es里面创建索引库
而是通过上一节初始化的RestClient对象,在Java里面去操作es,创建es的索引库。根本不需要kibana做中间者


第一步: 在src/main/java/cn.itcast.hotel目录新建constants.HotelConstants类,里面写DSL语句,如下
其中长长的字符串就是我们在前面 '2. hotel数据结构分析' 里面写的。忘了怎么写出来的,可以回去看看

  1. package cn.itcast.hotel.constants;
  2. public class HotelConstants {
  3. public static final String xxMappingTemplate = "{\n" +
  4. " \"mappings\": {\n" +
  5. " \"properties\": {\n" +
  6. " \"id\":{\n" +
  7. " \"type\": \"keyword\",\n" +
  8. " \"index\": true\n" +
  9. " },\n" +
  10. " \"name\":{\n" +
  11. " \"type\": \"text\",\n" +
  12. " \"analyzer\": \"ik_max_word\",\n" +
  13. " \"copy_to\": \"all\"\n" +
  14. " },\n" +
  15. " \"address\":{\n" +
  16. " \"type\": \"keyword\",\n" +
  17. " \"index\": false\n" +
  18. " },\n" +
  19. " \"price\":{\n" +
  20. " \"type\": \"float\",\n" +
  21. " \"index\": true\n" +
  22. " },\n" +
  23. " \"score\":{\n" +
  24. " \"type\": \"float\",\n" +
  25. " \"index\": true\n" +
  26. " },\n" +
  27. " \"brand\":{\n" +
  28. " \"type\": \"keyword\",\n" +
  29. " \"index\": true,\n" +
  30. " \"copy_to\": \"all\"\n" +
  31. " },\n" +
  32. " \"city\":{\n" +
  33. " \"type\": \"keyword\",\n" +
  34. " \"index\": true\n" +
  35. " },\n" +
  36. " \"business\":{\n" +
  37. " \"type\": \"keyword\",\n" +
  38. " \"index\": true,\n" +
  39. " \"copy_to\": \"all\"\n" +
  40. " },\n" +
  41. " \"location\":{\n" +
  42. " \"type\": \"geo_point\",\n" +
  43. " \"index\": true\n" +
  44. " },\n" +
  45. " \"pic\":{\n" +
  46. " \"type\": \"keyword\",\n" +
  47. " \"index\": false\n" +
  48. " },\n" +
  49. " \"all\":{\n" +
  50. " \"type\": \"text\",\n" +
  51. " \"analyzer\": \"ik_max_word\"\n" +
  52. " }\n" +
  53. " }\n" +
  54. " }\n" +
  55. "}";
  56. }


第二步: 在hotel-demo项目的HotelIndexTest类,添加如下

  1. //使用xxclient对象,向es创建索引库
  2. @Test
  3. void xxcreateHotelIndex() throws IOException {
  4. //创建Request对象,自定义索引库名称为gghotel
  5. CreateIndexRequest request = new CreateIndexRequest("gghotel");
  6. //准备请求的参数: DSL语句
  7. request.source(xxMappingTemplate, XContentType.JSON);//注意xxMappingTemplate是第一步定义的的静态常量,导包别导错了
  8. //发送请求
  9. xxclient.indices().create(request, RequestOptions.DEFAULT);
  10. }


第三步: 确保下面的服务你都在Centos7里面启动了

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器


第四步: 验证。运行HotelIndexTest类的xxcreateHotelIndex测试方法


第五步: 如何更直观地验证,es里面确实有刚刚创建的索引库(刚刚创建的索引库是叫gghotel)
那就不得不运行kibana了,这样才能打开web浏览器页面,进行查询

docker restart kibana #启动kibana容器


浏览器访问 http://你的ip地址:5601


5. 删除和判断索引库


首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作。不需要浏览器操作es,所以不需要启动kibana容器

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. #docker restart kibana #启动kibana容器


1、删除索引库。在hotel-demo项目的HotelIndexTest类,添加如下。然后运行xxtestDeleteHotelIndex方法

  1. //删除索引库
  2. @Test
  3. void xxtestDeleteHotelIndex() throws IOException {
  4. //创建Request对象,指定要删除哪个索引库
  5. DeleteIndexRequest request = new DeleteIndexRequest("gghotel");
  6. //发送请求
  7. xxclient.indices().delete(request, RequestOptions.DEFAULT);
  8. }


2、判断索引库是否存在。在hotel-demo项目的HotelIndexTest类,添加如下。然后运行xxtestDeleteHotelIndex方法

  1. //判断索引库是否存在
  2. @Test
  3. void xxtestExistsHotelIndex() throws IOException {
  4. //创建Request对象,判断哪个索引库是否存在在es
  5. GetIndexRequest request = new GetIndexRequest("gghotel");
  6. //发送请求
  7. boolean ffexists = xxclient.indices().exists(request, RequestOptions.DEFAULT);
  8. //输出一下,看是否存在
  9. System.out.println(ffexists ? "索引库已经存在" : "索引库不存在");
  10. }


6. 新增文档


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. #docker restart kibana #启动kibana容器


案例: 去数据库查询酒店数据,把查询到的结果导入到hotel索引库(上一节我们已经创建一个名为gghotel的索引库),实现酒店数据的增删改查
简单说就是先去数据查酒店数据,把结果转换成索引库所需要的格式(新增文档的DSL语法)然后写到索引库,然后在索引库对这些酒店数据进行增删改查
【必备操作】
你们拿到代码的时候,这些操作已经做好,不需要再去做,我只是写出来方便后续复习
(1)、在pojo目录里面有一个Hotel类,作用是指定根数据库交互的字段,写入了如下

  1. package cn.itcast.hotel.pojo;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.annotation.TableId;
  4. import com.baomidou.mybatisplus.annotation.TableName;
  5. import lombok.Data;
  6. @Data
  7. @TableName("tb_hotel")
  8. public class Hotel {
  9. @TableId(type = IdType.INPUT)
  10. private Long id;
  11. private String name;
  12. private String address;
  13. private Integer price;
  14. private Integer score;
  15. private String brand;
  16. private String city;
  17. private String starName;
  18. private String business;
  19. private String longitude;
  20. private String latitude;
  21. private String pic;
  22. }


(2)、在pojo目录里面有一个HotelDoc类,作用是跟es的索引库交互的字段,也就是跟我们索引库里面的字段类型联调,写入了如下

  1. package cn.itcast.hotel.pojo;
  2. import lombok.Data;
  3. import lombok.NoArgsConstructor;
  4. @Data
  5. @NoArgsConstructor
  6. public class HotelDoc {
  7. private Long id;
  8. private String name;
  9. private String address;
  10. private Integer price;
  11. private Integer score;
  12. private String brand;
  13. private String city;
  14. private String starName;
  15. private String business;
  16. private String xxlocation;
  17. private String pic;
  18. public HotelDoc(Hotel hotel) {
  19. this.id = hotel.getId();
  20. this.name = hotel.getName();
  21. this.address = hotel.getAddress();
  22. this.price = hotel.getPrice();
  23. this.score = hotel.getScore();
  24. this.brand = hotel.getBrand();
  25. this.city = hotel.getCity();
  26. this.starName = hotel.getStarName();
  27. this.business = hotel.getBusiness();
  28. this.xxlocation = hotel.getLatitude() + ", " + hotel.getLongitude();
  29. this.pic = hotel.getPic();
  30. }
  31. }


(3)、在service新建了IHotelService接口,作用是写mybatis-plus向数据库发送请求用于查询数据库的数据

  1. package cn.itcast.hotel.service;
  2. import cn.itcast.hotel.pojo.Hotel;
  3. import com.baomidou.mybatisplus.extension.service.IService;
  4. public interface IHotelService extends IService<Hotel> {
  5. }


(4)、在service新建了impl目录,在impl目录里面有一个HotelService类,是IHotelService接口的实现类

  1. package cn.itcast.hotel.service.impl;
  2. import cn.itcast.hotel.mapper.HotelMapper;
  3. import cn.itcast.hotel.pojo.Hotel;
  4. import cn.itcast.hotel.service.IHotelService;
  5. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  6. import org.springframework.stereotype.Service;
  7. @Service
  8. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
  9. }


【具体操作】
第一步: 在hotel-demo项目的src/test/java/cn.itcast.hotel目录新建HotelDocumentTest类,写入如下

  1. package cn.itcast.hotel;
  2. import cn.itcast.hotel.pojo.Hotel;
  3. import cn.itcast.hotel.pojo.HotelDoc;
  4. import cn.itcast.hotel.service.IHotelService;
  5. import com.alibaba.fastjson.JSON;
  6. import org.apache.http.HttpHost;
  7. import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
  8. import org.elasticsearch.action.index.IndexRequest;
  9. import org.elasticsearch.client.RequestOptions;
  10. import org.elasticsearch.client.RestClient;
  11. import org.elasticsearch.client.RestHighLevelClient;
  12. import org.elasticsearch.client.indices.CreateIndexRequest;
  13. import org.elasticsearch.client.indices.GetIndexRequest;
  14. import org.elasticsearch.common.xcontent.XContentType;
  15. import org.junit.jupiter.api.AfterEach;
  16. import org.junit.jupiter.api.BeforeEach;
  17. import org.junit.jupiter.api.Test;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.boot.test.context.SpringBootTest;
  20. import java.io.IOException;
  21. import static cn.itcast.hotel.constants.HotelConstants.xxMappingTemplate;
  22. /**
  23. * @author 35238
  24. * @date 2023/6/9 0009 8:51
  25. */
  26. @SpringBootTest
  27. public class HotelDocumentTest {
  28. private RestHighLevelClient xxclient;
  29. @BeforeEach
  30. //该注解表示一开始就完成RestHighLevelClient对象的初始化
  31. void setUp() {
  32. this.xxclient = new RestHighLevelClient(RestClient.builder(
  33. //指定你Centos7部署的es的主机地址
  34. HttpHost.create("http://192.168.127.180:9200")
  35. ));
  36. }
  37. @AfterEach
  38. //该注解表示销毁,当对象运行完之后,就销毁这个对象
  39. void tearDown() throws IOException {
  40. this.xxclient.close();
  41. }
  42. //-----------------------------上面是初始化,下面是操作文档的测试-------------------------------------------
  43. @Autowired
  44. //注入写好的IHotelService接口,用于去数据库查询数据
  45. private IHotelService xxhotelService;
  46. @Test
  47. //新增文档到gghotel索引库,请保证你的es里面已经存在gghotel索引库
  48. void testAddDocument() throws IOException {
  49. //去数据库查询数据,我们简单查询一下id为61083的数据。由于在实体类里面定义的id是Long类型,所以要加L表示该数字是Long类型
  50. Hotel xxdataExample = xxhotelService.getById(61083L);
  51. //把上一行数据库查询出来的字段类型转为es的索引库的文档类型,才能往索引库里面新增文档
  52. HotelDoc xxhotelDoc = new HotelDoc(xxdataExample);
  53. //准备Request对象,往哪个索引库添加文档,文档的id需要自定义,xxdataExample.getId().toString()表示文档id跟数据库的id一致
  54. IndexRequest xxrequest = new IndexRequest("gghotel").id(xxdataExample.getId().toString());
  55. //准备JSON文档.JSON.toJSONString()是com.alibaba.fastjson提供的API,用于把JSON转为String
  56. xxrequest.source(JSON.toJSONString(xxhotelDoc),XContentType.JSON);
  57. //发送请求
  58. xxclient.index(xxrequest,RequestOptions.DEFAULT);
  59. }
  60. }


第二步: 验证。运行HotelDocumentTest类的testAddDocument方法


第三步: 如何更直观地验证,es里面的gghotel索引库里面有刚刚我们新增的文档,文档id就是数据里面的字段id
那就不得不运行kibana了,这样才能打开web浏览器页面,进行查询

docker restart kibana #启动kibana容器


浏览器访问 http://你的ip地址:5601


7. 查询文档


我们在刚刚,为了直观地验证是否成功新增文档,需要启动kibana,然后去浏览器页面进行查询,非常的麻烦,下面就来学习通过Java代码,进行查询文档
难点: 根据id查询到的文档数据类型是json,需要反序列化为java对象
第一步: 在HotelDocumentTest类,添加如下

  1. @Test
  2. void xxtestGetDocumentById() throws IOException {
  3. //准备Request对象,要查询哪个索引库,要查询的文档i,我们上面指定的文档id是跟数据库字段的id一致,上面新增的那条文档的id是61083
  4. GetRequest yyrequest = new GetRequest("gghotel", "61083");
  5. //发送请求,获取响应结果
  6. GetResponse yyresponse = xxclient.get(yyrequest, RequestOptions.DEFAULT);
  7. //解析响应结果。getSourceAsString方法的作用是把得到的JSON结果转为String
  8. String yyjson = yyresponse.getSourceAsString();
  9. //JSON.parseObject()是com.alibaba.fastjson提供的API,作用是对上面那行的yyjson进行反序列化
  10. //第一个参数是你要对谁进行反序列化,第二个参数是你想要的数据类型
  11. HotelDoc yyhotelDoc = JSON.parseObject(yyjson, HotelDoc.class);
  12. //输出一下查询结果
  13. System.out.println(yyhotelDoc);
  14. }


第二步: 运行HotelDocumentTest类的xxtestGetDocumentById方法


8. 修改文档


根据id修改酒店数据。修改es的索引库的文档的数据,有两种方式,前面在学kibana操作文档的时候学过,可前去 '实用篇-ES-索引库和文档' 进行复习
1、全量修改,会删除旧文档,添加新文档。注意: 这种操作是直接用新值覆盖掉旧的,如果只put一个字段那么其它字段就没了,所以,你不想修改的字段也要原样写出来,不然就没了。如果你写的文档id或字段不存在的话,本来是修改操作,结果就变成新增操作
2、增量修改(我们学习这种)。修改指定字段的值。如果你写的文档id或字段不存在的话,本来是修改操作,结果就变成新增操作
首先保证你已经做好了 '实用篇-ES-环境搭建' ,以及上面的五小节,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. #docker restart kibana #启动kibana容器


第一步: 在HotelDocumentTest类,添加如下

  1. @Test
  2. void xxtestUpdateDocument() throws IOException {
  3. //准备Request对象,要修改哪个索引库,要修改的文档id
  4. UpdateRequest zzrequest = new UpdateRequest("gghotel", "61083");
  5. //准备请求参数,要修改哪些字段,修改成什么
  6. zzrequest.doc(
  7. "name","我修改了你3个文档字段",
  8. "price","999",
  9. "city","北京"
  10. );
  11. //发送请求,获取响应结果
  12. xxclient.update(zzrequest,RequestOptions.DEFAULT);
  13. }


第二步: 先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)的数据。运行HotelDocumentTest类的xxtestGetDocumentById方法


第三步: 运行HotelDocumentTest类的xxtestUpdateDocument方法,作用是修改数据,也就是我们第一步写的代码


第四步: 在去查一下文档的数据,验证第三步是否修改成功。运行HotelDocumentTest类的xxtestGetDocumentById方法


9. 删除文档


首先保证你已经做好了 '实用篇-ES-环境搭建' ,以及上面的五小节,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. #docker restart kibana #启动kibana容器

第一步: 在HotelDocumentTest类,添加如下

  1. @Test
  2. void wwtestDeleteDocument() throws IOException {
  3. //准备Request对象,要删除哪个索引库,要删除的文档id
  4. DeleteRequest wwrequest = new DeleteRequest("gghotel", "61083");
  5. //发送请求
  6. xxclient.delete(wwrequest,RequestOptions.DEFAULT);
  7. }


第二步: 先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)能不能查询到。运行HotelDocumentTest类的xxtestGetDocumentById方法


第三步: 删除id为61083的文档(相当于删除mysql中id为某个数的那一行)。运行HotelDocumentTest类的wwtestDeleteDocument方法


第四步: 验证。再次执行第二步,也就是运行HotelDocumentTest类的xxtestGetDocumentById方法


10. 批量导入文档


建议去前面的 '6. 新增文档' 复习一下,在索引库里面新增一条文档,是怎么实现的
在上面的6、7、8、9节中,我们一直都是操作一条id为61083的文档(相当于数据库表的某一行)。我们如何把mysql的更多数据导入进es的索引库(相当于mysql的表)呢,下面就来学习批量把文档导入进索引库

  1. 思路:
  2. 1、利用mybatis-plus把MySQL中的酒店数据查询出来
  3. 2、将查询到的酒店数据转换为文档类型的数据
  4. 3、利用RestClient中bulk批处理方法,实现批量新增文档


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. #docker restart kibana #启动kibana容器


第一步: 在HotelDocumentTest类,添加如下

  1. @Test
  2. void testBulkRequest() throws IOException {
  3. //向数据库批量查询酒店数据,list方法表示查询数据库的所有数据
  4. List<Hotel> kkhotels = xxhotelService.list();
  5. //创建Request
  6. BulkRequest vvrequest = new BulkRequest();
  7. //准备参数,实际上就是添加多个新增的Request
  8. for (Hotel kkhotel : kkhotels) {
  9. //把遍历拿到的每个kkhotels转换为文档类型的数据
  10. HotelDoc ffhotelDoc = new HotelDoc(kkhotel);//HotelDoc是我们写的一个实体类
  11. //往哪个索引库批量新增文档、新增后的文档id是什么,文档类型是JSON
  12. vvrequest.add(new IndexRequest("gghotel")
  13. .id(ffhotelDoc.getId().toString())
  14. //JSON.parseObject()是com.alibaba.fastjson提供的API,作用是对ffhotelDoc进行反序列化准换为json类型
  15. .source(JSON.toJSONString(ffhotelDoc),XContentType.JSON));
  16. }
  17. //发送请求
  18. xxclient.bulk(vvrequest,RequestOptions.DEFAULT);
  19. }


第二步: 运行HotelDocumentTest类的testBulkRequest方法


\
第三步: 如何更直观地验证,es里面的gghotel索引库里面有刚刚我们新增的文档。那就不得不运行kibana了,这样才能打开web浏览器页面,进行查询

docker restart kibana #启动kibana容器

浏览器访问 http://你的ip地址:5601

输入如下DSL语句,表示查询某个索引库的所有文档

GET /gghotel/_search


上面我们导入了很多文档(相当于数据库的行,很多行),下面我们将着重学习使用DSL对这些文档数据,进行查询

实用篇-ES-DSL查询文档

官方文档: https://elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl。DSL是用来查询文档的
Elasticsearch提供了基于JSON的DSL来定义查询,简单说就是用json来描述查询条件,然后发送给es服务,最后es服务基于查询条件,把结果返回给我们
常见的查询类型包括如下:
1、查询所有: 查询出所有数据,一般在测试的时候使用

match_all


2、全文检索查询: 利用分词器对用户输入内容进行分词,然后去倒排索引库中匹配

match_query
multi_match_query


3、精确查询: 根据精确的词条值去查找数据,一般是查找keyword、数值、日期、boolean等类型的字段。这些字段是不需要分词的,但是依旧会建立倒排索引,把字段的整体内容作为一个词条,并存入倒排索引。在查找的时候,也就不需要分词,直接把搜索的内容去跟倒排索引匹配即可

ids,表示根据id,进行精确匹配
range,表示根据数值范围,进行精确匹配
term,表示根据数据的值,进行精确匹配


4、地理查询: 根据经纬度查询

geo_distance
geo_bounding_box


5、复合查询: 复合查询可将上述各种查询条件组合一起,合并查询条件

bool,利用逻辑运算把其它查询条件组合起来
function_score,用于控制相关度算分,算分会影响性能

 下面会一个个学


1. DSL基本语法


查询的基本语法

  1. #查询所有
  2. GET /hotel/_search
  3. {
  4. "query":{
  5. "match_all": {
  6. }
  7. }
  8. }


【具体操作】
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
浏览器访问 http://你的ip地址:5601
输入如下


存在一个问题,我们明明查询的是所有文档,查询结果也显示查询出所有的文档了,为什么上图右侧,鼠标往下拉,最多才只有10条文档数据呢
原因: 受默认的分页条件限制,后面学习的时候,会进行解决


2. 全文检索查询


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
全文检索查询,分为下面两种,会对用户输入内容进行分词之后,再进行匹配。也就是利用分词器对用户输入内容进行分词,然后去倒排索引库中匹配。
【第一种全文检索查询】

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "match": {
  5. "字段名": "TEXT"
  6. }
  7. }
  8. }


match查询(也就是match_query查询): 全文检索查询的一种,会对用户输入的内容进行分词,然后去倒排索引库检索
具体操作如下,为了让大家知道gghotel索引库有哪些字段,我把当初建立gghotel索引库的类先放出来
注意: 我要解释一下,上面有个字段叫xxALL,那个字段是当时自定义的,不清楚的话可回去看 '实用篇-ES-RestClient操作' 的 '2. hotel数据结构分析'。
xxALL的作用如下图,相当于一个大的字段,里面存放了几个小字段,优点是我们可以在这个大的字段里面搜索到多个小字段的信息


然后,我们就正式开始全文检索查询,输入如下。注意xxALL换成其它字段也没事,例如换成name字段。正常来说,我们检索name字段,就只在那么字段检索匹配的分词文档,但是在XXALL字段里面检索时,也会检索到name、brand、business字段,原因如上面那个图的copy_to属性
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示查询某个索引库的所有文档


【第二种全文检索查询】

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "multi_match": {
  5. "query": "TEXT",
  6. "字段名": ["FIELD1", " FIELD12"]
  7. }
  8. }
  9. }


multi_match(也就是multi_match_query查询): 与match查询类似,只不过允许同时查询多个字段
例如,输入如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示查询查询business、brand、name字段中包含'如家'的文档,满足一个字段即可


3. 精确查询


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器


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

term: 根据词条的精确值查询,强调精确匹配
range: 根据值的范围查询,例如金额、时间


【第一种精确查询 term】
具体操作如下

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "term": {
  5. "字段名": {
  6. "value": "VALUE"
  7. }
  8. }
  9. }
  10. }


第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示查询city字段为 '上海' 的文档,必须是 '上海' 才能被匹配,不对'上海'进行分词,也就是不会拆成'上'和'海'


【第一种精确查询 range】
具体操作如下

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "range": {
  5. "字段名": {
  6. "gte": 10,
  7. "lte": 20
  8. }
  9. }
  10. }
  11. }


第一步: 浏览器访问 http://你的ip地址:5601

第二步: 输入如下DSL语句,表示查找price字段满足200~300数值的文档,注意字段类型不能是binary,也就是price字段的类型不能是binary
gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于

  1. # 第一种精确查询 term。
  2. GET /gghotel/_search
  3. {
  4. "query":{
  5. "term": {
  6. "city": {
  7. "value": "上海"
  8. }
  9. }
  10. }
  11. }


4. 地理查询

首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器


根据经纬度查询。常见的使用场景包括: 查询附近酒店、附近出租车、搜索附近的人。使用方式有很多种,介绍如下

geo_bounding_box: 查询geo_point值落在某个矩形范围的所有文档,用两个点来围成的矩形范围
geo_distance: 查询到指定中心点,且小于某个距离值的所有文档,圆心到圆边的范围


【第一种地理查询 geo_bounding_box 不演示这种,不常用】

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "geo_bounding_box": {
  5. "字段名": {
  6. "top_left": {
  7. "lat": 31.1,
  8. "lon": 121.5
  9. },
  10. "bottom_right": {
  11. "lat": 30.9,
  12. "lon": 121.7
  13. }
  14. }
  15. }
  16. }
  17. }


【第一种地理查询 geo_distance 下面演示这种】

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "geo_distance": {
  5. "distance": "15km",
  6. "字段名": "31.21,121.5"
  7. }
  8. }
  9. }


具体操作如下,但是,为了让大家知道gghotel索引库有哪些字段,我把当初建立gghotel索引库的类先放出来
上面的xxlocation字段类型必须是geo_point,否则该字段不能用于地理查询
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句。表示查找xxlocation字段在(31.25±15km,121.5±15km)范围内的文档


5. 相关性算分


上面学的全文检索查询、精确查询、地理查询,这三种查询在es当中都称为简单查询,下面我们将学习复合查询。复合查询可以其它简单查询组合起来,实现更复杂的搜索逻辑,其中就有 '算分函数查询' 如下
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器


算分函数查询(function score): 可以控制文档相关性算分、控制文档排名。例如搜索'外滩' 和 '如家' 词条时,某个文档要是都能匹配这两个词条,那么在所有被搜索出来的文档当中,这个文档的位置就最靠前,简单说就是越匹配就排名越靠前

 

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "match": {
  5. "字段名": {
  6. "query": "词条"
  7. }
  8. }
  9. }
  10. }


具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示在name字段,哪个文档的匹配度高,排名就靠前

  1. GET /gghotel/_search
  2. {
  3. "query": {
  4. "match": {
  5. "name": {
  6. "query": "7天连锁酒店"
  7. }
  8. }
  9. }
  10. }


6. 函数算分查询


这是第一种复合查询
上面只是简单演了相关性打分中的函数算分查询,文档与搜索关键字的相关度越高,打分就越高,排名就越靠前。不过,有的时候,我们希望人为地去控制控制文档的排名,例如某些文档我们就希望排名靠前一点,算分高一点,此时就需要使用函数算分查询,下面就来学习 '函数算分查询'
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器


使用 ’函数算分查询(function score query)’,可以在原始的相关性算分的基础上加以修改,得到一个想要的算分,从而去影响文档的排名,语法如下

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "function_score": {
  5. "query": { "match": {"字段": "词条"} },
  6. "functions": [
  7. {
  8. "filter": {"term": {"指定字段": "值"}},
  9. "算分函数": 函数结果
  10. }
  11. ],
  12. "boost_mode": "加权模式"
  13. }
  14. }
  15. }


具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示在 '如家' 这个品牌中,字段为'北京'的酒店排名靠前一些

  1. GET /gghotel/_search
  2. {
  3. "query": {
  4. "function_score": {
  5. "query": {"match": {
  6. "brand": "如家"
  7. }},
  8. "functions": [
  9. {
  10. "filter": {
  11. "term": {
  12. "city": "北京"
  13. }
  14. },
  15. "weight": 2
  16. }
  17. ],
  18. "boost_mode": "sum"
  19. }
  20. }
  21. }


7. 布尔查询


这是第二种复合查询
布尔查询不会去修改算分,而是把多个查询语句组合成一起,形成新查询,这些被组合的查询语句,被称为子查询。子查询的组合方式有如下四种
1、must:必须匹配每个子查询,类似"与"
2、should:选择性匹配子查询,类似"或"
3、must_not:必须不匹配,不参与算分,类似"非"
4、filter:必须匹配,不参与算分

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {"term": {"字段名": "字段值" }}
  7. ],
  8. "should": [
  9. {"term": {"字段名": "字段值" }},
  10. {"term": {"字段名": "字段值" }}
  11. ],
  12. "must_not": [
  13. { "range": { "字段名": { "lte": 最小字段值 } }}
  14. ],
  15. "filter": [
  16. { "range": {"字段名": { "gte": 最大字段值 } }}
  17. ]
  18. }
  19. }
  20. }


具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示搜索名字包含'如家',价格不高于400,在坐标31.21,121.5周围10km范围内的文档
must表示匹配条件(注意写在must里面就会参与算分,也就是查询出来的score值会更高),must_not表示取反,filter表示过滤

  1. GET /gghotel/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "name": "如家"
  9. }
  10. }
  11. ],
  12. "must_not": [
  13. {
  14. "range": {
  15. "price": {
  16. "gt": 400
  17. }
  18. }
  19. }
  20. ],
  21. "filter": [
  22. {
  23. "geo_distance": {
  24. "distance": "10km",
  25. "xxlocation": {
  26. "lat": 31.21,
  27. "lon": 121.5
  28. }
  29. }
  30. }
  31. ]
  32. }
  33. }
  34. }


8. 搜索结果处理-排序


elasticsearch(称为es)支持对搜索的结果,进行排序,默认是根据 '相关度' 算分,也就是score值,根据score值进行排序。
可以排序的字段类型有: keyword类型、数值类型、地理坐标类型、日期类型

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
sort里面可以指定多个排序字段,用花括号隔开。排序方式: ASC(升序)、DESC(降序)

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "sort": [
  7. {
  8. "需要排序的字段名": "排序方式"
  9. }
  10. ]
  11. }


具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
【案例一】
第二步: 输入如下DSL语句,表示对所有的文档,根据评分(score)进行降序排序,如果评分相同就根据价格(price)升序排序

  1. GET /gghotel/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "sort": [
  7. {
  8. "score": "desc"
  9. },
  10. {
  11. "price": "asc"
  12. }
  13. ]
  14. }


上图的_score算分为null,是因为我们如果做了排序,那么打分就没有意义了,所以es就会放弃打分不再做相关性算分,提高效率
【案例二】
获取国内任意位置的经纬度的网站: 获取鼠标点击经纬度-地图属性-示例中心-JS API 2.0 示例 | 高德地图API
longitude 经度 latitude 纬度 (经度,纬度): 这是我们描述经纬度的写法,先经度再纬度,但是在下面写的时候
第三步: 输入如下DSL语句,表示找到(121.66053,28.28811)周围的文档,并按照距离进行升序排序
下面两种写法都是一样的,注意第二种写法前面写的是纬度,后面写的是经度

  1. 第一种写法
  2. GET /gghotel/_search
  3. {
  4. "query": {
  5. "match_all": {}
  6. },
  7. "sort": [
  8. {
  9. "_geo_distance": {
  10. "xxlocation": {
  11. "lat": 28.28811,
  12. "lon": 121.66053
  13. },
  14. "order": "asc"
  15. }
  16. }
  17. ]
  18. }
  19. 第二种写法
  20. GET /gghotel/_search
  21. {
  22. "query": {
  23. "match_all": {}
  24. },
  25. "sort": [
  26. {
  27. "_geo_distance": {
  28. "xxlocation": "28.28811,121.66053",
  29. "order": "asc"
  30. }
  31. }
  32. ]
  33. }


上图右侧的sort表示距离 '28.28811,121.66053' 有多少公里,例如281547.94km。
上图的_score算分为null,是因为我们如果做了排序,那么打分就没有意义了,所以es就会放弃打分不再做相关性算分,提高效率


9. 搜索结果处理-分页

 首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
elasticsearch(称为es)默认情况下只返回前10 条数据。而如果要查询更多数据就需要修改分页参数,分页参数包括from和size,语法如下

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "要查询的字段": {}
  5. },
  6. "from": 要查第几页, // 分页开始的位置,默认为0
  7. "size": 每页显示多少条文档, // 期望获取的文档总数
  8. "sort": [ //表示排序
  9. {"price": "排序方式"}
  10. ]
  11. }


具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示对所有的文档,根据价格(price)进行升序排序,每次分页显示20条数据,看的是第六页
size默认是10,表示一页显示多少条文档。from默认是0,表示你要看的是第一页

  1. GET /gghotel/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "sort": [
  7. {
  8. "price": {
  9. "order": "asc"
  10. }
  11. }
  12. ],
  13. "from": 0,
  14. "size": 20
  15. }


上面是基础的分页用法,下面来详细了解es的分页。es的底层使用的是倒排索引,是不利于做分页的,es采用的是逻辑上的分页,就会导致当是分布式的时候,就会产生下面的问题,因此es限制结果集最多为10000
ES是分布式的,所以会面临深度分页的问题。例如按price排序后,获取from=990,size=10的数据,如下图


深度分页查询的演示,输入如下DSL语句,表示

  1. GET /gghotel/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "sort": [
  7. {
  8. "price": {
  9. "order": "asc"
  10. }
  11. }
  12. ],
  13. "from": 9991,
  14. "size": 10
  15. }


百度在这方面,最多能查76页,每页显示十条。京东在这方面,最多能查第100页,所以深度分页我们不需要担心,10000的限制足够了。但是,如果说一定要去解决深度分页问题的话,ES提供了两种解决方案(两种分页方式),如下
官方文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html
1、search after: 分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。缺点: 只能向后翻页,不能向前翻页
场景: 没有随机翻页需求的搜索,例如手机向下滚动翻页。虽然没有查询上限,但是size不能超过10000
2、scroll: 原理将排序数据形成快照,保存在内存。官方已经不推荐使用。缺点: 由于是快照,所以不能查到实时数据,由于是保存在内存,所以消耗内存
场景: 海量数据的获取和迁移。从es7.1开始不推荐
我们上面用的分页方式是 'from+size' 。优点: 支持随机翻页。缺点: 存在深度分页问题。场景: 百度、京东、谷歌、淘宝


10. 搜索结果处理-高亮


高亮: 就是在搜索结果中把搜索关键字突出显示。高亮显示的原理如下
1、将搜索结果中的关键字用标签标记出来
2、在页面中给标签添加css样式


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
语法

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "match": { //match表示带关键字的查询
  5. "字段": "TEXT"
  6. }
  7. },
  8. "highlight": {
  9. "fields": {
  10. "字段名": {
  11. "require_field_match": "false",//默认是true,表示 '字段' 要和 '字段名' 要一致。如果我们写的是不一致的话,就需要修改为false
  12. "pre_tags": "<em>", // 用来标记高亮字段的前置标签,es会帮我们把标签加在关键字上。默认是<em>
  13. "post_tags": "</em>" // 用来标记高亮字段的后置标签,es会帮我们把标签加在关键字上。默认是</em>
  14. }
  15. }
  16. }
  17. }


具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示

  1. GET /gghotel/_search
  2. {
  3. "query": {
  4. "match": {
  5. "xxALL": "北京"
  6. }
  7. },
  8. "highlight": {
  9. "fields": {
  10. "name": {
  11. "require_field_match": "false",
  12. "pre_tags": "<em>",
  13. "post_tags": "</em>"
  14. }
  15. }
  16. }
  17. }


11. 搜索结果处理-总结


搜索结果处理的整体语法

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "match": {
  5. "字段名": "如家"
  6. }
  7. },
  8. "from": 0, // 分页开始的位置
  9. "size": 20, // 期望获取的文档总数
  10. "sort": [
  11. { "price": "asc" }, // 普通排序
  12. {
  13. "_geo_distance" : { // 距离排序
  14. "location" : "31.040699,121.618075",
  15. "order" : "asc",
  16. "unit" : "km"
  17. }
  18. }
  19. ],
  20. "highlight": {
  21. "fields": { // 高亮字段
  22. "字段名": {
  23. "pre_tags": "<em>", // 用来标记高亮字段的前置标签
  24. "post_tags": "</em>" // 用来标记高亮字段的后置标签
  25. }
  26. }
  27. }
  28. }

实用篇-ES-RestClient查询文档


1. 快速入门

上面的查询文档都是依赖kibana,在浏览器页面使用DSL语句去查询es,如何用java去查询es里面的文档(数据)呢


我们通过match_all查询来演示基本的API,注意下面演示的是 'match_all查询,也叫基础查询'
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. #docker restart kibana #启动kibana容器


在进行下面的操作之前,确保你已经看了前面 '实用篇-ES-RestClient操作文档' 学的 '1. RestClient案例准备',然后在进行下面的操作
第一步: 在src/test/java/cn.itcast.hotel目录新建HotelSearchTest类,写入如下

  1. package cn.itcast.hotel;
  2. import org.apache.http.HttpHost;
  3. import org.elasticsearch.action.search.SearchRequest;
  4. import org.elasticsearch.action.search.SearchResponse;
  5. import org.elasticsearch.client.RequestOptions;
  6. import org.elasticsearch.client.RestClient;
  7. import org.elasticsearch.client.RestHighLevelClient;
  8. import org.elasticsearch.index.query.QueryBuilders;
  9. import org.junit.jupiter.api.AfterEach;
  10. import org.junit.jupiter.api.BeforeEach;
  11. import org.junit.jupiter.api.Test;
  12. import java.io.IOException;
  13. public class HotelSearchTest {
  14. private RestHighLevelClient xxclient;
  15. @BeforeEach
  16. //该注解表示一开始就完成RestHighLevelClient对象的初始化
  17. void setUp() {
  18. this.xxclient = new RestHighLevelClient(RestClient.builder(
  19. //指定你Centos7部署的es的主机地址
  20. HttpHost.create("http://192.168.127.180:9200")
  21. ));
  22. }
  23. @AfterEach
  24. //该注解表示销毁,当对象运行完之后,就销毁这个对象
  25. void tearDown() throws IOException {
  26. this.xxclient.close();
  27. }
  28. //-----------------------------上面是初始化,下面是查询文档-快速入门的测试-------------------------------------------
  29. @Test
  30. void xxtestMatchAll() throws IOException {
  31. //准备Request对象,要查询哪个索引库,
  32. SearchRequest xxrequest = new SearchRequest("gghotel");
  33. //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
  34. xxrequest.source().query(QueryBuilders.matchAllQuery());
  35. //发送请求
  36. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  37. //在控制台输出结果
  38. System.out.println(xxresponse);
  39. }
  40. }


上面java代码以及对应的DSL语句如下图


第二步: 把控制台里面我们需要的数据解析出来。返回的数据很多,我们主要是解析hits里面的数据就行了
把HotelSearchTest类修改为如下,主要的修改是sout之前做了一次解析,拿到我们想要的数据

  1. package cn.itcast.hotel;
  2. import cn.itcast.hotel.pojo.HotelDoc;
  3. import com.alibaba.fastjson.JSON;
  4. import org.apache.http.HttpHost;
  5. import org.elasticsearch.action.search.SearchRequest;
  6. import org.elasticsearch.action.search.SearchResponse;
  7. import org.elasticsearch.client.RequestOptions;
  8. import org.elasticsearch.client.RestClient;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.index.query.QueryBuilders;
  11. import org.elasticsearch.search.SearchHit;
  12. import org.elasticsearch.search.SearchHits;
  13. import org.junit.jupiter.api.AfterEach;
  14. import org.junit.jupiter.api.BeforeEach;
  15. import org.junit.jupiter.api.Test;
  16. import java.io.IOException;
  17. public class HotelSearchTest {
  18. private RestHighLevelClient xxclient;
  19. @BeforeEach
  20. //该注解表示一开始就完成RestHighLevelClient对象的初始化
  21. void setUp() {
  22. this.xxclient = new RestHighLevelClient(RestClient.builder(
  23. //指定你Centos7部署的es的主机地址
  24. HttpHost.create("http://192.168.127.180:9200")
  25. ));
  26. }
  27. @AfterEach
  28. //该注解表示销毁,当对象运行完之后,就销毁这个对象
  29. void tearDown() throws IOException {
  30. this.xxclient.close();
  31. }
  32. //-----------------------------上面是初始化,下面是查询文档-快速入门的测试-------------------------------------------
  33. @Test
  34. void xxtestMatchAll() throws IOException {
  35. //准备Request对象,要查询哪个索引库,
  36. SearchRequest xxrequest = new SearchRequest("gghotel");
  37. //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
  38. xxrequest.source().query(QueryBuilders.matchAllQuery());
  39. //发送请求
  40. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  41. //解析获取到杂乱JSON数据
  42. SearchHits xxsearchHits = xxresponse.getHits();
  43. //获取总条数
  44. long xxtotal = xxsearchHits.getTotalHits().value;
  45. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  46. //获取hits数组
  47. SearchHit[] xxhits = xxsearchHits.getHits();
  48. //遍历数组,把hits数组的每个source取出来
  49. for (SearchHit xxhit : xxhits) {
  50. String xxjson = xxhit.getSourceAsString();
  51. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  52. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  53. //最终输出
  54. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  55. }
  56. }
  57. }


上面java代码以及对应的DSL语句如下图


2. match的三种查询


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器


全文检索的 match 和 multi_match 查询与 match_all 的API基本一致。差别是查询条件,也就是query的部分,如下图


我们刚刚在第一节演示的是 match_all(也叫基本查询) 查询,下面将演示 match(也叫单字段查询) 和 multi_match(也叫多字段查询) 查询
【matc_all查询,也叫基本查询,我们在 '快速入门' 已经演示过】
在HotelSearchTest类添加如下(已做可跳过)

  1. @Test
  2. void xxtestMatchAll() throws IOException {
  3. //准备Request对象,要查询哪个索引库,
  4. SearchRequest xxrequest = new SearchRequest("gghotel");
  5. //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
  6. xxrequest.source().query(QueryBuilders.matchAllQuery());
  7. //发送请求
  8. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  9. //解析获取到杂乱JSON数据
  10. SearchHits xxsearchHits = xxresponse.getHits();
  11. //获取总条数
  12. long xxtotal = xxsearchHits.getTotalHits().value;
  13. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  14. //获取hits数组
  15. SearchHit[] xxhits = xxsearchHits.getHits();
  16. //遍历数组,把hits数组的每个source取出来
  17. for (SearchHit xxhit : xxhits) {
  18. String xxjson = xxhit.getSourceAsString();
  19. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  20. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  21. //最终输出
  22. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  23. }
  24. }


【match 查询,也叫单字段查询】
在HotelSearchTest类添加如下

  1. @Test
  2. void xxtestMatch() throws IOException {
  3. //准备Request对象,要查询哪个索引库,
  4. SearchRequest xxrequest = new SearchRequest("gghotel");
  5. //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
  6. xxrequest.source().query(QueryBuilders.matchQuery("name","如家"));
  7. //发送请求
  8. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  9. //解析获取到杂乱JSON数据
  10. SearchHits xxsearchHits = xxresponse.getHits();
  11. //获取总条数
  12. long xxtotal = xxsearchHits.getTotalHits().value;
  13. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  14. //获取hits数组
  15. SearchHit[] xxhits = xxsearchHits.getHits();
  16. //遍历数组,把hits数组的每个source取出来
  17. for (SearchHit xxhit : xxhits) {
  18. String xxjson = xxhit.getSourceAsString();
  19. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  20. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  21. //最终输出
  22. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  23. }
  24. }


【multi_match 查询,也叫多字段查询】
在HotelSearchTest类添加如下

  1. @Test
  2. void xxtestMutilMatch() throws IOException {
  3. //准备Request对象,要查询哪个索引库,
  4. SearchRequest xxrequest = new SearchRequest("gghotel");
  5. //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
  6. xxrequest.source().query(QueryBuilders.multiMatchQuery("如家","name","business"));
  7. //发送请求
  8. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  9. //解析获取到杂乱JSON数据
  10. SearchHits xxsearchHits = xxresponse.getHits();
  11. //获取总条数
  12. long xxtotal = xxsearchHits.getTotalHits().value;
  13. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  14. //获取hits数组
  15. SearchHit[] xxhits = xxsearchHits.getHits();
  16. //遍历数组,把hits数组的每个source取出来
  17. for (SearchHit xxhit : xxhits) {
  18. String xxjson = xxhit.getSourceAsString();
  19. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  20. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  21. //最终输出
  22. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  23. }
  24. }


总结: 要构建查询条件,只要记住一个QueryBuilders类即可


3. 解析代码的抽取


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
我们发现对于 match、multi_match、match_all 查询,的解析部分的代码都是相同的,所以我们可以对解析部分的代码进行抽取(ctrl+alt+m),如下

  1. //这个方法就是我们抽取出来的,负责解析的
  2. private void handleResponse(SearchResponse xxresponse) {
  3. //解析获取到杂乱JSON数据
  4. SearchHits xxsearchHits = xxresponse.getHits();
  5. //获取总条数
  6. long xxtotal = xxsearchHits.getTotalHits().value;
  7. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  8. //获取hits数组
  9. SearchHit[] xxhits = xxsearchHits.getHits();
  10. //遍历数组,把hits数组的每个source取出来
  11. for (SearchHit xxhit : xxhits) {
  12. String xxjson = xxhit.getSourceAsString();
  13. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  14. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  15. //最终输出
  16. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  17. }
  18. }


4. term、range精确查询


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。精确查询常见的有两种:

term: 根据词条的精确值查询,强调精确匹配
range: 根据值的范围查询,例如金额、时间


java代码和DSL语句的对应关系如下图。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于


【term查询】在HotelSearchTest类添加如下

  1. @Test
  2. void xxtestTerm() throws IOException {
  3. //准备Request对象,要查询哪个索引库,
  4. SearchRequest xxrequest = new SearchRequest("gghotel");
  5. //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
  6. xxrequest.source().query(QueryBuilders.termQuery("city","上海"));
  7. //发送请求
  8. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  9. //解析获取到杂乱JSON数据
  10. SearchHits xxsearchHits = xxresponse.getHits();
  11. //获取总条数
  12. long xxtotal = xxsearchHits.getTotalHits().value;
  13. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  14. //获取hits数组
  15. SearchHit[] xxhits = xxsearchHits.getHits();
  16. //遍历数组,把hits数组的每个source取出来
  17. for (SearchHit xxhit : xxhits) {
  18. String xxjson = xxhit.getSourceAsString();
  19. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  20. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  21. //最终输出
  22. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  23. }
  24. }


【range查询】在HotelSearchTest类添加如下

  1. @Test
  2. void xxtestTerm() throws IOException {
  3. //准备Request对象,要查询哪个索引库,
  4. SearchRequest xxrequest = new SearchRequest("gghotel");
  5. //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
  6. xxrequest.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
  7. //发送请求
  8. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  9. //解析获取到杂乱JSON数据
  10. SearchHits xxsearchHits = xxresponse.getHits();
  11. //获取总条数
  12. long xxtotal = xxsearchHits.getTotalHits().value;
  13. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  14. //获取hits数组
  15. SearchHit[] xxhits = xxsearchHits.getHits();
  16. //遍历数组,把hits数组的每个source取出来
  17. for (SearchHit xxhit : xxhits) {
  18. String xxjson = xxhit.getSourceAsString();
  19. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  20. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  21. //最终输出
  22. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  23. }
  24. }


总结: 要构建查询条件,只要记住一个QueryBuilders类即可


5. bool复合查询


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
java代码和DSL语句的对应关系如下图


【bool查询】在HotelSearchTest类添加如下

  1. @Test
  2. void xxtestBool() throws IOException {
  3. //准备Request对象,要查询哪个索引库,
  4. SearchRequest xxrequest = new SearchRequest("gghotel");
  5. //创建布尔查询
  6. BoolQueryBuilder xxboolQuery = QueryBuilders.boolQuery();
  7. //添加must条件
  8. xxboolQuery.must(QueryBuilders.termQuery("city","上海"));
  9. //添加filter条件
  10. xxboolQuery.filter(QueryBuilders.rangeQuery("price").lte(200));
  11. //把上面的布尔对象传进来,就可以生效了
  12. xxrequest.source().query(xxboolQuery);
  13. //发送请求
  14. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  15. //解析获取到杂乱JSON数据
  16. SearchHits xxsearchHits = xxresponse.getHits();
  17. //获取总条数
  18. long xxtotal = xxsearchHits.getTotalHits().value;
  19. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  20. //获取hits数组
  21. SearchHit[] xxhits = xxsearchHits.getHits();
  22. //遍历数组,把hits数组的每个source取出来
  23. for (SearchHit xxhit : xxhits) {
  24. String xxjson = xxhit.getSourceAsString();
  25. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  26. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  27. //最终输出
  28. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  29. }
  30. }


总结: 要构建查询条件,只要记住一个QueryBuilders类即可


6. geo_distance地理查询


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
【geo_distance查询】在HotelSearchTest类添加如下

  1. @Test
  2. void xxtestGeoDistance() throws IOException {
  3. //准备Request对象,要查询哪个索引库,
  4. SearchRequest xxrequest = new SearchRequest("gghotel");
  5. //创建一个地理位置查询构造器,指定了要查询字段的是xxlocation
  6. GeoDistanceQueryBuilder xxgeoQuery = QueryBuilders.geoDistanceQuery("xxlocation");
  7. xxgeoQuery.point(31.25, 121.5);//设置查询的中心点坐标,这里的经度和纬度分别为 31.25121.5
  8. xxgeoQuery.distance(5, DistanceUnit.KILOMETERS);//设置查询的半径距离和单位,这里的 5 即表示 5 公里
  9. // 创建一个查询构造器
  10. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  11. // 将查询条件添加到查询构造器对象
  12. searchSourceBuilder.query(xxgeoQuery);
  13. // 将查询构造器的对象,添加到查询请求对象xxrequest中,就可以生效了
  14. xxrequest.source(searchSourceBuilder);
  15. //发送请求
  16. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  17. //解析获取到杂乱JSON数据
  18. SearchHits xxsearchHits = xxresponse.getHits();
  19. //获取总条数
  20. long xxtotal = xxsearchHits.getTotalHits().value;
  21. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  22. //获取hits数组
  23. SearchHit[] xxhits = xxsearchHits.getHits();
  24. //遍历数组,把hits数组的每个source取出来
  25. for (SearchHit xxhit : xxhits) {
  26. String xxjson = xxhit.getSourceAsString();
  27. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  28. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  29. //最终输出
  30. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  31. }
  32. }


总结: 要构建查询条件,只要记住一个QueryBuilders类即可


7. 排序和分页


上面是各种查询的学习,当我们把文档查询出来的时候,接下来就是对文档的处理,也就是你要把查询结果怎么展示出来。API以及对应的DSL语句如下图


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
【排序、分页】在HotelSearchTest类添加如下

  1. @Test
  2. void xxtestPageAndSort() throws IOException {
  3. //页面、每页大小。如果你要翻第二页,就把下面的xxpage改成2
  4. int xxpage = 1, xxsize = 5;
  5. //准备Request对象,要查询哪个索引库,
  6. SearchRequest xxrequest = new SearchRequest("gghotel");
  7. //查询全部
  8. xxrequest.source().query(QueryBuilders.matchAllQuery());
  9. //sort排序,asc升序,desc降序
  10. xxrequest.source().sort("price", SortOrder.ASC);
  11. //fromsize分页。例如查第一页,每页显示5条文档(数据)。from表示当前页,我们使用公式动态设定
  12. xxrequest.source().from((xxpage-1)*xxsize).size(5);
  13. //发送请求
  14. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  15. //解析获取到杂乱JSON数据
  16. SearchHits xxsearchHits = xxresponse.getHits();
  17. //获取总条数
  18. long xxtotal = xxsearchHits.getTotalHits().value;
  19. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  20. //获取hits数组
  21. SearchHit[] xxhits = xxsearchHits.getHits();
  22. //遍历数组,把hits数组的每个source取出来
  23. for (SearchHit xxhit : xxhits) {
  24. String xxjson = xxhit.getSourceAsString();
  25. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  26. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  27. //最终输出
  28. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  29. }
  30. }


8. 高亮显示


高亮API包括请求DSL构建和结果解析两部分,API和对应的DSL语句如下图,下图只是构建,再下面还有解析,高亮必须由构建+解析才能实现


解析,如下图


首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
【高亮显示-】在HotelSearchTest类添加如下

  1. @Test
  2. void xxtestHightlight() throws IOException {
  3. //准备Request对象,要查询哪个索引库,
  4. SearchRequest xxrequest = new SearchRequest("gghotel");
  5. //【构建】
  6. //查询name字段的文档
  7. xxrequest.source().query(QueryBuilders.matchQuery("name","上海"));
  8. //对查询出来的文档,的特定字段进行高亮显示
  9. xxrequest.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(true).preTags("<em>").postTags("</em>"));
  10. //发送请求
  11. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  12. //解析获取到杂乱JSON数据
  13. SearchHits xxsearchHits = xxresponse.getHits();
  14. //获取总条数
  15. long xxtotal = xxsearchHits.getTotalHits().value;
  16. System.out.println("共搜索到"+xxtotal+"条文档(数据)");
  17. //获取hits数组
  18. SearchHit[] xxhits = xxsearchHits.getHits();
  19. //遍历数组,把hits数组的每个source取出来
  20. for (SearchHit xxhit : xxhits) {
  21. String xxjson = xxhit.getSourceAsString();
  22. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  23. HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
  24. //【解析】获取高亮结果
  25. Map<String, HighlightField> xxhighlightFields = xxhit.getHighlightFields();
  26. //使用CollectionUtils工具类,进行判空,避免空指针
  27. if (!CollectionUtils.isEmpty(xxhighlightFields)){
  28. //根据字段名获取高亮结果
  29. HighlightField xxhighlightField = xxhighlightFields.get("name");
  30. //判断name不为空
  31. if (xxhighlightField != null) {
  32. //获取高亮值
  33. String xxname = xxhighlightField.getFragments()[0].string();
  34. //覆盖非高亮结果
  35. xxhotelDoc.setName(xxname);
  36. }
  37. }
  38. //最终输出
  39. System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
  40. }
  41. }

实用篇-ES-黑马旅游案例


这个案例我做了两遍才做出来了,第一遍排了一上午的错,所以很有必要进行环境准备,下面我将带领你对一下我的环境,全网最详细的自创笔记 


1. 环境准备-docker


企业部署一般都是采用Linux操作系统,而其中又数CentOS发行版占比最多,因此我们接下来会在CentOS下安装Docker
CentOS7镜像快速下载,我正在用的

https://cowtransfer.com/s/56423adc78374f

远程软件FinalShell快速下载,我正在用的

https://cowtransfer.com/s/b4c8fcb5c15244


idea+jdk下载

https://cowtransfer.com/s/7dcb0c66154d45


mysql下载

https://cowtransfer.com/s/567413055c9a4f




第一步: 在VMware虚拟机安装CentOS7系统,安装完成之后,使用finalshell远程软件进行远程连接,然后安装yum工具,执行如下

  1. yum install -y yum-utils \
  2. device-mapper-persistent-data \
  3. lvm2 --skip-broken


第二步: 更新本地镜像源,执行如下

  1. # 设置docker镜像源
  2. yum-config-manager \
  3. --add-repo \
  4. https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  5. sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
  6. yum makecache fast


第三步: 执行如下安装docker,稍等片刻,docker即可安装成功。docker-ce为社区免费版本

yum install -y docker-ce


第四步: 由于Docker应用需要用到各种端口,逐一去修改防火墙设置,会非常麻烦,所以学习期间直接关闭防火墙即可

  1. # 关闭
  2. systemctl stop firewalld
  3. # 禁止开机启动防火墙
  4. systemctl disable firewalld


第五步: 通过命令启动docker

  1. systemctl start docker # 启动docker服务
  2. systemctl stop docker # 停止docker服务
  3. systemctl restart docker # 重启docker服务
  4. systemctl status docker # 查看docker的启动状态
  5. docker -v # 查看docker版本


第六步: 配置docker镜像仓库,设置为国内的镜像仓库,以后在docker里面下载东西的时候速度会更快。分别执行如下命令

sudo mkdir -p /etc/docker       # 创建文件夹
  1. sudo tee /etc/docker/daemon.json <<-'EOF'
  2. {
  3. "registry-mirrors": ["https://93we6x1g.mirror.aliyuncs.com"]
  4. }
  5. EOF # 在刚刚创建的文件夹里面新建daemon.json文件,并写入花括号里面的数据
sudo systemctl daemon-reload    # 重新加载daemon.json文件
sudo systemctl restart docker   # 重启docker


2. 环境准备-elasticsearch


第一步: 创建网络

  1. systemctl start docker # 启动docker服务
  2. docker network create es-net #创建一个网络,名字是es-net


第二步: 加载es镜像。采用elasticsearch的7.12.1版本的镜像,这个镜像体积有800多MB,所以需要在Windows上下载链接安装包,下载下来是一个es的镜像tar包,然后传到CentOS7的/root目录

es.tar下载: https://cowtransfer.com/s/c84ac851b9ba44
kibana.tar下载: https://cowtransfer.com/s/a76d8339d7ba4d


第三步: 把在CentOS7的/root目录的es镜像,导入到docker

docker load -i es.tar


第四步: 创建并运行es容器,容器名称就叫es。在docker(也叫Docker大容器、Docker主机、宿主机),根据es镜像来创建es容器

  1. docker run -d \
  2. --name es \
  3. -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  4. -e "discovery.type=single-node" \
  5. -v es-data:/usr/share/elasticsearch/data \
  6. -v es-plugins:/usr/share/elasticsearch/plugins \
  7. --privileged \
  8. --network es-net \
  9. -p 9200:9200 \
  10. -p 9300:9300 \
  11. elasticsearch:7.12.1


然后,在浏览器中输入:http://你的ip地址:9200 即可看到elasticsearch的响应结果


3. 环境准备-mysql


第一步: 打开database软件,把tb_hotel.sql文件导入进你的数据库

tb_hotel.sql下载: https://cowtransfer.com/s/68c94a66d17248
  1. create database if not exists elasticsearch;
  2. use elasticsearch;


4. 环境准备-项目导入


第一步: 把下载好的hotel-demo.zip压缩包解压,得到hotel-demo文件夹,在idea打开hotel-demo

hotel-demo.zip下载:https://cowtransfer.com/s/36ac0a9f9d9043


第二步: 修改application.yml文件,配置正确的数据库信息


第三步: 把pom.xml修改为如下

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.3.10.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>cn.itcast.demo</groupId>
  12. <artifactId>hotel-demo</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>hotel-demo</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. <elasticsearch.version>7.12.1</elasticsearch.version>
  19. </properties>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-web</artifactId>
  24. </dependency>
  25. <!--引入es的RestHighLevelClient,版本要跟你Centos7里面部署的es版本一致-->
  26. <dependency>
  27. <groupId>org.elasticsearch.client</groupId>
  28. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  29. <version>7.12.1</version>
  30. </dependency>
  31. <dependency>
  32. <groupId>com.baomidou</groupId>
  33. <artifactId>mybatis-plus-boot-starter</artifactId>
  34. <version>3.4.2</version>
  35. </dependency>
  36. <dependency>
  37. <groupId>mysql</groupId>
  38. <artifactId>mysql-connector-java</artifactId>
  39. <scope>runtime</scope>
  40. </dependency>
  41. <dependency>
  42. <groupId>org.projectlombok</groupId>
  43. <artifactId>lombok</artifactId>
  44. <optional>true</optional>
  45. </dependency>
  46. <dependency>
  47. <groupId>org.springframework.boot</groupId>
  48. <artifactId>spring-boot-starter-test</artifactId>
  49. <scope>test</scope>
  50. <exclusions>
  51. <exclusion>
  52. <groupId>org.junit.vintage</groupId>
  53. <artifactId>junit-vintage-engine</artifactId>
  54. </exclusion>
  55. </exclusions>
  56. </dependency>
  57. <!--FastJson-->
  58. <dependency>
  59. <groupId>com.alibaba</groupId>
  60. <artifactId>fastjson</artifactId>
  61. <version>1.2.71</version>
  62. </dependency>
  63. <dependency>
  64. <groupId>org.apache.commons</groupId>
  65. <artifactId>commons-lang3</artifactId>
  66. </dependency>
  67. </dependencies>
  68. <build>
  69. <plugins>
  70. <plugin>
  71. <groupId>org.springframework.boot</groupId>
  72. <artifactId>spring-boot-maven-plugin</artifactId>
  73. <configuration>
  74. <excludes>
  75. <exclude>
  76. <groupId>org.projectlombok</groupId>
  77. <artifactId>lombok</artifactId>
  78. </exclude>
  79. </excludes>
  80. </configuration>
  81. </plugin>
  82. </plugins>
  83. </build>
  84. </project>


5. 环境准备-同步数据


把mysql的数据导入进es,我们需要使用前面学的es提供的RestClient,就可以通过java代码创建索引库,并往这个索引库导入文档(文档就是数据的意思)
第一步: 在hotel-demo项目的 src/test/java/cn.itcast.hotel 目录新建 HotelIndexTest 类,用于在es中创建名为hotel的索引库,写入如下
写完就运行xxcreateHotelIndex方法,把索引库创建出来

  1. package cn.itcast.hotel;
  2. import org.apache.http.HttpHost;
  3. import org.elasticsearch.client.RequestOptions;
  4. import org.elasticsearch.client.RestClient;
  5. import org.elasticsearch.client.RestHighLevelClient;
  6. import org.elasticsearch.client.indices.CreateIndexRequest;
  7. import org.elasticsearch.common.xcontent.XContentType;
  8. import org.junit.jupiter.api.AfterEach;
  9. import org.junit.jupiter.api.BeforeEach;
  10. import org.junit.jupiter.api.Test;
  11. import java.io.IOException;
  12. import static cn.itcast.hotel.constants.HotelConstants.xxMappingTemplate;
  13. public class HotelIndexTest {
  14. private RestHighLevelClient xxclient;
  15. @BeforeEach
  16. //该注解表示一开始就完成RestHighLevelClient对象的初始化
  17. void setUp() {
  18. this.xxclient = new RestHighLevelClient(RestClient.builder(
  19. //指定你Centos7部署的es的主机地址
  20. HttpHost.create("http://192.168.127.180:9200")
  21. ));
  22. }
  23. @AfterEach
  24. //该注解表示销毁,当对象运行完之后,就销毁这个对象
  25. void tearDown() throws IOException {
  26. this.xxclient.close();
  27. }
  28. //删除索引库(如果下面创建hotel索引库的时候,出现已存在,那么就执行这里的删除操作,把hotel索引库删掉,再创建)
  29. @Test
  30. void xxtestDeleteHotelIndex() throws IOException {
  31. //创建Request对象,指定要删除哪个索引库
  32. DeleteIndexRequest gghotel = new DeleteIndexRequest("hotel");
  33. //发送请求
  34. xxclient.indices().delete(gghotel, RequestOptions.DEFAULT);
  35. }
  36. //使用xxclient对象,向es创建索引库
  37. @Test
  38. void xxcreateHotelIndex() throws IOException {
  39. //创建Request对象,自定义索引库名称为hotel
  40. CreateIndexRequest gghotel = new CreateIndexRequest("hotel");
  41. //准备请求的参数: DSL语句
  42. gghotel.source(xxMappingTemplate, XContentType.JSON);
  43. //发送请求
  44. xxclient.indices().create(gghotel, RequestOptions.DEFAULT);
  45. }
  46. }


第二步: 在hotel-demo项目的 src/main/java/cn.itcast.hotel 目录新建 constants.HotelConstants类,为es准备数据,写入如下

  1. package cn.itcast.hotel.constants;
  2. public class HotelConstants {
  3. public static final String xxMappingTemplate = "{\n" +
  4. " \"mappings\": {\n" +
  5. " \"properties\": {\n" +
  6. " \"id\":{\n" +
  7. " \"type\": \"keyword\",\n" +
  8. " \"index\": true\n" +
  9. " },\n" +
  10. " \"name\":{\n" +
  11. " \"type\": \"text\",\n" +
  12. " \"analyzer\": \"ik_max_word\",\n" +
  13. " \"copy_to\": \"all\"\n" +
  14. " },\n" +
  15. " \"address\":{\n" +
  16. " \"type\": \"keyword\",\n" +
  17. " \"index\": false\n" +
  18. " },\n" +
  19. " \"price\":{\n" +
  20. " \"type\": \"integer\",\n" +
  21. " \"index\": true\n" +
  22. " },\n" +
  23. " \"score\":{\n" +
  24. " \"type\": \"integer\",\n" +
  25. " \"index\": true\n" +
  26. " },\n" +
  27. " \"brand\":{\n" +
  28. " \"type\": \"keyword\",\n" +
  29. " \"index\": true,\n" +
  30. " \"copy_to\": \"all\"\n" +
  31. " },\n" +
  32. " \"city\":{\n" +
  33. " \"type\": \"keyword\",\n" +
  34. " \"index\": true\n" +
  35. " },\n" +
  36. " \"business\":{\n" +
  37. " \"type\": \"keyword\",\n" +
  38. " \"index\": true,\n" +
  39. " \"copy_to\": \"all\"\n" +
  40. " },\n" +
  41. " \"location\":{\n" +
  42. " \"type\": \"geo_point\",\n" +
  43. " \"index\": true\n" +
  44. " },\n" +
  45. " \"starName\":{\n" +
  46. " \"type\": \"keyword\",\n" +
  47. " \"index\": true\n" +
  48. " },\n" +
  49. " \"pic\":{\n" +
  50. " \"type\": \"keyword\",\n" +
  51. " \"index\": false\n" +
  52. " },\n" +
  53. " \"all\":{\n" +
  54. " \"type\": \"text\",\n" +
  55. " \"analyzer\": \"ik_max_word\"\n" +
  56. " }\n" +
  57. " }\n" +
  58. " }\n" +
  59. "}";
  60. }


第三步(这一步好像项目本身做好了,已做可跳过): 在hotel-demo项目的 src/main/java/cn.itcast.hotel/pojo 目录新建Hotel、HotelDoc类,写入如下

  1. package cn.itcast.hotel.pojo;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.annotation.TableId;
  4. import com.baomidou.mybatisplus.annotation.TableName;
  5. import lombok.Data;
  6. @Data
  7. @TableName("tb_hotel")
  8. public class Hotel {
  9. @TableId(type = IdType.INPUT)
  10. private Long id;
  11. private String name;
  12. private String address;
  13. private Integer price;
  14. private Integer score;
  15. private String brand;
  16. private String city;
  17. private String starName;
  18. private String business;
  19. private String longitude;
  20. private String latitude;
  21. private String pic;
  22. }
  1. package cn.itcast.hotel.pojo;
  2. import lombok.Data;
  3. import lombok.NoArgsConstructor;
  4. @Data
  5. @NoArgsConstructor
  6. public class HotelDoc {
  7. private Long id;
  8. private String name;
  9. private String address;
  10. private Integer price;
  11. private Integer score;
  12. private String brand;
  13. private String city;
  14. private String starName;
  15. private String business;
  16. private String location;
  17. private String pic;
  18. public HotelDoc(Hotel hotel) {
  19. this.id = hotel.getId();
  20. this.name = hotel.getName();
  21. this.address = hotel.getAddress();
  22. this.price = hotel.getPrice();
  23. this.score = hotel.getScore();
  24. this.brand = hotel.getBrand();
  25. this.city = hotel.getCity();
  26. this.starName = hotel.getStarName();
  27. this.business = hotel.getBusiness();
  28. this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
  29. this.pic = hotel.getPic();
  30. }
  31. }


第四步: 在hotel-demo项目的 src/test/java/cn.itcast.hotel 目录新建 HotelDocumentTest类,用于把mysql的数据批量导入进es,写入如下
写完就运行testBulkRequest方法,把数据往索引库里面批量导入

  1. package cn.itcast.hotel;
  2. import cn.itcast.hotel.pojo.Hotel;
  3. import cn.itcast.hotel.pojo.HotelDoc;
  4. import cn.itcast.hotel.service.IHotelService;
  5. import com.alibaba.fastjson.JSON;
  6. import org.apache.http.HttpHost;
  7. import org.elasticsearch.action.bulk.BulkRequest;
  8. import org.elasticsearch.action.index.IndexRequest;
  9. import org.elasticsearch.client.RequestOptions;
  10. import org.elasticsearch.client.RestClient;
  11. import org.elasticsearch.client.RestHighLevelClient;
  12. import org.elasticsearch.common.xcontent.XContentType;
  13. import org.junit.jupiter.api.AfterEach;
  14. import org.junit.jupiter.api.BeforeEach;
  15. import org.junit.jupiter.api.Test;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.boot.test.context.SpringBootTest;
  18. import java.io.IOException;
  19. import java.util.List;
  20. @SpringBootTest
  21. public class HotelDocumentTest {
  22. private RestHighLevelClient xxclient;
  23. @BeforeEach
  24. //该注解表示一开始就完成RestHighLevelClient对象的初始化
  25. void setUp() {
  26. this.xxclient = new RestHighLevelClient(RestClient.builder(
  27. //指定你Centos7部署的es的主机地址
  28. HttpHost.create("http://192.168.127.180:9200")
  29. ));
  30. }
  31. @AfterEach
  32. //该注解表示销毁,当对象运行完之后,就销毁这个对象
  33. void tearDown() throws IOException {
  34. this.xxclient.close();
  35. }
  36. @Autowired
  37. //注入写好的IHotelService接口,用于去数据库查询数据
  38. private IHotelService xxhotelService;
  39. @Test
  40. void testBulkRequest() throws IOException {
  41. //向数据库批量查询酒店数据,list方法表示查询数据库的所有数据
  42. List<Hotel> kkhotels = xxhotelService.list();
  43. //创建Request
  44. BulkRequest vvrequest = new BulkRequest();
  45. //准备参数,实际上就是添加多个新增的Request
  46. for (Hotel kkhotel : kkhotels) {
  47. //把遍历拿到的每个kkhotels转换为文档类型的数据
  48. HotelDoc ffhotelDoc = new HotelDoc(kkhotel);//HotelDoc是我们写的一个实体类
  49. //往哪个索引库批量新增文档、新增后的文档id是什么,文档类型是JSON
  50. vvrequest.add(new IndexRequest("hotel")
  51. .id(ffhotelDoc.getId().toString())
  52. //JSON.parseObject()是com.alibaba.fastjson提供的API,作用是对ffhotelDoc进行反序列化准换为json类型
  53. .source(JSON.toJSONString(ffhotelDoc),XContentType.JSON));
  54. }
  55. //发送请求
  56. xxclient.bulk(vvrequest,RequestOptions.DEFAULT);
  57. }
  58. }


6. 搜索、分页


【请保证网络正常,否则页面的静态资源部分加载不了】

  1. systemctl start docker # 启动docker服务
  2. docker restart es #启动elasticsearch容器
  3. #docker restart kibana #启动kibana容器


实现步骤如下
一、根据前端的请求,定义实体类来接收前端的请求


(1)在pojo目录新建RequestParams类,写入如下

  1. package cn.itcast.hotel.pojo;
  2. import lombok.Data;
  3. @Data
  4. public class RequestParams {
  5. //搜索关键字
  6. private String key;
  7. //当前页码
  8. private Integer page;
  9. //每页大小
  10. private Integer size;
  11. //将来的排序字段
  12. private String sortBy;
  13. }

二、定义controller接口,接收页面请求,调用IHotelService的search方法


(1)在pojo目录新建PageResult类,写入如下

  1. package cn.itcast.hotel.pojo;
  2. import lombok.Data;
  3. import java.util.List;
  4. @Data
  5. public class PageResult {
  6. //总条数
  7. private Long total;
  8. //类型
  9. private List<HotelDoc> hotels;
  10. //不带参构造函数
  11. public PageResult() {
  12. }
  13. //带参构造函数
  14. public PageResult(Long total, List<HotelDoc> hotels) {
  15. this.total = total;
  16. this.hotels = hotels;
  17. }
  18. }

(2)、把IHotelService接口,修改为如下

  1. package cn.itcast.hotel.service;
  2. import cn.itcast.hotel.pojo.Hotel;
  3. import cn.itcast.hotel.pojo.PageResult;
  4. import cn.itcast.hotel.pojo.RequestParams;
  5. import com.baomidou.mybatisplus.extension.service.IService;
  6. public interface IHotelService extends IService<Hotel> {
  7. PageResult search(RequestParams params);
  8. }


(3)、把HotelDemoApplication启动类,修改为如下

  1. package cn.itcast.hotel;
  2. import com.baomidou.mybatisplus.annotation.DbType;
  3. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
  4. import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
  5. import org.apache.http.HttpHost;
  6. import org.elasticsearch.client.RestClient;
  7. import org.elasticsearch.client.RestHighLevelClient;
  8. import org.mybatis.spring.annotation.MapperScan;
  9. import org.springframework.boot.SpringApplication;
  10. import org.springframework.boot.autoconfigure.SpringBootApplication;
  11. import org.springframework.context.annotation.Bean;
  12. @MapperScan("cn.itcast.hotel.mapper")
  13. @SpringBootApplication
  14. public class HotelDemoApplication {
  15. public static void main(String[] args) {
  16. SpringApplication.run(HotelDemoApplication.class, args);
  17. }
  18. @Bean
  19. //注入es提供的RestHighLevelClient类
  20. public RestHighLevelClient client(){
  21. return new RestHighLevelClient(RestClient.builder(
  22. //指定你Centos7部署的es的主机地址
  23. HttpHost.create("http://192.168.127.180:9200")
  24. ));
  25. }
  26. }


(4)、在src/main/java/cn.itcast.hotel目录新建web.HotelController类,写入如下

  1. package cn.itcast.hotel.web;
  2. import cn.itcast.hotel.pojo.PageResult;
  3. import cn.itcast.hotel.pojo.RequestParams;
  4. import cn.itcast.hotel.service.IHotelService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.PostMapping;
  7. import org.springframework.web.bind.annotation.RequestBody;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RestController;
  10. @RestController
  11. @RequestMapping("/hotel")
  12. public class HotelController {
  13. //注入项目准备好的IHotelService接口
  14. @Autowired
  15. private IHotelService hotelService;
  16. @PostMapping("/list")
  17. //使用@RequestBody注解,接收前端的请求。PageResult、RequestParams是我们刚刚定义的实体类
  18. public PageResult search(@RequestBody RequestParams params){
  19. return hotelService.search(params);
  20. }
  21. }


三、定义IHotelService中的search方法,利用match查询实现根据关键字搜索酒店信息
(4)、把HotelService类,修改为如下

  1. package cn.itcast.hotel.service.impl;
  2. import cn.itcast.hotel.mapper.HotelMapper;
  3. import cn.itcast.hotel.pojo.Hotel;
  4. import cn.itcast.hotel.pojo.HotelDoc;
  5. import cn.itcast.hotel.pojo.PageResult;
  6. import cn.itcast.hotel.pojo.RequestParams;
  7. import cn.itcast.hotel.service.IHotelService;
  8. import com.alibaba.fastjson.JSON;
  9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  10. import org.elasticsearch.action.search.SearchRequest;
  11. import org.elasticsearch.action.search.SearchResponse;
  12. import org.elasticsearch.client.RequestOptions;
  13. import org.elasticsearch.client.RestHighLevelClient;
  14. import org.elasticsearch.index.query.QueryBuilders;
  15. import org.elasticsearch.search.SearchHit;
  16. import org.elasticsearch.search.SearchHits;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.stereotype.Service;
  19. import java.io.IOException;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. @Service
  23. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
  24. @Autowired
  25. //注入在引导类声明好的@Bean
  26. private RestHighLevelClient client;
  27. @Override
  28. public PageResult search(RequestParams params) {
  29. try {
  30. //准备Request对象,要查询哪个索引库,
  31. SearchRequest request = new SearchRequest("hotel");
  32. //【关键字搜索功能】
  33. String key = params.getKey();//前端传过来的搜索关键字
  34. //判断前端传的key是否为空,避免空指针
  35. if (key == null || "".equals(key)) {
  36. //matchAllQuery方法表示查es的全部文档,不需要条件
  37. request.source().query(QueryBuilders.matchAllQuery());
  38. } else {
  39. //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
  40. request.source().query(QueryBuilders.matchQuery("all", key));
  41. }
  42. //【分页功能】
  43. int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
  44. int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
  45. request.source().from((page - 1) * size).size(size);
  46. //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
  47. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  48. //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
  49. return handleResponse(response);
  50. } catch (IOException e) {
  51. throw new RuntimeException(e);
  52. }
  53. }
  54. //这个方法就是我们抽取出来的,负责解析的
  55. private PageResult handleResponse(SearchResponse response) {
  56. //解析获取到杂乱JSON数据
  57. SearchHits searchHits = response.getHits();
  58. //获取总条数
  59. long total = searchHits.getTotalHits().value;
  60. System.out.println("共搜索到"+total+"条文档(数据)");
  61. //获取hits数组
  62. SearchHit[] hits = searchHits.getHits();
  63. //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
  64. List<HotelDoc> hotels = new ArrayList<>();
  65. for (SearchHit hit : hits) {
  66. String json = hit.getSourceAsString();
  67. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  68. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  69. hotels.add(hotelDoc);
  70. }
  71. //封装返回
  72. return new PageResult(total,hotels);
  73. }
  74. }


四、运行HotelDemoApplication引导类,浏览器访问 http://localhost:8089/


7. 条件过滤


先看需求。添加品牌、城市、星级、价格等条件过滤功能


分析:
1、修改RequestParams类,添加brand、city、startName、minPrice、maxPrice等参数
2、修改HotelService类的search方法的实现,在关键字搜索时,如果brand等参数存在,就需要对其做过滤
3、注意多个条件之间是AND关系,组合多条件用BooleanQuery
4、参数存在才需要过滤,做好非空判断
5、city精确匹配,brand精确匹配,startName精确匹配,price范围过滤
第一步: 把RequestParams类,修改为如下

  1. package cn.itcast.hotel.pojo;
  2. import lombok.Data;
  3. @Data
  4. public class RequestParams {
  5. //搜索关键字
  6. private String key;
  7. //当前页码
  8. private Integer page;
  9. //每页大小
  10. private Integer size;
  11. //将来的排序字段
  12. private String sortBy;
  13. //城市
  14. private String city;
  15. //品牌
  16. private String brand;
  17. //星级
  18. private String starName;
  19. //价格最小值
  20. private Integer minPrice;
  21. //价格最大值
  22. private Integer maxPrice;
  23. }


第二步: 把HotelService类,修改为如下

  1. package cn.itcast.hotel.service.impl;
  2. import cn.itcast.hotel.mapper.HotelMapper;
  3. import cn.itcast.hotel.pojo.Hotel;
  4. import cn.itcast.hotel.pojo.HotelDoc;
  5. import cn.itcast.hotel.pojo.PageResult;
  6. import cn.itcast.hotel.pojo.RequestParams;
  7. import cn.itcast.hotel.service.IHotelService;
  8. import com.alibaba.fastjson.JSON;
  9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  10. import org.elasticsearch.action.search.SearchRequest;
  11. import org.elasticsearch.action.search.SearchResponse;
  12. import org.elasticsearch.client.RequestOptions;
  13. import org.elasticsearch.client.RestHighLevelClient;
  14. import org.elasticsearch.index.query.BoolQueryBuilder;
  15. import org.elasticsearch.index.query.QueryBuilders;
  16. import org.elasticsearch.search.SearchHit;
  17. import org.elasticsearch.search.SearchHits;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.stereotype.Service;
  20. import java.io.IOException;
  21. import java.util.ArrayList;
  22. import java.util.List;
  23. @Service
  24. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
  25. @Autowired
  26. //注入在引导类声明好的@Bean
  27. private RestHighLevelClient client;
  28. @Override
  29. public PageResult search(RequestParams params) {
  30. try {
  31. //准备Request对象,要查询哪个索引库,
  32. SearchRequest request = new SearchRequest("hotel");
  33. //【构建BooleanQuery】
  34. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  35. //【关键字搜索】
  36. //判断前端传的key是否为空,避免空指针
  37. String key = params.getKey();//前端传过来的搜索关键字
  38. //用的是must精确查找
  39. if (key == null || "".equals(key)) {
  40. //matchAllQuery方法表示查es的全部文档,不需要条件
  41. boolQuery.must(QueryBuilders.matchAllQuery());
  42. } else {
  43. //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
  44. boolQuery.must(QueryBuilders.matchQuery("all", key));
  45. }
  46. //【条件过滤】
  47. //城市,term精确查找,注意判空
  48. if(params.getCity() != null && !params.getCity().equals("")){
  49. boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
  50. }
  51. //品牌,term精确查找,注意判空
  52. if(params.getBrand() != null && !params.getBrand().equals("")){
  53. boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
  54. }
  55. //星级,term精确查找,注意判空
  56. if(params.getStarName() != null && !params.getStarName().equals("")){
  57. //注意下面那行的是starName,不要写成startName
  58. boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
  59. }
  60. //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
  61. if(params.getMinPrice() != null && params.getMaxPrice() != null){
  62. boolQuery.filter(QueryBuilders
  63. .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
  64. }
  65. //这一步必须有
  66. request.source().query(boolQuery);
  67. //【分页功能】
  68. int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
  69. int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
  70. request.source().from((page - 1) * size).size(size);
  71. //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
  72. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  73. //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
  74. return handleResponse(response);
  75. } catch (IOException e) {
  76. throw new RuntimeException(e);
  77. }
  78. }
  79. //这个方法就是我们抽取出来的,负责解析的
  80. private PageResult handleResponse(SearchResponse response) {
  81. //解析获取到杂乱JSON数据
  82. SearchHits searchHits = response.getHits();
  83. //获取总条数
  84. long total = searchHits.getTotalHits().value;
  85. System.out.println("共搜索到"+total+"条文档(数据)");
  86. //获取hits数组
  87. SearchHit[] hits = searchHits.getHits();
  88. //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
  89. List<HotelDoc> hotels = new ArrayList<>();
  90. for (SearchHit hit : hits) {
  91. String json = hit.getSourceAsString();
  92. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  93. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  94. hotels.add(hotelDoc);
  95. }
  96. //封装返回
  97. return new PageResult(total,hotels);
  98. }
  99. }


第三步: 运行HotelDemoApplication引导类,浏览器访问 http://localhost:8089/


第四步: 解决"四星","五星" 无法作为条件进行查询的问题


8. 我附近的酒店


需求: 实现前端页面点击定位后,会将你所在的位置发送给后台,前端的请求信息如下,会向后端发送location参数。
如果谷歌浏览器发送不了位置请求的话,建议临时换成火狐浏览器


分析:
1、修改RequestParams参数,接收来自前端的location字段
2、修改HotelService类的search方法的业务逻辑,如果location有值,就添加根据geo_distance排序的功能
java代码实现距离排序,对应的DSL语句如下


第一步: 把RequestParams类,修改为如下

  1. package cn.itcast.hotel.pojo;
  2. import lombok.Data;
  3. @Data
  4. public class RequestParams {
  5. //搜索关键字
  6. private String key;
  7. //当前页码
  8. private Integer page;
  9. //每页大小
  10. private Integer size;
  11. //将来的排序字段
  12. private String sortBy;
  13. //城市
  14. private String city;
  15. //品牌
  16. private String brand;
  17. //星级
  18. private String starName;
  19. //价格最小值
  20. private Integer minPrice;
  21. //价格最大值
  22. private Integer maxPrice;
  23. //地理位置查询的字段,前端会把location传给我们
  24. private String location;
  25. }


第二步: 把HotelDoc类,修改为如下

  1. package cn.itcast.hotel.pojo;
  2. import lombok.Data;
  3. import lombok.NoArgsConstructor;
  4. @Data
  5. @NoArgsConstructor
  6. public class HotelDoc {
  7. private Long id;
  8. private String name;
  9. private String address;
  10. private Integer price;
  11. private Integer score;
  12. private String brand;
  13. private String city;
  14. private String starName;
  15. private String business;
  16. private String location;
  17. private String pic;
  18. //地理位置查询相关的字段。distance字段用于保存解析后的距离值
  19. private Object distance;
  20. public HotelDoc(Hotel hotel) {
  21. this.id = hotel.getId();
  22. this.name = hotel.getName();
  23. this.address = hotel.getAddress();
  24. this.price = hotel.getPrice();
  25. this.score = hotel.getScore();
  26. this.brand = hotel.getBrand();
  27. this.city = hotel.getCity();
  28. this.starName = hotel.getStarName();
  29. this.business = hotel.getBusiness();
  30. this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
  31. this.pic = hotel.getPic();
  32. }
  33. }


第三步: 把HotelService类,修改为如下

  1. package cn.itcast.hotel.service.impl;
  2. import cn.itcast.hotel.mapper.HotelMapper;
  3. import cn.itcast.hotel.pojo.Hotel;
  4. import cn.itcast.hotel.pojo.HotelDoc;
  5. import cn.itcast.hotel.pojo.PageResult;
  6. import cn.itcast.hotel.pojo.RequestParams;
  7. import cn.itcast.hotel.service.IHotelService;
  8. import com.alibaba.fastjson.JSON;
  9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  10. import org.elasticsearch.action.search.SearchRequest;
  11. import org.elasticsearch.action.search.SearchResponse;
  12. import org.elasticsearch.client.RequestOptions;
  13. import org.elasticsearch.client.RestHighLevelClient;
  14. import org.elasticsearch.common.geo.GeoPoint;
  15. import org.elasticsearch.common.unit.DistanceUnit;
  16. import org.elasticsearch.index.query.BoolQueryBuilder;
  17. import org.elasticsearch.index.query.QueryBuilders;
  18. import org.elasticsearch.search.SearchHit;
  19. import org.elasticsearch.search.SearchHits;
  20. import org.elasticsearch.search.sort.SortBuilders;
  21. import org.elasticsearch.search.sort.SortOrder;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.stereotype.Service;
  24. import java.io.IOException;
  25. import java.util.ArrayList;
  26. import java.util.List;
  27. @Service
  28. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
  29. @Autowired
  30. //注入在引导类声明好的@Bean
  31. private RestHighLevelClient client;
  32. @Override
  33. public PageResult search(RequestParams params) {
  34. try {
  35. //准备Request对象,要查询哪个索引库,
  36. SearchRequest request = new SearchRequest("hotel");
  37. //【构建BooleanQuery】
  38. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  39. //【关键字搜索】
  40. //判断前端传的key是否为空,避免空指针
  41. String key = params.getKey();//前端传过来的搜索关键字
  42. //用的是must精确查找
  43. if (key == null || "".equals(key)) {
  44. //matchAllQuery方法表示查es的全部文档,不需要条件
  45. boolQuery.must(QueryBuilders.matchAllQuery());
  46. } else {
  47. //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
  48. boolQuery.must(QueryBuilders.matchQuery("all", key));
  49. }
  50. //【条件过滤】
  51. //城市,term精确查找,注意判空
  52. if(params.getCity() != null && !params.getCity().equals("")){
  53. boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
  54. }
  55. //品牌,term精确查找,注意判空
  56. if(params.getBrand() != null && !params.getBrand().equals("")){
  57. boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
  58. }
  59. //星级,term精确查找,注意判空
  60. if(params.getStarName() != null && !params.getStarName().equals("")){
  61. boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
  62. }
  63. //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
  64. if(params.getMinPrice() != null && params.getMaxPrice() != null){
  65. boolQuery.filter(QueryBuilders
  66. .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
  67. }
  68. //这一步必须有
  69. request.source().query(boolQuery);
  70. //【分页功能】
  71. int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
  72. int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
  73. request.source().from((page - 1) * size).size(size);
  74. //【地理排序功能】
  75. String location = params.getLocation();
  76. //对前端传的location进行判断是否为空
  77. if (location != null && !location.equals("")){
  78. //sort排序,指定是geoDistanceSort地理坐标排序,要排序的字段是location,中心点是new GeoPoint(location)
  79. request.source().sort(SortBuilders
  80. .geoDistanceSort("location",new GeoPoint(location))
  81. .order(SortOrder.ASC) //升序排序
  82. .unit(DistanceUnit.KILOMETERS) //地理坐标的单位
  83. );
  84. }
  85. //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
  86. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  87. //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
  88. return handleResponse(response);
  89. } catch (IOException e) {
  90. throw new RuntimeException(e);
  91. }
  92. }
  93. //这个方法就是我们抽取出来的,负责解析的
  94. private PageResult handleResponse(SearchResponse response) {
  95. //解析获取到杂乱JSON数据
  96. SearchHits searchHits = response.getHits();
  97. //获取总条数
  98. long total = searchHits.getTotalHits().value;
  99. System.out.println("共搜索到"+total+"条文档(数据)");
  100. //获取hits数组
  101. SearchHit[] hits = searchHits.getHits();
  102. //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
  103. List<HotelDoc> hotels = new ArrayList<>();
  104. for (SearchHit hit : hits) {
  105. String json = hit.getSourceAsString();
  106. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  107. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  108. //【地理坐标查询的解析】,通过getSortValues方法来获取排序值,得到的是多个值也就是数组,我们只需要一个值
  109. Object[] sortValues = hit.getSortValues();
  110. //判断是否为空
  111. if (sortValues.length>0){
  112. Object sortValue = sortValues[0];
  113. //把拿到的sortValue返回到页面,也就是需要把sortValue值放到HoteDoc
  114. hotelDoc.setDistance(sortValue);
  115. }
  116. hotels.add(hotelDoc);
  117. }
  118. //封装返回
  119. return new PageResult(total,hotels);
  120. }
  121. }

第四步: 重启HotelDemoApplication引导类,浏览器访问http://localhost:8089/。点击定位按钮,查看是否能查询出距离自己最近的酒店并显示米数


9. 广告置顶

需求: 让指定的酒店在搜索结果中排名置顶。我们给需要置顶的酒店文档添加一个标记。然后利用function score给带有标记的文档增加权重
分析:
1、给HotelDoc类添加isAD字段,Boolean类型
2、挑选几个你喜欢的酒店,给它的文档数据添加isAD字段,值为true
3、修改HotelService类的search方法,添加function score功能,给isAD值为true的酒店增加权重
Function Score查询可以控制文档的相关性算分,java代码以及对应DSL语句如下图


第一步: 把HotelDoc类,修改为如下

  1. package cn.itcast.hotel.pojo;
  2. import lombok.Data;
  3. import lombok.NoArgsConstructor;
  4. @Data
  5. @NoArgsConstructor
  6. public class HotelDoc {
  7. private Long id;
  8. private String name;
  9. private String address;
  10. private Integer price;
  11. private Integer score;
  12. private String brand;
  13. private String city;
  14. private String starName;
  15. private String business;
  16. private String location;
  17. private String pic;
  18. //地理位置查询相关的字段。distance字段用于保存解析后的距离值
  19. private Object distance;
  20. //用于广告置顶的字段
  21. private Boolean isAD;
  22. public HotelDoc(Hotel hotel) {
  23. this.id = hotel.getId();
  24. this.name = hotel.getName();
  25. this.address = hotel.getAddress();
  26. this.price = hotel.getPrice();
  27. this.score = hotel.getScore();
  28. this.brand = hotel.getBrand();
  29. this.city = hotel.getCity();
  30. this.starName = hotel.getStarName();
  31. this.business = hotel.getBusiness();
  32. this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
  33. this.pic = hotel.getPic();
  34. }
  35. }


第二步: 使用DSL语句为索引库增加字段,由于使用DSL语句需要在浏览器使用kibana,所以我们把docker里面的kibana容器运行一下

docker restart kibana #启动kibana容器


第三步: 启动kibana之后,浏览器访问 http://你的ip地址:5601
第四步: DSL语句,表示给某个id文档添加新字段。id不一定要跟我一样,随便去mysql数据库找几个id就行

  1. POST /hotel/_update/1557997004
  2. {
  3. "doc":{
  4. "isAD": true
  5. }
  6. }
  7. POST /hotel/_update/1406627919
  8. {
  9. "doc":{
  10. "isAD": true
  11. }
  12. }


第五步: 把HotelService类,修改为如下

  1. package cn.itcast.hotel.service.impl;
  2. import cn.itcast.hotel.mapper.HotelMapper;
  3. import cn.itcast.hotel.pojo.Hotel;
  4. import cn.itcast.hotel.pojo.HotelDoc;
  5. import cn.itcast.hotel.pojo.PageResult;
  6. import cn.itcast.hotel.pojo.RequestParams;
  7. import cn.itcast.hotel.service.IHotelService;
  8. import com.alibaba.fastjson.JSON;
  9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  10. import org.elasticsearch.action.search.SearchRequest;
  11. import org.elasticsearch.action.search.SearchResponse;
  12. import org.elasticsearch.client.RequestOptions;
  13. import org.elasticsearch.client.RestHighLevelClient;
  14. import org.elasticsearch.common.geo.GeoPoint;
  15. import org.elasticsearch.common.unit.DistanceUnit;
  16. import org.elasticsearch.index.query.BoolQueryBuilder;
  17. import org.elasticsearch.index.query.QueryBuilders;
  18. import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
  19. import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
  20. import org.elasticsearch.search.SearchHit;
  21. import org.elasticsearch.search.SearchHits;
  22. import org.elasticsearch.search.sort.SortBuilders;
  23. import org.elasticsearch.search.sort.SortOrder;
  24. import org.springframework.beans.factory.annotation.Autowired;
  25. import org.springframework.stereotype.Service;
  26. import java.io.IOException;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. @Service
  30. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
  31. @Autowired
  32. //注入在引导类声明好的@Bean
  33. private RestHighLevelClient client;
  34. @Override
  35. public PageResult search(RequestParams params) {
  36. try {
  37. //准备Request对象,要查询哪个索引库,
  38. SearchRequest request = new SearchRequest("hotel");
  39. //【构建BooleanQuery,下面那行的boolQuery是原始查询】
  40. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  41. //【关键字搜索】
  42. //判断前端传的key是否为空,避免空指针
  43. String key = params.getKey();//前端传过来的搜索关键字
  44. //用的是must精确查找
  45. if (key == null || "".equals(key)) {
  46. //matchAllQuery方法表示查es的全部文档,不需要条件
  47. boolQuery.must(QueryBuilders.matchAllQuery());
  48. } else {
  49. //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
  50. boolQuery.must(QueryBuilders.matchQuery("all", key));
  51. }
  52. //【条件过滤】
  53. //城市,term精确查找,注意判空
  54. if(params.getCity() != null && !params.getCity().equals("")){
  55. boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
  56. }
  57. //品牌,term精确查找,注意判空
  58. if(params.getBrand() != null && !params.getBrand().equals("")){
  59. boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
  60. }
  61. //星级,term精确查找,注意判空
  62. if(params.getStarName() != null && !params.getStarName().equals("")){
  63. boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
  64. }
  65. //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
  66. if(params.getMinPrice() != null && params.getMaxPrice() != null){
  67. boolQuery.filter(QueryBuilders
  68. .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
  69. }
  70. //【构建functionScoreQuery,实现算分查询。对应的是广告置顶功能】
  71. FunctionScoreQueryBuilder functionScoreQuery =
  72. //原始查询,需要进行相关性算分的查询
  73. QueryBuilders.functionScoreQuery(boolQuery,
  74. //function score的数组,里面有很多function score
  75. new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
  76. //一个具体的function score
  77. new FunctionScoreQueryBuilder.FilterFunctionBuilder(
  78. //过滤,简单说就是满足isAD字段为true的文档就会参与算分
  79. QueryBuilders.termQuery("isAD",true),
  80. //要使用什么算分函数,下面那行使用的是weightFactorFunction加权算分,最终score分数越大,排名就越前
  81. ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10
  82. )
  83. });
  84. //这一步必须有
  85. request.source().query(functionScoreQuery);
  86. //【分页功能】
  87. int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
  88. int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
  89. request.source().from((page - 1) * size).size(size);
  90. //【地理排序功能】
  91. String location = params.getLocation();
  92. //对前端传的location进行判断是否为空
  93. if (location != null && !location.equals("")){
  94. //sort排序,指定是geoDistanceSort地理坐标排序,要排序的字段是location,中心点是new GeoPoint(location)
  95. request.source().sort(SortBuilders
  96. .geoDistanceSort("location",new GeoPoint(location))
  97. .order(SortOrder.ASC) //升序排序
  98. .unit(DistanceUnit.KILOMETERS) //地理坐标的单位
  99. );
  100. }
  101. //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
  102. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  103. //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
  104. return handleResponse(response);
  105. } catch (IOException e) {
  106. throw new RuntimeException(e);
  107. }
  108. }
  109. //这个方法就是我们抽取出来的,负责解析的
  110. private PageResult handleResponse(SearchResponse response) {
  111. //解析获取到杂乱JSON数据
  112. SearchHits searchHits = response.getHits();
  113. //获取总条数
  114. long total = searchHits.getTotalHits().value;
  115. System.out.println("共搜索到"+total+"条文档(数据)");
  116. //获取hits数组
  117. SearchHit[] hits = searchHits.getHits();
  118. //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
  119. List<HotelDoc> hotels = new ArrayList<>();
  120. for (SearchHit hit : hits) {
  121. String json = hit.getSourceAsString();
  122. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  123. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  124. //【地理坐标查询的解析】,通过getSortValues方法来获取排序值,得到的是多个值也就是数组,我们只需要一个值
  125. Object[] sortValues = hit.getSortValues();
  126. //判断是否为空
  127. if (sortValues.length>0){
  128. Object sortValue = sortValues[0];
  129. //把拿到的sortValue返回到页面,也就是需要把sortValue值放到HoteDoc
  130. hotelDoc.setDistance(sortValue);
  131. }
  132. hotels.add(hotelDoc);
  133. }
  134. //封装返回
  135. return new PageResult(total,hotels);
  136. }
  137. }


第六步: 重启HotelDemoApplication引导类,浏览器访问http://localhost:8089/。查看我们指定的那两个酒店是否置顶


10. 高亮显示


高亮API包括请求DSL构建和结果解析两部分,API和对应的DSL语句如下图,下图只是构建,再下面还有解析,高亮必须由构建+解析才能实现


解析,如下图


第一步: 把HotelService类,修改为如下

  1. package cn.itcast.hotel.service.impl;
  2. import cn.itcast.hotel.mapper.HotelMapper;
  3. import cn.itcast.hotel.pojo.Hotel;
  4. import cn.itcast.hotel.pojo.HotelDoc;
  5. import cn.itcast.hotel.pojo.PageResult;
  6. import cn.itcast.hotel.pojo.RequestParams;
  7. import cn.itcast.hotel.service.IHotelService;
  8. import com.alibaba.fastjson.JSON;
  9. import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
  10. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  11. import org.elasticsearch.action.search.SearchRequest;
  12. import org.elasticsearch.action.search.SearchResponse;
  13. import org.elasticsearch.client.RequestOptions;
  14. import org.elasticsearch.client.RestHighLevelClient;
  15. import org.elasticsearch.common.geo.GeoPoint;
  16. import org.elasticsearch.common.unit.DistanceUnit;
  17. import org.elasticsearch.index.query.BoolQueryBuilder;
  18. import org.elasticsearch.index.query.QueryBuilders;
  19. import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
  20. import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
  21. import org.elasticsearch.search.SearchHit;
  22. import org.elasticsearch.search.SearchHits;
  23. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  24. import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
  25. import org.elasticsearch.search.sort.SortBuilders;
  26. import org.elasticsearch.search.sort.SortOrder;
  27. import org.springframework.beans.factory.annotation.Autowired;
  28. import org.springframework.stereotype.Service;
  29. import java.io.IOException;
  30. import java.util.ArrayList;
  31. import java.util.List;
  32. import java.util.Map;
  33. @Service
  34. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
  35. @Autowired
  36. //注入在引导类声明好的@Bean
  37. private RestHighLevelClient client;
  38. @Override
  39. public PageResult search(RequestParams params) {
  40. try {
  41. //准备Request对象,要查询哪个索引库,
  42. SearchRequest request = new SearchRequest("hotel");
  43. //【构建BooleanQuery,下面那行的boolQuery是原始查询】
  44. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  45. //【关键字搜索】
  46. //判断前端传的key是否为空,避免空指针
  47. String key = params.getKey();//前端传过来的搜索关键字
  48. //用的是must精确查找
  49. if (key == null || "".equals(key)) {
  50. //matchAllQuery方法表示查es的全部文档,不需要条件
  51. boolQuery.must(QueryBuilders.matchAllQuery());
  52. } else {
  53. //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
  54. boolQuery.must(QueryBuilders.matchQuery("all", key));
  55. }
  56. //【条件过滤】
  57. //城市,term精确查找,注意判空
  58. if(params.getCity() != null && !params.getCity().equals("")){
  59. boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
  60. }
  61. //品牌,term精确查找,注意判空
  62. if(params.getBrand() != null && !params.getBrand().equals("")){
  63. boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
  64. }
  65. //星级,term精确查找,注意判空
  66. if(params.getStarName() != null && !params.getStarName().equals("")){
  67. boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
  68. }
  69. //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
  70. if(params.getMinPrice() != null && params.getMaxPrice() != null){
  71. boolQuery.filter(QueryBuilders
  72. .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
  73. }
  74. //【构建functionScoreQuery,实现算分查询。对应的是广告置顶功能】
  75. FunctionScoreQueryBuilder functionScoreQuery =
  76. //原始查询,需要进行相关性算分的查询
  77. QueryBuilders.functionScoreQuery(boolQuery,
  78. //function score的数组,里面有很多function score
  79. new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
  80. //一个具体的function score
  81. new FunctionScoreQueryBuilder.FilterFunctionBuilder(
  82. //过滤,简单说就是满足isAD字段为true的文档就会参与算分
  83. QueryBuilders.termQuery("isAD",true),
  84. //要使用什么算分函数,下面那行使用的是weightFactorFunction加权算分,最终score分数越大,排名就越前
  85. ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10
  86. )
  87. });
  88. //【高亮显示】对查询出来的文档,的特定字段进行高亮显示
  89. request.source().highlighter(new HighlightBuilder().field("all").requireFieldMatch(true).preTags("<em>").postTags("</em>"));
  90. //这一步必须有
  91. request.source().query(functionScoreQuery);
  92. //【分页功能】
  93. int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
  94. int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
  95. request.source().from((page - 1) * size).size(size);
  96. //【地理排序功能】
  97. String location = params.getLocation();
  98. //对前端传的location进行判断是否为空
  99. if (location != null && !location.equals("")){
  100. //sort排序,指定是geoDistanceSort地理坐标排序,要排序的字段是location,中心点是new GeoPoint(location)
  101. request.source().sort(SortBuilders
  102. .geoDistanceSort("location",new GeoPoint(location))
  103. .order(SortOrder.ASC) //升序排序
  104. .unit(DistanceUnit.KILOMETERS) //地理坐标的单位
  105. );
  106. }
  107. //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
  108. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  109. //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
  110. return handleResponse(response);
  111. } catch (IOException e) {
  112. throw new RuntimeException(e);
  113. }
  114. }
  115. //这个方法就是我们抽取出来的,负责解析的
  116. private PageResult handleResponse(SearchResponse response) {
  117. //解析获取到杂乱JSON数据
  118. SearchHits searchHits = response.getHits();
  119. //获取总条数
  120. long total = searchHits.getTotalHits().value;
  121. System.out.println("共搜索到"+total+"条文档(数据)");
  122. //获取hits数组
  123. SearchHit[] hits = searchHits.getHits();
  124. //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
  125. List<HotelDoc> hotels = new ArrayList<>();
  126. for (SearchHit hit : hits) {
  127. String json = hit.getSourceAsString();
  128. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  129. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  130. //【地理坐标查询的解析】,通过getSortValues方法来获取排序值,得到的是多个值也就是数组,我们只需要一个值
  131. Object[] sortValues = hit.getSortValues();
  132. //判断是否为空
  133. if (sortValues.length>0){
  134. Object sortValue = sortValues[0];
  135. //把拿到的sortValue返回到页面,也就是需要把sortValue值放到HoteDoc
  136. hotelDoc.setDistance(sortValue);
  137. }
  138. //【解析】获取高亮结果
  139. Map<String, HighlightField> xxhighlightFields = hit.getHighlightFields();
  140. //使用CollectionUtils工具类,进行判空,避免空指针
  141. if (!CollectionUtils.isEmpty(xxhighlightFields)){
  142. //根据字段名获取高亮结果
  143. HighlightField xxhighlightField = xxhighlightFields.get("all");
  144. //判断name不为空
  145. if (xxhighlightField != null) {
  146. //获取高亮值
  147. String xxname = xxhighlightField.getFragments()[0].string();
  148. //覆盖非高亮结果
  149. hotelDoc.setName(xxname);
  150. }
  151. }
  152. hotels.add(hotelDoc);
  153. }
  154. //封装返回
  155. return new PageResult(total,hotels);
  156. }
  157. }

第二步: 重启HotelDemoApplication引导类,浏览器访问http://localhost:8089/。查看是否能将搜索词高亮显示


写好的项目: 文件下载-奶牛快传 Download |CowTransfer


实用篇-ES-数据聚合

官方文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html

1. 聚合的分类


聚合: 可以实现对文档数据的统计、分析、运算。聚合常见的有如下三类。注意,聚合的字段必然是不分词的,原因: 聚合不能是text类型
●桶 (Bucket) 聚合: 用来对文档做分组

  • ○Term     Aggregation聚合: 按照文档字段值分组。(我们下面会演示这个,按照品牌进行分桶)
  • ○Date    Histogram聚合: 按照日期阶梯分组,例如一周为一组,或者一月为一组

●度量 (Metric) 聚合: 用以计算一些值,比如最大值、最小值、平均值

  • ○avg: 求平均值
  • ○max: 求最大值
  • ○min: 求最小值
  • ○stats: 同时求max、min、avg、sum。(我们下面会演示这个,按照品牌进行求评分最值和平均值)

●管道 (Pipeline) 聚合: 其它聚合的结果为基础做聚合。这种用的不多


2. DSL实现Bucket聚合


Bucket聚合,也就是桶聚合
现在,我们要统计所有数据中的酒店品牌有多少种,此时我们可以根据酒店品牌的名称做聚合,由于品牌是字段,也就是要对字段值做分组,采用的是TermAggregation聚合,类型为term类型,DSL示例如下
确保你的环境正常启动
浏览器访问 http://你的ip地址:5601
然后我们使用的索引库是hotel,没有这个索引库的话,可以去前面 '实用篇-ES-黑马旅游案例' 的 '5. 环境准备-同步数据' 进行索引库的创建和添加数据
第一步: 具体操作,浏览器输入如下,表示对不同的品牌进行聚合,也就是不同的品牌为不同的桶,相同的品牌放进一个桶里面


第二步: 如何修改默认的排序规则,我们不希望是按照找出来的文档总条数降序排序。默认情况下,Bucket会统计Bucket内的文档数量,记为_count,并且按照count降序排序。我们如果要修改结果排序方式的话,只需要加一个order属性,如下


第三步: 我们上面是对整个索引库的数据做聚合搜索,如果索引库本身有庞大数据的话,对整个索引库的聚合搜索是对内存消耗非常大,我们希望自定义聚合的搜索范围,也就是限定要聚合的文档范围,只需要添加query条件即可,如下
gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于


3. DSL实现Metrics聚合


Metrics聚合,也就是度量聚合。例如,我们要求获取每个品牌的用户评分的min最小值、max最大值、avg平均值。注意不是整个索引库的所有酒店(文档)进行求值,所以要结合上一节的Bucket聚合一起使用
确保你的环境正常启动
浏览器访问 http://你的ip地址:5601
第一步: 具体操作,浏览器输入如下,表示对品牌(父聚合)的评分(子聚合)进行求值


第二步: 如果我们还需要对结果按照评分的平均值,再去做个排序,看一下哪个酒店评价最高,注意我们是在同里面做排序,也就是排序要写在terms里面


4. RestClient实现聚合


确保你的环境正常启动
如何在java代码使用RestClient来实现聚合。java代码以及对应的DSL语句如下图
请求,得到的是json数据


解析,对聚合结果的json数据进行解析


具体操作: 是基于之前的hotel-demo项目上继续编写,前面学的 '实用篇-ES-RestClient查询文档' 的基础上进行编写
第一步: 在HotelSearchTest类,添加如下

  1. @Test
  2. void testAggregation() throws IOException {
  3. //准备Request对象
  4. SearchRequest xxrequest = new SearchRequest("hotel");
  5. //准备DSL。设置size
  6. xxrequest.source().size(0);
  7. //准备DSL。聚合语句
  8. xxrequest.source().aggregation(AggregationBuilders
  9. .terms("BrandAggMyName") //自定义聚合名称为BrandAggMyName
  10. .field("brand")
  11. .size(10)
  12. );
  13. //发出请求
  14. SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
  15. //【解析】
  16. //解析聚合结果
  17. Aggregations xxaggregations = xxresponse.getAggregations();
  18. //根据聚合名称获取聚合结果
  19. Terms xxbrandTerms = xxaggregations.get("BrandAggMyName");
  20. //获取桶(buckets),获取的是一个集合
  21. List<? extends Terms.Bucket> xxbuckets = xxbrandTerms.getBuckets();
  22. //遍历集合,取出每一个bucket
  23. for (Terms.Bucket xxbucket : xxbuckets) {
  24. //获取key。这个key就是品牌信息
  25. String key = xxbucket.getKeyAsString();
  26. System.out.println(key);
  27. }
  28. }


第二步: 运行testAggregation方法


5. 多条件聚合


案例: 在前面的 '黑马路由案例' 中,搜索页面的品牌、城市等信息不应该是在页面写死,而是通过聚合索引库中的酒店数据得来的


确保你的环境正常启动
第一步: 在IHotelService接口添加如下

  1. /**
  2. * 查询城市、星级、品牌的聚合结果
  3. * @return 聚合结果,格式:{"城市": ["上海", "北京"], "品牌": ["如家", "希尔顿"]}
  4. */
  5. //在给出来的案例图中,左侧加深的字是key,右侧浅灰的字是value,并且右侧的value有多个值。所以我们使用Map集合,并且value是list集合
  6. Map<String, List<String>> xxfilters();


第二步: 在HotelService实现类添加如下

  1. @Override
  2. public Map<String, List<String>> xxfilters() {
  3. try {
  4. //准备Request对象
  5. SearchRequest xxrequest = new SearchRequest("hotel");
  6. //准备DSL。设置size
  7. xxrequest.source().size(0);
  8. //准备DSL。聚合语句。对多个字段进行聚合
  9. buildAggregation(xxrequest);
  10. //发出请求
  11. SearchResponse xxresponse = client.search(xxrequest, RequestOptions.DEFAULT);
  12. //【解析】
  13. //解析聚合结果
  14. Map<String, List<String>> yyresult = new HashMap<>();
  15. Aggregations xxaggregations = xxresponse.getAggregations();
  16. //1、根据名称获取品牌的结果
  17. List<String> xxbrandList = getAggByName(xxaggregations,"BrandAggMyName");
  18. //把品牌的结果信息放入map
  19. yyresult.put("品牌",xxbrandList);
  20. //2、根据名称获取城市的结果
  21. List<String> xxcityList = getAggByName(xxaggregations,"cityAggMyName");
  22. //把城市的结果信息放入map
  23. yyresult.put("城市",xxcityList);
  24. //3、根据名称获取星级的结果
  25. List<String> xxstarList = getAggByName(xxaggregations,"starAggMyName");
  26. //把星级的结果信息放入map
  27. yyresult.put("星级",xxstarList);
  28. //返回yyresult
  29. return yyresult;
  30. } catch (IOException e) {
  31. throw new RuntimeException(e);
  32. }
  33. }
  34. private List<String> getAggByName(Aggregations xxaggregations,String kkaggName) {
  35. //根据聚合名称获取聚合结果
  36. Terms xxbrandTerms = xxaggregations.get(kkaggName);
  37. //获取桶(buckets),获取的是一个集合
  38. List<? extends Terms.Bucket> xxbuckets = xxbrandTerms.getBuckets();
  39. //遍历xxbuckets集合,取出每一个key。把取到的key放到xxbrandList集合
  40. List<String> xxbrandList = new ArrayList<>();
  41. for (Terms.Bucket xxbucket : xxbuckets) {
  42. //获取key。这个key就是品牌信息
  43. String xxkey = xxbucket.getKeyAsString();
  44. xxbrandList.add(xxkey);
  45. }
  46. return xxbrandList;
  47. }
  48. //把聚合的代码抽取出来
  49. private void buildAggregation(SearchRequest xxrequest) {
  50. xxrequest.source().aggregation(AggregationBuilders
  51. .terms("BrandAggMyName") //自定义聚合名称为BrandAggMyName
  52. .field("brand")
  53. .size(100)//聚合结果限制
  54. );
  55. xxrequest.source().aggregation(AggregationBuilders
  56. .terms("cityAggMyName")
  57. .field("city")
  58. .size(100)
  59. );
  60. xxrequest.source().aggregation(AggregationBuilders
  61. .terms("starAggMyName")
  62. .field("starName")
  63. .size(100)
  64. );
  65. }


抽取代码成为方法


第三步: 在HotelDemoApplicationTests添加如下。并运行contextLoads方法

  1. @Autowired
  2. private IHotelService hotelService;
  3. @Test
  4. void contextLoads() {
  5. Map<String, List<String>> filters = hotelService.xxfilters();
  6. System.out.println(filters);
  7. }


6. hm-带过滤条件的聚合


对接前端接口,也就是把上面 '5. 多条件聚合' 实现的功能返回到前端页面,达到最终效果
前端页面会向服务端发起请求,查询品牌、城市、星级字段的聚合结果
确保你的环境正常启动
首先,保证你已经学完前面的 '实用篇-ES-黑马旅游案例' 并且在浏览器能打开前端页面


点击页面的搜索,打开浏览器控制台看一下前端向后端请求的参数


分析:
1、可以看到请求参数与之前search时的RequestParam完全一致,这是在限定聚合时的文档范围。用户搜索“外滩”,价格在300~600,那聚合必须是在这个搜索条件基础上完成
2、编写controller接口,接收该请求
3、修改IUserService#getFilters()方法,添加RequestParam参数
3、修改getFilters方法的业务,聚合时添加query条件
具体操作如下
第一步: 把HotelDemoApplicationTests类注释掉
第二步: 把IHotelService接口修改为如下,原本xxfilters方法是没有参数的,现在我们要给xxfilters方法添加参数

  1. package cn.itcast.hotel.service;
  2. import cn.itcast.hotel.pojo.Hotel;
  3. import cn.itcast.hotel.pojo.PageResult;
  4. import cn.itcast.hotel.pojo.RequestParams;
  5. import com.baomidou.mybatisplus.extension.service.IService;
  6. import java.util.List;
  7. import java.util.Map;
  8. public interface IHotelService extends IService<Hotel> {
  9. PageResult search(RequestParams params);
  10. /**
  11. * 查询城市、星级、品牌的聚合结果
  12. * @return 聚合结果,格式:{"城市": ["上海", "北京"], "品牌": ["如家", "希尔顿"]}
  13. */
  14. //在给出来的案例图中,左侧加深的字是key,右侧浅灰的字是value,并且右侧的value有多个值。所以我们使用Map集合,并且value是list集合
  15. Map<String, List<String>> xxfilters(RequestParams params);
  16. }


第三步: 把HotelService实现类修改为如下

  1. package cn.itcast.hotel.service.impl;
  2. import cn.itcast.hotel.mapper.HotelMapper;
  3. import cn.itcast.hotel.pojo.Hotel;
  4. import cn.itcast.hotel.pojo.HotelDoc;
  5. import cn.itcast.hotel.pojo.PageResult;
  6. import cn.itcast.hotel.pojo.RequestParams;
  7. import cn.itcast.hotel.service.IHotelService;
  8. import com.alibaba.fastjson.JSON;
  9. import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
  10. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  11. import org.apache.http.HttpHost;
  12. import org.elasticsearch.action.search.SearchRequest;
  13. import org.elasticsearch.action.search.SearchResponse;
  14. import org.elasticsearch.client.RequestOptions;
  15. import org.elasticsearch.client.RestClient;
  16. import org.elasticsearch.client.RestHighLevelClient;
  17. import org.elasticsearch.common.geo.GeoPoint;
  18. import org.elasticsearch.common.unit.DistanceUnit;
  19. import org.elasticsearch.index.query.BoolQueryBuilder;
  20. import org.elasticsearch.index.query.QueryBuilders;
  21. import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
  22. import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
  23. import org.elasticsearch.search.SearchHit;
  24. import org.elasticsearch.search.SearchHits;
  25. import org.elasticsearch.search.aggregations.AggregationBuilders;
  26. import org.elasticsearch.search.aggregations.Aggregations;
  27. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  28. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  29. import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
  30. import org.elasticsearch.search.sort.SortBuilders;
  31. import org.elasticsearch.search.sort.SortOrder;
  32. import org.springframework.beans.factory.annotation.Autowired;
  33. import org.springframework.stereotype.Service;
  34. import java.io.IOException;
  35. import java.util.ArrayList;
  36. import java.util.HashMap;
  37. import java.util.List;
  38. import java.util.Map;
  39. @Service
  40. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
  41. @Autowired
  42. //注入在引导类声明好的@Bean
  43. private RestHighLevelClient client;
  44. @Override
  45. public PageResult search(RequestParams params) {
  46. try {
  47. //准备Request对象,要查询哪个索引库,
  48. SearchRequest request = new SearchRequest("hotel");
  49. buildBasicQuery(params, request);
  50. //【分页功能】
  51. int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
  52. int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
  53. request.source().from((page - 1) * size).size(size);
  54. //【地理排序功能】
  55. String location = params.getLocation();
  56. //对前端传的location进行判断是否为空
  57. if (location != null && !location.equals("")){
  58. //sort排序,指定是geoDistanceSort地理坐标排序,要排序的字段是location,中心点是new GeoPoint(location)
  59. request.source().sort(SortBuilders
  60. .geoDistanceSort("location",new GeoPoint(location))
  61. .order(SortOrder.ASC) //升序排序
  62. .unit(DistanceUnit.KILOMETERS) //地理坐标的单位
  63. );
  64. }
  65. //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
  66. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  67. //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
  68. return handleResponse(response);
  69. } catch (IOException e) {
  70. throw new RuntimeException(e);
  71. }
  72. }
  73. private SearchRequest buildBasicQuery(RequestParams params,SearchRequest request) {
  74. //【构建BooleanQuery,下面那行的boolQuery是原始查询】
  75. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  76. //【关键字搜索】
  77. //判断前端传的key是否为空,避免空指针
  78. String key = params.getKey();//前端传过来的搜索关键字
  79. //用的是must精确查找
  80. if (key == null || "".equals(key)) {
  81. //matchAllQuery方法表示查es的全部文档,不需要条件
  82. boolQuery.must(QueryBuilders.matchAllQuery());
  83. } else {
  84. //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
  85. boolQuery.must(QueryBuilders.matchQuery("all", key));
  86. }
  87. //【条件过滤】
  88. //城市,term精确查找,注意判空
  89. if(params.getCity() != null && !params.getCity().equals("")){
  90. boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
  91. }
  92. //品牌,term精确查找,注意判空
  93. if(params.getBrand() != null && !params.getBrand().equals("")){
  94. boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
  95. }
  96. //星级,term精确查找,注意判空
  97. if(params.getStarName() != null && !params.getStarName().equals("")){
  98. boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
  99. }
  100. //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
  101. if(params.getMinPrice() != null && params.getMaxPrice() != null){
  102. boolQuery.filter(QueryBuilders
  103. .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
  104. }
  105. //【构建functionScoreQuery,实现算分查询。对应的是广告置顶功能】
  106. FunctionScoreQueryBuilder functionScoreQuery =
  107. //原始查询,需要进行相关性算分的查询
  108. QueryBuilders.functionScoreQuery(boolQuery,
  109. //function score的数组,里面有很多function score
  110. new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
  111. //一个具体的function score
  112. new FunctionScoreQueryBuilder.FilterFunctionBuilder(
  113. //过滤,简单说就是满足isAD字段为true的文档就会参与算分
  114. QueryBuilders.termQuery("isAD",true),
  115. //要使用什么算分函数,下面那行使用的是weightFactorFunction加权算分,最终score分数越大,排名就越前
  116. ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10
  117. )
  118. });
  119. //【高亮显示】对查询出来的文档,的特定字段进行高亮显示
  120. request.source().highlighter(new HighlightBuilder().field("all").requireFieldMatch(true).preTags("<em>").postTags("</em>"));
  121. //这一步必须有
  122. request.source().query(functionScoreQuery);
  123. return request;
  124. }
  125. //这个方法就是我们抽取出来的,负责解析的
  126. private PageResult handleResponse(SearchResponse response) {
  127. //解析获取到杂乱JSON数据
  128. SearchHits searchHits = response.getHits();
  129. //获取总条数
  130. long total = searchHits.getTotalHits().value;
  131. System.out.println("共搜索到"+total+"条文档(数据)");
  132. //获取hits数组
  133. SearchHit[] hits = searchHits.getHits();
  134. //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
  135. List<HotelDoc> hotels = new ArrayList<>();
  136. for (SearchHit hit : hits) {
  137. String json = hit.getSourceAsString();
  138. //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
  139. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  140. //【地理坐标查询的解析】,通过getSortValues方法来获取排序值,得到的是多个值也就是数组,我们只需要一个值
  141. Object[] sortValues = hit.getSortValues();
  142. //判断是否为空
  143. if (sortValues.length>0){
  144. Object sortValue = sortValues[0];
  145. //把拿到的sortValue返回到页面,也就是需要把sortValue值放到HoteDoc
  146. hotelDoc.setDistance(sortValue);
  147. }
  148. //【解析】获取高亮结果
  149. Map<String, HighlightField> xxhighlightFields = hit.getHighlightFields();
  150. //使用CollectionUtils工具类,进行判空,避免空指针
  151. if (!CollectionUtils.isEmpty(xxhighlightFields)){
  152. //根据字段名获取高亮结果
  153. HighlightField xxhighlightField = xxhighlightFields.get("all");
  154. //判断name不为空
  155. if (xxhighlightField != null) {
  156. //获取高亮值
  157. String xxname = xxhighlightField.getFragments()[0].string();
  158. //覆盖非高亮结果
  159. hotelDoc.setName(xxname);
  160. }
  161. }
  162. hotels.add(hotelDoc);
  163. }
  164. //封装返回
  165. return new PageResult(total,hotels);
  166. }
  167. //--------------------------------------------下面是多条件聚合-----------------------------------------
  168. @Override
  169. public Map<String, List<String>> xxfilters(RequestParams params) {
  170. try {
  171. //准备Request对象
  172. SearchRequest xxrequest = new SearchRequest("hotel");
  173. //添加query查询信息,也就是限定聚合的范围
  174. buildBasicQuery(params, xxrequest);
  175. //准备DSL。设置size
  176. xxrequest.source().size(0);
  177. //准备DSL。聚合语句。对多个字段进行聚合
  178. buildAggregation(xxrequest);
  179. //发出请求
  180. SearchResponse xxresponse = client.search(xxrequest, RequestOptions.DEFAULT);
  181. //【解析】
  182. //解析聚合结果
  183. Map<String, List<String>> yyresult = new HashMap<>();
  184. Aggregations xxaggregations = xxresponse.getAggregations();
  185. //1、根据名称获取品牌的结果
  186. List<String> xxbrandList = getAggByName(xxaggregations,"BrandAggMyName");
  187. //把品牌的结果信息放入map
  188. yyresult.put("brand",xxbrandList);
  189. //2、根据名称获取城市的结果
  190. List<String> xxcityList = getAggByName(xxaggregations,"cityAggMyName");
  191. //把城市的结果信息放入map
  192. yyresult.put("city",xxcityList);
  193. //3、根据名称获取星级的结果
  194. List<String> xxstarList = getAggByName(xxaggregations,"starAggMyName");
  195. //把星级的结果信息放入map
  196. yyresult.put("starName",xxstarList);
  197. //返回yyresult
  198. return yyresult;
  199. } catch (IOException e) {
  200. throw new RuntimeException(e);
  201. }
  202. }
  203. private List<String> getAggByName(Aggregations xxaggregations,String kkaggName) {
  204. //根据聚合名称获取聚合结果
  205. Terms xxbrandTerms = xxaggregations.get(kkaggName);
  206. //获取桶(buckets),获取的是一个集合
  207. List<? extends Terms.Bucket> xxbuckets = xxbrandTerms.getBuckets();
  208. //遍历xxbuckets集合,取出每一个key。把取到的key放到xxbrandList集合
  209. List<String> xxbrandList = new ArrayList<>();
  210. for (Terms.Bucket xxbucket : xxbuckets) {
  211. //获取key。这个key就是品牌信息
  212. String xxkey = xxbucket.getKeyAsString();
  213. xxbrandList.add(xxkey);
  214. }
  215. return xxbrandList;
  216. }
  217. //把聚合的代码抽取出来
  218. private void buildAggregation(SearchRequest xxrequest) {
  219. xxrequest.source().aggregation(AggregationBuilders
  220. .terms("BrandAggMyName") //自定义聚合名称为BrandAggMyName
  221. .field("brand")
  222. .size(100)//聚合结果限制
  223. );
  224. xxrequest.source().aggregation(AggregationBuilders
  225. .terms("cityAggMyName")
  226. .field("city")
  227. .size(100)
  228. );
  229. xxrequest.source().aggregation(AggregationBuilders
  230. .terms("starAggMyName")
  231. .field("starName")
  232. .size(100)
  233. );
  234. }
  235. //--------------------------------------------上面是多条件聚合-----------------------------------------
  236. }


第四步: 重新运行HotelDemoApplication引导类,浏览器查看是否功能正常


实用篇-ES-自动补全


1. 安装拼音分词器


elasticsearch的拼音分词插件的官方地址: https://www.wpsshop.cn/w/Monodyee/article/detail/455709

推荐阅读
相关标签
  

闽ICP备14008679号