赞
踩
目录
ES是elasticsearch的简称。我在SpringBoot学习 '数据层解决方案' 的时候,写过一次ES笔记,可以结合一起看一下。
之前在SpringBoot里面写的相关ES笔记是基于Windows的,现在我们是基于docker容器来使用,需要你们提前准备好自己的docker容器以及掌握docker操作
常见的分布式搜索的技术,如下
随着业务发展,数据量越来越庞大,传统的MySQL数据库难以满足我们的需求,所以在微服务架构下,一般都会用到一种分布式搜索的技术,下面我们会学分布式搜索中最流行的一种,也就是elasticsearch的用法。包括学习elasticsearch的概念、安装、使用。其中学习elasticsearch的使用的时候,主要通过两个方面,一方面是elasticsearch对于索引库(类似于数据库,把数据导入进索引库,导入的数据就是所谓的文档,我们要实现文档的增删改查)的操作,另一方面我们还会学习elasticsearch官方提供的Restful的API(也就是Java客户端),来更方便的操作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的核心技术是倒排索引,下面会学
传统数据库(例如MySQL)采用正向索引,例如给下表(tb_goods)中的id创建索引
elasticsearch采用倒排索引,例如给下表(tb_goods)中的id创建索引
总结
1、正向索引: 基于文档id来创建索引。查询词条时必须先找到文档,而后判断是否包含词条
2、倒排索引: 对文档内容进行分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条去查询文档id,然后获取到文档
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,答案并不是,两者擅长的事情不一样,如下
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容器互联
- systemctl start docker # 启动docker服务
- 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容器
- docker run -d \
- --name es \
- -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
- -e "discovery.type=single-node" \
- -v es-data:/usr/share/elasticsearch/data \
- -v es-plugins:/usr/share/elasticsearch/plugins \
- --privileged \
- --network es-net \
- -p 9200:9200 \
- -p 9300:9300 \
- 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/
注意,是跟上一节的 '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容器
- docker run -d \
- --name kibana \
- -e ELASTICSEARCH_HOSTS=http://es:9200 \
- --network=es-net \
- -p 5601:5601 \
- 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语句的时候,是带有提示功能的,非常好用
IK分词器官网: https://github.com/medcl/elasticsearch-analysis-ik。注意elasticsearch、kibana、IK分词器,这三者通常是一起使用的
es在创建倒排索引时,需要对文档进行分词。在搜索时,需要对用户输入的内容进行分词。但默认的分词规则不支持中文处理,默认是只支持对英文进行分词,但是在正常业务中,我们需要处理的文档大多是中文,所以我们需要对中文进行分词,所以就需要安装IK分词器
为了直观的体现,es的分词规则不支持英文,我们可以做下面的小演示如下
- #测试分词器
- POST /_analyze
- {
- "text": "我正在学习安装IK分词器",
- "analyzer": "english"
- }
上图,就算分词器名称改成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
- # 重启elasticsearch容器
- docker restart es
第四步: 查看elasticsearch容器的启动日志
docker logs -f es
第五步: 确保elasticsearch、kibana已正常运行
- docker restart es #启动elasticsearch容器
- docker restart kibana #启动kibana容器
第五步: 测试。在浏览器中输入:http://你的ip地址:5601 即可看到elasticsearch的响应结果
IK分词器包含两种模式:
Ik分词器的分词,底层是一个字典,在字典里面会有各种各样的词语,当ik分词器需要对分词文本进行分词时,ik分词器就会拿着这个文本(乱拆成多个词或词语),一个个去字典里面匹配,如果能匹配到,证明某个词(乱拆成多个词或词语)是词,就把这个证明后的词分出来,作为一个词
第一个问题: 字典的分词效果是有限的,只能对日常生活中常见的语义相关的词,进行分词,由于字典的词汇量少,所以我们需要对字典进行扩展。
第二个问题: 字典的分词效果往往存在违禁词,我们不希望IK分词器能匹配并成功把词典里的违禁词作为分词,解决: 禁用某些敏感词条
解决:
1、要拓展或禁用ik分词器的词库,只需要修改一个分词器目录中的config目录中的IKAnalyzer.cfg.xml文件,如下
- cd /var/lib/docker/volumes/es-plugins/_data/ik/config
- vi IKAnalyzer.cfg.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
- <properties>
- <comment>IK Analyzer 扩展配置</comment>
- <!--用户可以在这里配置自己的扩展字典-->
- <entry key="ext_dict">ext.dic</entry>
- <!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典-->
- <entry key="ext_stopwords">stopword.dic</entry>
- </properties>
2、在config目录新建myext.dic文件,写入自己想要的特定词,也就是扩展词。新建mystopword.dic文件,写入自己想要禁用的特定词,也就是不参与分词的词
cd /var/lib/docker/volumes/es-plugins/_data/ik/config
- touch myext.dic
- vi myext.dic
- touch mystopword.dic
- vi mystopword.dic
3、重新启动elasticsearch、kibana
- docker restart es #启动elasticsearch容器
- docker restart kibana #启动kibana容器
4、测试。在浏览器中输入:http://你的ip地址:5601 即可看到elasticsearch的响应结果
http://192.168.200.231:5601
IK分词器包含两种模式:
根据上图,确实可以根据我们指定的扩展词进行分析,违禁词也确实被禁用没有被分词
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: 子字段,也就是属性和子属性
ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下
- PUT /索引库名称
- {
- "mappings": {//映射
- "properties": {//字段
- "字段名":{
- "type": "text",
- "analyzer": "ik_smart"
- },
- "字段名2":{
- "type": "keyword",
- "index": false //false表示这个字段不参与搜索,该字段不会创建为倒排索引,false不加双引号
- },
- "字段名3":{
- "properties": {//这个就是子字段
- "子字段": {
- "type": "keyword"
- }
- }
- },
- // ...略
- }
- }
- }
具体操作: 首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #启动kibana容器
第一步: 浏览器访问 http://你的ip地址:5601 。输入如下,注意把注释删掉
http://192.168.200.231:5601
- # 创建索引库,名字自定义,例如huanfqc
- PUT /huanfqc
- {
- "mappings": {
-
- "properties": {
- "xxinfo": {
- "type": "text", //文本类型,可以被分词器分词
- "analyzer": "ik_smart" //必须指定分词器
- },
- "xxemail": {
- "type": "keyword", //精确值类型,不可被分词器分词,本身就是最简的
- "index": false //不参与搜索,用户不能通过搜索搜到xxemail字段
- },
- "name": {
- "type": "object", //对象类型
- "properties": { //父字段
- "firstName": { //子字段
- "type": "keyword", //精确值类型,不可被分词器分词,本身就是最简的
- "index": true //参与搜索,用户通过可搜索到firstName字段
- },
- "lastName": { //子字段
- "type": "keyword", //精确值类型,不可被分词器分词,本身就是最简的
- "index": true //参与搜索,用户通过可搜索到lastName字段
- }
- }
- }
- }
- }
- }
- # 创建索引库,名字自定义,例如huanfqc
- PUT /huanfqc
- {
- "mappings": {
-
- "properties": {
- "xxinfo": {
- "type": "text",
- "analyzer": "ik_smart"
- },
- "xxemail": {
- "type": "keyword",
- "index": false
- },
- "name": {
- "type": "object",
- "properties": {
- "firstName": {
- "type": "keyword",
- "index": true
- },
- "lastName": {
- "type": "keyword",
- "index": true
- }
- }
- }
- }
- }
- }
具体操作: 首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #启动kibana容器
1、查询索引库语法
GET /索引库名
2、往索引库添加新字段,注意: 索引库是无法被修改的,但是可以添加新字段(不能和已有的重复,否则报错)
- PUT /索引库名/_mapping
- {
- "properties": {
- "新字段名":{
- "type": "integer"
- }
- }
- }
-
- //例如如下
- PUT /huanfqc/_mapping
- {
- "properties": {
- "age": {
- "type": "integer"
- }
- }
- }
3、删除索引库语法
DELETE /索引库名
具体操作: 首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作。并且已经创建了名为huanfqc的索引库
1、新增文档的DSL语法,其实就是告诉kibana,我们要把文档添加到es的哪个索引库,如果省略文档id的话,es会默认随机生成一个,建议自己指定文档id
- POST /索引库名/_doc/文档id
- {
- "字段1": "值1",
- "字段2": "值2",
- "字段3": {
- "子属性1": "值3",
- "子属性2": "值4"
- },
- // ...
- }
- #创建文档
- POST /huanfqc/_doc/1
- {
- "xxinfo":"焕发@青春-学Java",
- "email": "123@huanfqc.cn",
- "name":{
- "firstName":"张",
- "lastName":"三"
- }
- }
2、查询文档。语法: GET /索引库名/_doc/文档id 。例如如下
- #查询文档
- GET /huanfqc/_doc/1
3、删除文档。语法: DELETE/索引库名/_doc/文档id 。例如如下
- #删除文档
- DELETE /huanfqc/_doc/1
具体操作: 首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作。并且已经创建了名为huanfqc的索引库、文档id为1的文档
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #启动kibana容器
方式一: 全量修改,会删除旧文档,添加新文档。修改文档的DSL语法,如下
注意: 这种操作是直接用新值覆盖掉旧的,如果只put一个字段那么其它字段就没了,所以,你不想修改的字段也要原样写出来,不然就没了
注意: 如果你写的文档id或字段不存在的话,本来是修改操作,结果就变成新增操作
- #修改文档
- PUT /索引库名/_doc/文档id
- {
- "字段1": "值1",
- "字段2": "值2",
- // ... 略
- }
- #修改文档
- PUT /huanfqc/_doc/1
- {
- "xxinfo":"修改你了-焕发@青春-学Java",
- "email": "123@huanfqc.cn",
- "name":{
- "firstName":"修改你了-张",
- "lastName":"三",
- "xxupdate":"我还加了一个"
- }
- }
方式二: 增量修改。修改指定字段的值
注意: 如果你写的文档id或字段不存在的话,本来是修改操作,结果就变成新增操作
- #修改文档
- POST /索引库名/_update/文档id
- {
- "doc": {
- "要修改的字段名": "新的值",
- }
- }
- #修改文档
- POST /huanfqc/_update/1
- {
- "doc": {
- "firstName": "修改-法外狂徒张三"
- }
- }
下面的全部内容都是连续的,请不要跳过某一小节
对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文件导入进你的数据库
- create database if not exists elasticsearch;
- use elasticsearch;
第二步: 把下载好的hotel-demo.zip压缩包解压,得到hotel-demo文件夹,在idea打开hotel-demo
第三步: 修改application.yml文件,配置正确的数据库信息
在es中,mapping要考虑的问题: 字段名、数据类型、是否参与搜索、是否分词、如果分词那么分词器是什么。
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- #docker restart kibana #启动kibana容器
我们刚刚在mysql导入了tb_hotel.sql,里面有很多数据,我们需要基于这些数据结构,去分析并尝试编写对应的es的mapping映射
先看mysql中的数据类型(已有),如下
- CREATE TABLE `tb_hotel` (
- `id` bigint(20) NOT NULL COMMENT '酒店id',
- `name` varchar(255) NOT NULL COMMENT '酒店名称;例:7天酒店',
- `address` varchar(255) NOT NULL COMMENT '酒店地址;例:航头路',
- `price` int(10) NOT NULL COMMENT '酒店价格;例:329',
- `score` int(2) NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
- `brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
- `city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
- `star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1钻到5钻',
- `business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹桥',
- `latitude` varchar(32) NOT NULL COMMENT '纬度;例:31.2497',
- `longitude` varchar(32) NOT NULL COMMENT '经度;例:120.3925',
- `pic` varchar(255) DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
根据mysql的数据类型等信息,编写es(没有,自己对着上面的sql写的)。注意经纬度在es里面是geo_point类型,且经纬度是写在一起的
- # 酒店的mapping
- PUT /hotel
- {
- "mappings": {
- "properties": {
- "id":{
- "type": "keyword",
- "index": true
- },
- "name":{
- "type": "text",
- "analyzer": "ik_max_word"
- },
- "address":{
- "type": "keyword",
- "index": false
- },
- "price":{
- "type": "float",
- "index": true
- },
- "score":{
- "type": "float",
- "index": true
- },
- "brand":{
- "type": "keyword",
- "index": true
- },
- "city":{
- "type": "keyword",
- "index": true
- },
- "business":{
- "type": "keyword",
- "index": true
- },
- "xxlocation":{
- "type": "geo_point",
- "index": true
- },
- "pic":{
- "type": "keyword",
- "index": false
- }
- }
- }
- }
操作主要是在idea的hotel-demo项目进行,hotel-demo项目(不是springcloud项目,只是springboot项目)是前面 '1. RestClient案例准备',跳过的可回去补
第一步: 在hotel-demo项目的pom.xml添加如下
- <elasticsearch.version>7.12.1</elasticsearch.version>
-
- <!--引入es的RestHighLevelClient,版本要跟你Centos7里面部署的es版本一致-->
- <dependency>
- <groupId>org.elasticsearch.client</groupId>
- <artifactId>elasticsearch-rest-high-level-client</artifactId>
- <version>7.12.1</version>
- </dependency>
第二步: 在hotel-demo项目的src/test/java/cn.itcast.hotel目录新建HotelIndexTest类,写入如下
- package cn.itcast.hotel;
-
- import org.apache.http.HttpHost;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
-
- import java.io.IOException;
-
- public class HotelIndexTest {
-
- private RestHighLevelClient xxclient;
-
- @BeforeEach
- //该注解表示一开始就完成RestHighLevelClient对象的初始化
- void setUp() {
- this.xxclient = new RestHighLevelClient(RestClient.builder(
- //指定你Centos7部署的es的主机地址
- HttpHost.create("http://192.168.200.231:9200")
- ));
- }
-
- @AfterEach
- //该注解表示销毁,当对象运行完之后,就销毁这个对象
- void tearDown() throws IOException {
- this.xxclient.close();
- }
-
- @Test
- //现在才是测试代码,对象已经在上面初始化并且有销毁的步骤了,下面直接打印
- void yytestInit() {
- System.out.println(xxclient);
- }
- }
第三步: 确保下面的服务你都在Centos7里面启动了
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
第四步: 运行HotelIndexTest类yytestInit方法
不是通过kibana的浏览器控制台,通过DSL语句来进行操作es,在es里面创建索引库
而是通过上一节初始化的RestClient对象,在Java里面去操作es,创建es的索引库。根本不需要kibana做中间者
第一步: 在src/main/java/cn.itcast.hotel目录新建constants.HotelConstants类,里面写DSL语句,如下
其中长长的字符串就是我们在前面 '2. hotel数据结构分析' 里面写的。忘了怎么写出来的,可以回去看看
- package cn.itcast.hotel.constants;
-
-
- public class HotelConstants {
-
- public static final String xxMappingTemplate = "{\n" +
- " \"mappings\": {\n" +
- " \"properties\": {\n" +
- " \"id\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"name\":{\n" +
- " \"type\": \"text\",\n" +
- " \"analyzer\": \"ik_max_word\",\n" +
- " \"copy_to\": \"all\"\n" +
- " },\n" +
- " \"address\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": false\n" +
- " },\n" +
- " \"price\":{\n" +
- " \"type\": \"float\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"score\":{\n" +
- " \"type\": \"float\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"brand\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true,\n" +
- " \"copy_to\": \"all\"\n" +
- " },\n" +
- " \"city\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"business\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true,\n" +
- " \"copy_to\": \"all\"\n" +
- " },\n" +
- " \"location\":{\n" +
- " \"type\": \"geo_point\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"pic\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": false\n" +
- " },\n" +
- " \"all\":{\n" +
- " \"type\": \"text\",\n" +
- " \"analyzer\": \"ik_max_word\"\n" +
- " }\n" +
- " }\n" +
- " }\n" +
- "}";
- }
第二步: 在hotel-demo项目的HotelIndexTest类,添加如下
- //使用xxclient对象,向es创建索引库
- @Test
- void xxcreateHotelIndex() throws IOException {
- //创建Request对象,自定义索引库名称为gghotel
- CreateIndexRequest request = new CreateIndexRequest("gghotel");
- //准备请求的参数: DSL语句
- request.source(xxMappingTemplate, XContentType.JSON);//注意xxMappingTemplate是第一步定义的的静态常量,导包别导错了
- //发送请求
- xxclient.indices().create(request, RequestOptions.DEFAULT);
- }
第三步: 确保下面的服务你都在Centos7里面启动了
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
第四步: 验证。运行HotelIndexTest类的xxcreateHotelIndex测试方法
第五步: 如何更直观地验证,es里面确实有刚刚创建的索引库(刚刚创建的索引库是叫gghotel)
那就不得不运行kibana了,这样才能打开web浏览器页面,进行查询
docker restart kibana #启动kibana容器
浏览器访问 http://你的ip地址:5601
首先保证你已经做好了 '实用篇-ES-环境搭建' ,然后开始下面的操作。不需要浏览器操作es,所以不需要启动kibana容器
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- #docker restart kibana #启动kibana容器
1、删除索引库。在hotel-demo项目的HotelIndexTest类,添加如下。然后运行xxtestDeleteHotelIndex方法
- //删除索引库
- @Test
- void xxtestDeleteHotelIndex() throws IOException {
- //创建Request对象,指定要删除哪个索引库
- DeleteIndexRequest request = new DeleteIndexRequest("gghotel");
- //发送请求
- xxclient.indices().delete(request, RequestOptions.DEFAULT);
- }
2、判断索引库是否存在。在hotel-demo项目的HotelIndexTest类,添加如下。然后运行xxtestDeleteHotelIndex方法
- //判断索引库是否存在
- @Test
- void xxtestExistsHotelIndex() throws IOException {
- //创建Request对象,判断哪个索引库是否存在在es
- GetIndexRequest request = new GetIndexRequest("gghotel");
- //发送请求
- boolean ffexists = xxclient.indices().exists(request, RequestOptions.DEFAULT);
- //输出一下,看是否存在
- System.out.println(ffexists ? "索引库已经存在" : "索引库不存在");
- }
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- #docker restart kibana #启动kibana容器
案例: 去数据库查询酒店数据,把查询到的结果导入到hotel索引库(上一节我们已经创建一个名为gghotel的索引库),实现酒店数据的增删改查
简单说就是先去数据查酒店数据,把结果转换成索引库所需要的格式(新增文档的DSL语法)然后写到索引库,然后在索引库对这些酒店数据进行增删改查
【必备操作】
你们拿到代码的时候,这些操作已经做好,不需要再去做,我只是写出来方便后续复习
(1)、在pojo目录里面有一个Hotel类,作用是指定根数据库交互的字段,写入了如下
- package cn.itcast.hotel.pojo;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- @Data
- @TableName("tb_hotel")
- public class Hotel {
- @TableId(type = IdType.INPUT)
- private Long id;
- private String name;
- private String address;
- private Integer price;
- private Integer score;
- private String brand;
- private String city;
- private String starName;
- private String business;
- private String longitude;
- private String latitude;
- private String pic;
- }
(2)、在pojo目录里面有一个HotelDoc类,作用是跟es的索引库交互的字段,也就是跟我们索引库里面的字段类型联调,写入了如下
- package cn.itcast.hotel.pojo;
-
- import lombok.Data;
- import lombok.NoArgsConstructor;
-
- @Data
- @NoArgsConstructor
- public class HotelDoc {
- private Long id;
- private String name;
- private String address;
- private Integer price;
- private Integer score;
- private String brand;
- private String city;
- private String starName;
- private String business;
- private String xxlocation;
- private String pic;
-
- public HotelDoc(Hotel hotel) {
- this.id = hotel.getId();
- this.name = hotel.getName();
- this.address = hotel.getAddress();
- this.price = hotel.getPrice();
- this.score = hotel.getScore();
- this.brand = hotel.getBrand();
- this.city = hotel.getCity();
- this.starName = hotel.getStarName();
- this.business = hotel.getBusiness();
- this.xxlocation = hotel.getLatitude() + ", " + hotel.getLongitude();
- this.pic = hotel.getPic();
- }
- }
(3)、在service新建了IHotelService接口,作用是写mybatis-plus向数据库发送请求用于查询数据库的数据
- package cn.itcast.hotel.service;
- import cn.itcast.hotel.pojo.Hotel;
- import com.baomidou.mybatisplus.extension.service.IService;
-
- public interface IHotelService extends IService<Hotel> {
-
- }
(4)、在service新建了impl目录,在impl目录里面有一个HotelService类,是IHotelService接口的实现类
- package cn.itcast.hotel.service.impl;
-
- import cn.itcast.hotel.mapper.HotelMapper;
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.service.IHotelService;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.springframework.stereotype.Service;
-
- @Service
- public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
-
- }
【具体操作】
第一步: 在hotel-demo项目的src/test/java/cn.itcast.hotel目录新建HotelDocumentTest类,写入如下
- package cn.itcast.hotel;
-
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.HotelDoc;
- import cn.itcast.hotel.service.IHotelService;
- import com.alibaba.fastjson.JSON;
- import org.apache.http.HttpHost;
- import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
- import org.elasticsearch.action.index.IndexRequest;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.client.indices.CreateIndexRequest;
- import org.elasticsearch.client.indices.GetIndexRequest;
- import org.elasticsearch.common.xcontent.XContentType;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
-
- import java.io.IOException;
- import static cn.itcast.hotel.constants.HotelConstants.xxMappingTemplate;
-
- /**
- * @author 35238
- * @date 2023/6/9 0009 8:51
- */
- @SpringBootTest
- public class HotelDocumentTest {
-
- private RestHighLevelClient xxclient;
-
- @BeforeEach
- //该注解表示一开始就完成RestHighLevelClient对象的初始化
- void setUp() {
- this.xxclient = new RestHighLevelClient(RestClient.builder(
- //指定你Centos7部署的es的主机地址
- HttpHost.create("http://192.168.127.180:9200")
- ));
- }
-
- @AfterEach
- //该注解表示销毁,当对象运行完之后,就销毁这个对象
- void tearDown() throws IOException {
- this.xxclient.close();
- }
-
- //-----------------------------上面是初始化,下面是操作文档的测试-------------------------------------------
- @Autowired
- //注入写好的IHotelService接口,用于去数据库查询数据
- private IHotelService xxhotelService;
-
- @Test
- //新增文档到gghotel索引库,请保证你的es里面已经存在gghotel索引库
- void testAddDocument() throws IOException {
-
- //去数据库查询数据,我们简单查询一下id为61083的数据。由于在实体类里面定义的id是Long类型,所以要加L表示该数字是Long类型
- Hotel xxdataExample = xxhotelService.getById(61083L);
-
- //把上一行数据库查询出来的字段类型转为es的索引库的文档类型,才能往索引库里面新增文档
- HotelDoc xxhotelDoc = new HotelDoc(xxdataExample);
-
- //准备Request对象,往哪个索引库添加文档,文档的id需要自定义,xxdataExample.getId().toString()表示文档id跟数据库的id一致
- IndexRequest xxrequest = new IndexRequest("gghotel").id(xxdataExample.getId().toString());
-
- //准备JSON文档.JSON.toJSONString()是com.alibaba.fastjson提供的API,用于把JSON转为String
- xxrequest.source(JSON.toJSONString(xxhotelDoc),XContentType.JSON);
-
- //发送请求
- xxclient.index(xxrequest,RequestOptions.DEFAULT);
- }
- }
第二步: 验证。运行HotelDocumentTest类的testAddDocument方法
第三步: 如何更直观地验证,es里面的gghotel索引库里面有刚刚我们新增的文档,文档id就是数据里面的字段id
那就不得不运行kibana了,这样才能打开web浏览器页面,进行查询
docker restart kibana #启动kibana容器
浏览器访问 http://你的ip地址:5601
我们在刚刚,为了直观地验证是否成功新增文档,需要启动kibana,然后去浏览器页面进行查询,非常的麻烦,下面就来学习通过Java代码,进行查询文档
难点: 根据id查询到的文档数据类型是json,需要反序列化为java对象
第一步: 在HotelDocumentTest类,添加如下
- @Test
- void xxtestGetDocumentById() throws IOException {
-
- //准备Request对象,要查询哪个索引库,要查询的文档i,我们上面指定的文档id是跟数据库字段的id一致,上面新增的那条文档的id是61083
- GetRequest yyrequest = new GetRequest("gghotel", "61083");
-
- //发送请求,获取响应结果
- GetResponse yyresponse = xxclient.get(yyrequest, RequestOptions.DEFAULT);
-
- //解析响应结果。getSourceAsString方法的作用是把得到的JSON结果转为String
- String yyjson = yyresponse.getSourceAsString();
-
- //JSON.parseObject()是com.alibaba.fastjson提供的API,作用是对上面那行的yyjson进行反序列化
- //第一个参数是你要对谁进行反序列化,第二个参数是你想要的数据类型
- HotelDoc yyhotelDoc = JSON.parseObject(yyjson, HotelDoc.class);
-
- //输出一下查询结果
- System.out.println(yyhotelDoc);
-
- }
第二步: 运行HotelDocumentTest类的xxtestGetDocumentById方法
根据id修改酒店数据。修改es的索引库的文档的数据,有两种方式,前面在学kibana操作文档的时候学过,可前去 '实用篇-ES-索引库和文档' 进行复习
1、全量修改,会删除旧文档,添加新文档。注意: 这种操作是直接用新值覆盖掉旧的,如果只put一个字段那么其它字段就没了,所以,你不想修改的字段也要原样写出来,不然就没了。如果你写的文档id或字段不存在的话,本来是修改操作,结果就变成新增操作
2、增量修改(我们学习这种)。修改指定字段的值。如果你写的文档id或字段不存在的话,本来是修改操作,结果就变成新增操作
首先保证你已经做好了 '实用篇-ES-环境搭建' ,以及上面的五小节,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- #docker restart kibana #启动kibana容器
第一步: 在HotelDocumentTest类,添加如下
- @Test
- void xxtestUpdateDocument() throws IOException {
-
- //准备Request对象,要修改哪个索引库,要修改的文档id
- UpdateRequest zzrequest = new UpdateRequest("gghotel", "61083");
-
- //准备请求参数,要修改哪些字段,修改成什么
- zzrequest.doc(
- "name","我修改了你3个文档字段",
- "price","999",
- "city","北京"
- );
-
- //发送请求,获取响应结果
- xxclient.update(zzrequest,RequestOptions.DEFAULT);
- }
第二步: 先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)的数据。运行HotelDocumentTest类的xxtestGetDocumentById方法
第三步: 运行HotelDocumentTest类的xxtestUpdateDocument方法,作用是修改数据,也就是我们第一步写的代码
第四步: 在去查一下文档的数据,验证第三步是否修改成功。运行HotelDocumentTest类的xxtestGetDocumentById方法
首先保证你已经做好了 '实用篇-ES-环境搭建' ,以及上面的五小节,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- #docker restart kibana #启动kibana容器
第一步: 在HotelDocumentTest类,添加如下
- @Test
- void wwtestDeleteDocument() throws IOException {
-
- //准备Request对象,要删除哪个索引库,要删除的文档id
- DeleteRequest wwrequest = new DeleteRequest("gghotel", "61083");
-
- //发送请求
- xxclient.delete(wwrequest,RequestOptions.DEFAULT);
- }
第二步: 先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)能不能查询到。运行HotelDocumentTest类的xxtestGetDocumentById方法
第三步: 删除id为61083的文档(相当于删除mysql中id为某个数的那一行)。运行HotelDocumentTest类的wwtestDeleteDocument方法
第四步: 验证。再次执行第二步,也就是运行HotelDocumentTest类的xxtestGetDocumentById方法
建议去前面的 '6. 新增文档' 复习一下,在索引库里面新增一条文档,是怎么实现的
在上面的6、7、8、9节中,我们一直都是操作一条id为61083的文档(相当于数据库表的某一行)。我们如何把mysql的更多数据导入进es的索引库(相当于mysql的表)呢,下面就来学习批量把文档导入进索引库
- 思路:
- 1、利用mybatis-plus把MySQL中的酒店数据查询出来
- 2、将查询到的酒店数据转换为文档类型的数据
- 3、利用RestClient中bulk批处理方法,实现批量新增文档
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- #docker restart kibana #启动kibana容器
第一步: 在HotelDocumentTest类,添加如下
- @Test
- void testBulkRequest() throws IOException {
-
- //向数据库批量查询酒店数据,list方法表示查询数据库的所有数据
- List<Hotel> kkhotels = xxhotelService.list();
-
- //创建Request
- BulkRequest vvrequest = new BulkRequest();
-
- //准备参数,实际上就是添加多个新增的Request
- for (Hotel kkhotel : kkhotels) {
- //把遍历拿到的每个kkhotels转换为文档类型的数据
- HotelDoc ffhotelDoc = new HotelDoc(kkhotel);//HotelDoc是我们写的一个实体类
- //往哪个索引库批量新增文档、新增后的文档id是什么,文档类型是JSON
- vvrequest.add(new IndexRequest("gghotel")
- .id(ffhotelDoc.getId().toString())
- //JSON.parseObject()是com.alibaba.fastjson提供的API,作用是对ffhotelDoc进行反序列化准换为json类型
- .source(JSON.toJSONString(ffhotelDoc),XContentType.JSON));
- }
-
- //发送请求
- xxclient.bulk(vvrequest,RequestOptions.DEFAULT);
- }
第二步: 运行HotelDocumentTest类的testBulkRequest方法
\
第三步: 如何更直观地验证,es里面的gghotel索引库里面有刚刚我们新增的文档。那就不得不运行kibana了,这样才能打开web浏览器页面,进行查询
docker restart kibana #启动kibana容器
浏览器访问 http://你的ip地址:5601
输入如下DSL语句,表示查询某个索引库的所有文档
GET /gghotel/_search
上面我们导入了很多文档(相当于数据库的行,很多行),下面我们将着重学习使用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,用于控制相关度算分,算分会影响性能
下面会一个个学
查询的基本语法
- #查询所有
- GET /hotel/_search
- {
- "query":{
- "match_all": {
-
- }
- }
- }
【具体操作】
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
浏览器访问 http://你的ip地址:5601
输入如下
存在一个问题,我们明明查询的是所有文档,查询结果也显示查询出所有的文档了,为什么上图右侧,鼠标往下拉,最多才只有10条文档数据呢
原因: 受默认的分页条件限制,后面学习的时候,会进行解决
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
全文检索查询,分为下面两种,会对用户输入内容进行分词之后,再进行匹配。也就是利用分词器对用户输入内容进行分词,然后去倒排索引库中匹配。
【第一种全文检索查询】
- GET /索引库名/_search
- {
- "query": {
- "match": {
- "字段名": "TEXT"
- }
- }
- }
match查询(也就是match_query查询): 全文检索查询的一种,会对用户输入的内容进行分词,然后去倒排索引库检索
具体操作如下,为了让大家知道gghotel索引库有哪些字段,我把当初建立gghotel索引库的类先放出来
注意: 我要解释一下,上面有个字段叫xxALL,那个字段是当时自定义的,不清楚的话可回去看 '实用篇-ES-RestClient操作' 的 '2. hotel数据结构分析'。
xxALL的作用如下图,相当于一个大的字段,里面存放了几个小字段,优点是我们可以在这个大的字段里面搜索到多个小字段的信息
然后,我们就正式开始全文检索查询,输入如下。注意xxALL换成其它字段也没事,例如换成name字段。正常来说,我们检索name字段,就只在那么字段检索匹配的分词文档,但是在XXALL字段里面检索时,也会检索到name、brand、business字段,原因如上面那个图的copy_to属性
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示查询某个索引库的所有文档
【第二种全文检索查询】
- GET /索引库名/_search
- {
- "query": {
- "multi_match": {
- "query": "TEXT",
- "字段名": ["FIELD1", " FIELD12"]
- }
- }
- }
multi_match(也就是multi_match_query查询): 与match查询类似,只不过允许同时查询多个字段
例如,输入如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示查询查询business、brand、name字段中包含'如家'的文档,满足一个字段即可
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器
精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。精确查询常见的有两种:
term: 根据词条的精确值查询,强调精确匹配
range: 根据值的范围查询,例如金额、时间
【第一种精确查询 term】
具体操作如下
- GET /索引库名/_search
- {
- "query": {
- "term": {
- "字段名": {
- "value": "VALUE"
- }
- }
- }
- }
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示查询city字段为 '上海' 的文档,必须是 '上海' 才能被匹配,不对'上海'进行分词,也就是不会拆成'上'和'海'
【第一种精确查询 range】
具体操作如下
- GET /索引库名/_search
- {
- "query": {
- "range": {
- "字段名": {
- "gte": 10,
- "lte": 20
- }
- }
- }
- }
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示查找price字段满足200~300数值的文档,注意字段类型不能是binary,也就是price字段的类型不能是binary
gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
- # 第一种精确查询 term。
- GET /gghotel/_search
- {
- "query":{
- "term": {
- "city": {
- "value": "上海"
- }
- }
- }
- }
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器
根据经纬度查询。常见的使用场景包括: 查询附近酒店、附近出租车、搜索附近的人。使用方式有很多种,介绍如下
geo_bounding_box: 查询geo_point值落在某个矩形范围的所有文档,用两个点来围成的矩形范围
geo_distance: 查询到指定中心点,且小于某个距离值的所有文档,圆心到圆边的范围
【第一种地理查询 geo_bounding_box 不演示这种,不常用】
- GET /索引库名/_search
- {
- "query": {
- "geo_bounding_box": {
- "字段名": {
- "top_left": {
- "lat": 31.1,
- "lon": 121.5
- },
- "bottom_right": {
- "lat": 30.9,
- "lon": 121.7
- }
- }
- }
- }
- }
【第一种地理查询 geo_distance 下面演示这种】
- GET /索引库名/_search
- {
- "query": {
- "geo_distance": {
- "distance": "15km",
- "字段名": "31.21,121.5"
- }
- }
- }
具体操作如下,但是,为了让大家知道gghotel索引库有哪些字段,我把当初建立gghotel索引库的类先放出来
上面的xxlocation字段类型必须是geo_point,否则该字段不能用于地理查询
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句。表示查找xxlocation字段在(31.25±15km,121.5±15km)范围内的文档
上面学的全文检索查询、精确查询、地理查询,这三种查询在es当中都称为简单查询,下面我们将学习复合查询。复合查询可以其它简单查询组合起来,实现更复杂的搜索逻辑,其中就有 '算分函数查询' 如下
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器
算分函数查询(function score): 可以控制文档相关性算分、控制文档排名。例如搜索'外滩' 和 '如家' 词条时,某个文档要是都能匹配这两个词条,那么在所有被搜索出来的文档当中,这个文档的位置就最靠前,简单说就是越匹配就排名越靠前
- GET /索引库名/_search
- {
- "query": {
- "match": {
- "字段名": {
- "query": "词条"
- }
- }
- }
- }
具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示在name字段,哪个文档的匹配度高,排名就靠前
- GET /gghotel/_search
- {
- "query": {
- "match": {
- "name": {
- "query": "7天连锁酒店"
- }
- }
- }
- }
这是第一种复合查询
上面只是简单演了相关性打分中的函数算分查询,文档与搜索关键字的相关度越高,打分就越高,排名就越靠前。不过,有的时候,我们希望人为地去控制控制文档的排名,例如某些文档我们就希望排名靠前一点,算分高一点,此时就需要使用函数算分查询,下面就来学习 '函数算分查询'
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器
使用 ’函数算分查询(function score query)’,可以在原始的相关性算分的基础上加以修改,得到一个想要的算分,从而去影响文档的排名,语法如下
- GET /索引库名/_search
- {
- "query": {
- "function_score": {
- "query": { "match": {"字段": "词条"} },
- "functions": [
- {
- "filter": {"term": {"指定字段": "值"}},
- "算分函数": 函数结果
- }
- ],
- "boost_mode": "加权模式"
- }
- }
- }
具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示在 '如家' 这个品牌中,字段为'北京'的酒店排名靠前一些
- GET /gghotel/_search
- {
- "query": {
- "function_score": {
- "query": {"match": {
- "brand": "如家"
- }},
- "functions": [
- {
- "filter": {
- "term": {
- "city": "北京"
- }
- },
- "weight": 2
- }
- ],
- "boost_mode": "sum"
- }
- }
- }
这是第二种复合查询
布尔查询不会去修改算分,而是把多个查询语句组合成一起,形成新查询,这些被组合的查询语句,被称为子查询。子查询的组合方式有如下四种
1、must:必须匹配每个子查询,类似"与"
2、should:选择性匹配子查询,类似"或"
3、must_not:必须不匹配,不参与算分,类似"非"
4、filter:必须匹配,不参与算分
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
- GET /索引库名/_search
- {
- "query": {
- "bool": {
- "must": [
- {"term": {"字段名": "字段值" }}
- ],
- "should": [
- {"term": {"字段名": "字段值" }},
- {"term": {"字段名": "字段值" }}
- ],
- "must_not": [
- { "range": { "字段名": { "lte": 最小字段值 } }}
- ],
- "filter": [
- { "range": {"字段名": { "gte": 最大字段值 } }}
- ]
- }
- }
- }
具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示搜索名字包含'如家',价格不高于400,在坐标31.21,121.5周围10km范围内的文档
must表示匹配条件(注意写在must里面就会参与算分,也就是查询出来的score值会更高),must_not表示取反,filter表示过滤
- GET /gghotel/_search
- {
- "query": {
- "bool": {
- "must": [
- {
- "match": {
- "name": "如家"
- }
- }
- ],
- "must_not": [
- {
- "range": {
- "price": {
- "gt": 400
- }
- }
- }
- ],
- "filter": [
- {
- "geo_distance": {
- "distance": "10km",
- "xxlocation": {
- "lat": 31.21,
- "lon": 121.5
- }
- }
- }
- ]
- }
- }
- }
elasticsearch(称为es)支持对搜索的结果,进行排序,默认是根据 '相关度' 算分,也就是score值,根据score值进行排序。
可以排序的字段类型有: keyword类型、数值类型、地理坐标类型、日期类型
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- docker restart kibana #由于DSL语句是需要kibana服务,然后在浏览器进行,所以这里要开启kibana容器
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
sort里面可以指定多个排序字段,用花括号隔开。排序方式: ASC(升序)、DESC(降序)
- GET /索引库名/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "需要排序的字段名": "排序方式"
- }
- ]
- }
具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
【案例一】
第二步: 输入如下DSL语句,表示对所有的文档,根据评分(score)进行降序排序,如果评分相同就根据价格(price)升序排序
- GET /gghotel/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "score": "desc"
- },
- {
- "price": "asc"
- }
- ]
- }
上图的_score算分为null,是因为我们如果做了排序,那么打分就没有意义了,所以es就会放弃打分不再做相关性算分,提高效率
【案例二】
获取国内任意位置的经纬度的网站: 获取鼠标点击经纬度-地图属性-示例中心-JS API 2.0 示例 | 高德地图API
longitude 经度 latitude 纬度 (经度,纬度): 这是我们描述经纬度的写法,先经度再纬度,但是在下面写的时候
第三步: 输入如下DSL语句,表示找到(121.66053,28.28811)周围的文档,并按照距离进行升序排序
下面两种写法都是一样的,注意第二种写法前面写的是纬度,后面写的是经度
- 第一种写法
- GET /gghotel/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "_geo_distance": {
- "xxlocation": {
- "lat": 28.28811,
- "lon": 121.66053
- },
- "order": "asc"
- }
- }
- ]
- }
-
- 第二种写法
- GET /gghotel/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "_geo_distance": {
- "xxlocation": "28.28811,121.66053",
- "order": "asc"
- }
- }
- ]
- }
上图右侧的sort表示距离 '28.28811,121.66053' 有多少公里,例如281547.94km。
上图的_score算分为null,是因为我们如果做了排序,那么打分就没有意义了,所以es就会放弃打分不再做相关性算分,提高效率
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
elasticsearch(称为es)默认情况下只返回前10 条数据。而如果要查询更多数据就需要修改分页参数,分页参数包括from和size,语法如下
- GET /索引库名/_search
- {
- "query": {
- "要查询的字段": {}
- },
- "from": 要查第几页, // 分页开始的位置,默认为0
- "size": 每页显示多少条文档, // 期望获取的文档总数
- "sort": [ //表示排序
- {"price": "排序方式"}
- ]
- }
具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示对所有的文档,根据价格(price)进行升序排序,每次分页显示20条数据,看的是第六页
size默认是10,表示一页显示多少条文档。from默认是0,表示你要看的是第一页
- GET /gghotel/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "price": {
- "order": "asc"
- }
- }
- ],
- "from": 0,
- "size": 20
- }
上面是基础的分页用法,下面来详细了解es的分页。es的底层使用的是倒排索引,是不利于做分页的,es采用的是逻辑上的分页,就会导致当是分布式的时候,就会产生下面的问题,因此es限制结果集最多为10000
ES是分布式的,所以会面临深度分页的问题。例如按price排序后,获取from=990,size=10的数据,如下图
深度分页查询的演示,输入如下DSL语句,表示
- GET /gghotel/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "price": {
- "order": "asc"
- }
- }
- ],
- "from": 9991,
- "size": 10
- }
百度在这方面,最多能查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' 。优点: 支持随机翻页。缺点: 存在深度分页问题。场景: 百度、京东、谷歌、淘宝
高亮: 就是在搜索结果中把搜索关键字突出显示。高亮显示的原理如下
1、将搜索结果中的关键字用标签标记出来
2、在页面中给标签添加css样式
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,导入了批量文档。然后开始下面的操作
语法
- GET /索引库名/_search
- {
- "query": {
- "match": { //match表示带关键字的查询
- "字段": "TEXT"
- }
- },
- "highlight": {
- "fields": {
- "字段名": {
- "require_field_match": "false",//默认是true,表示 '字段' 要和 '字段名' 要一致。如果我们写的是不一致的话,就需要修改为false
- "pre_tags": "<em>", // 用来标记高亮字段的前置标签,es会帮我们把标签加在关键字上。默认是<em>
- "post_tags": "</em>" // 用来标记高亮字段的后置标签,es会帮我们把标签加在关键字上。默认是</em>
- }
- }
- }
- }
具体操作如下
第一步: 浏览器访问 http://你的ip地址:5601
第二步: 输入如下DSL语句,表示
- GET /gghotel/_search
- {
- "query": {
- "match": {
- "xxALL": "北京"
- }
- },
- "highlight": {
- "fields": {
- "name": {
- "require_field_match": "false",
- "pre_tags": "<em>",
- "post_tags": "</em>"
- }
- }
- }
- }
搜索结果处理的整体语法
- GET /索引库名/_search
- {
- "query": {
- "match": {
- "字段名": "如家"
- }
- },
- "from": 0, // 分页开始的位置
- "size": 20, // 期望获取的文档总数
- "sort": [
- { "price": "asc" }, // 普通排序
- {
- "_geo_distance" : { // 距离排序
- "location" : "31.040699,121.618075",
- "order" : "asc",
- "unit" : "km"
- }
- }
- ],
- "highlight": {
- "fields": { // 高亮字段
- "字段名": {
- "pre_tags": "<em>", // 用来标记高亮字段的前置标签
- "post_tags": "</em>" // 用来标记高亮字段的后置标签
- }
- }
- }
- }
上面的查询文档都是依赖kibana,在浏览器页面使用DSL语句去查询es,如何用java去查询es里面的文档(数据)呢
我们通过match_all查询来演示基本的API,注意下面演示的是 'match_all查询,也叫基础查询'
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- #docker restart kibana #启动kibana容器
在进行下面的操作之前,确保你已经看了前面 '实用篇-ES-RestClient操作文档' 学的 '1. RestClient案例准备',然后在进行下面的操作
第一步: 在src/test/java/cn.itcast.hotel目录新建HotelSearchTest类,写入如下
- package cn.itcast.hotel;
-
- import org.apache.http.HttpHost;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
-
- import java.io.IOException;
-
- public class HotelSearchTest {
-
- private RestHighLevelClient xxclient;
-
- @BeforeEach
- //该注解表示一开始就完成RestHighLevelClient对象的初始化
- void setUp() {
- this.xxclient = new RestHighLevelClient(RestClient.builder(
- //指定你Centos7部署的es的主机地址
- HttpHost.create("http://192.168.127.180:9200")
- ));
- }
-
- @AfterEach
- //该注解表示销毁,当对象运行完之后,就销毁这个对象
- void tearDown() throws IOException {
- this.xxclient.close();
- }
-
- //-----------------------------上面是初始化,下面是查询文档-快速入门的测试-------------------------------------------
-
- @Test
- void xxtestMatchAll() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
- xxrequest.source().query(QueryBuilders.matchAllQuery());
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
- //在控制台输出结果
- System.out.println(xxresponse);
- }
- }
上面java代码以及对应的DSL语句如下图
第二步: 把控制台里面我们需要的数据解析出来。返回的数据很多,我们主要是解析hits里面的数据就行了
把HotelSearchTest类修改为如下,主要的修改是sout之前做了一次解析,拿到我们想要的数据
- package cn.itcast.hotel;
-
- import cn.itcast.hotel.pojo.HotelDoc;
- import com.alibaba.fastjson.JSON;
- import org.apache.http.HttpHost;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.search.SearchHit;
- import org.elasticsearch.search.SearchHits;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
-
- import java.io.IOException;
-
- public class HotelSearchTest {
-
- private RestHighLevelClient xxclient;
-
- @BeforeEach
- //该注解表示一开始就完成RestHighLevelClient对象的初始化
- void setUp() {
- this.xxclient = new RestHighLevelClient(RestClient.builder(
- //指定你Centos7部署的es的主机地址
- HttpHost.create("http://192.168.127.180:9200")
- ));
- }
-
- @AfterEach
- //该注解表示销毁,当对象运行完之后,就销毁这个对象
- void tearDown() throws IOException {
- this.xxclient.close();
- }
-
- //-----------------------------上面是初始化,下面是查询文档-快速入门的测试-------------------------------------------
-
- @Test
- void xxtestMatchAll() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
- xxrequest.source().query(QueryBuilders.matchAllQuery());
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
-
- }
- }
- }
上面java代码以及对应的DSL语句如下图
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
全文检索的 match 和 multi_match 查询与 match_all 的API基本一致。差别是查询条件,也就是query的部分,如下图
我们刚刚在第一节演示的是 match_all(也叫基本查询) 查询,下面将演示 match(也叫单字段查询) 和 multi_match(也叫多字段查询) 查询
【matc_all查询,也叫基本查询,我们在 '快速入门' 已经演示过】
在HotelSearchTest类添加如下(已做可跳过)
- @Test
- void xxtestMatchAll() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
- xxrequest.source().query(QueryBuilders.matchAllQuery());
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
【match 查询,也叫单字段查询】
在HotelSearchTest类添加如下
- @Test
- void xxtestMatch() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
- xxrequest.source().query(QueryBuilders.matchQuery("name","如家"));
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
【multi_match 查询,也叫多字段查询】
在HotelSearchTest类添加如下
- @Test
- void xxtestMutilMatch() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
- xxrequest.source().query(QueryBuilders.multiMatchQuery("如家","name","business"));
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
总结: 要构建查询条件,只要记住一个QueryBuilders类即可
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
我们发现对于 match、multi_match、match_all 查询,的解析部分的代码都是相同的,所以我们可以对解析部分的代码进行抽取(ctrl+alt+m),如下
- //这个方法就是我们抽取出来的,负责解析的
- private void handleResponse(SearchResponse xxresponse) {
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。精确查询常见的有两种:
term: 根据词条的精确值查询,强调精确匹配
range: 根据值的范围查询,例如金额、时间
java代码和DSL语句的对应关系如下图。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
【term查询】在HotelSearchTest类添加如下
- @Test
- void xxtestTerm() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
- xxrequest.source().query(QueryBuilders.termQuery("city","上海"));
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
【range查询】在HotelSearchTest类添加如下
- @Test
- void xxtestTerm() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //准备DSL语句,source方法可以调用很多API。QueryBuilders是RestClient提供的工具,可以调用很多查询类型
- xxrequest.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
总结: 要构建查询条件,只要记住一个QueryBuilders类即可
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
java代码和DSL语句的对应关系如下图
【bool查询】在HotelSearchTest类添加如下
- @Test
- void xxtestBool() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //创建布尔查询
- BoolQueryBuilder xxboolQuery = QueryBuilders.boolQuery();
- //添加must条件
- xxboolQuery.must(QueryBuilders.termQuery("city","上海"));
- //添加filter条件
- xxboolQuery.filter(QueryBuilders.rangeQuery("price").lte(200));
-
- //把上面的布尔对象传进来,就可以生效了
- xxrequest.source().query(xxboolQuery);
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
总结: 要构建查询条件,只要记住一个QueryBuilders类即可
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
【geo_distance查询】在HotelSearchTest类添加如下
- @Test
- void xxtestGeoDistance() throws IOException {
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //创建一个地理位置查询构造器,指定了要查询字段的是xxlocation
- GeoDistanceQueryBuilder xxgeoQuery = QueryBuilders.geoDistanceQuery("xxlocation");
- xxgeoQuery.point(31.25, 121.5);//设置查询的中心点坐标,这里的经度和纬度分别为 31.25 和 121.5
- xxgeoQuery.distance(5, DistanceUnit.KILOMETERS);//设置查询的半径距离和单位,这里的 5 即表示 5 公里
-
- // 创建一个查询构造器
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- // 将查询条件添加到查询构造器对象
- searchSourceBuilder.query(xxgeoQuery);
- // 将查询构造器的对象,添加到查询请求对象xxrequest中,就可以生效了
- xxrequest.source(searchSourceBuilder);
-
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
总结: 要构建查询条件,只要记住一个QueryBuilders类即可
上面是各种查询的学习,当我们把文档查询出来的时候,接下来就是对文档的处理,也就是你要把查询结果怎么展示出来。API以及对应的DSL语句如下图
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
【排序、分页】在HotelSearchTest类添加如下
- @Test
- void xxtestPageAndSort() throws IOException {
-
- //页面、每页大小。如果你要翻第二页,就把下面的xxpage改成2
- int xxpage = 1, xxsize = 5;
-
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //查询全部
- xxrequest.source().query(QueryBuilders.matchAllQuery());
-
- //sort排序,asc升序,desc降序
- xxrequest.source().sort("price", SortOrder.ASC);
- //from、size分页。例如查第一页,每页显示5条文档(数据)。from表示当前页,我们使用公式动态设定
- xxrequest.source().from((xxpage-1)*xxsize).size(5);
-
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
高亮API包括请求DSL构建和结果解析两部分,API和对应的DSL语句如下图,下图只是构建,再下面还有解析,高亮必须由构建+解析才能实现
解析,如下图
首先保证你已经做好了 '实用篇-ES-环境搭建' ,创建了名为gghotel的索引库,然后开始下面的操作。如果需要浏览器操作es,那就不需要启动kibana容器
【高亮显示-】在HotelSearchTest类添加如下
- @Test
- void xxtestHightlight() throws IOException {
-
- //准备Request对象,要查询哪个索引库,
- SearchRequest xxrequest = new SearchRequest("gghotel");
-
- //【构建】
- //查询name字段的文档
- xxrequest.source().query(QueryBuilders.matchQuery("name","上海"));
- //对查询出来的文档,的特定字段进行高亮显示
- xxrequest.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(true).preTags("<em>").postTags("</em>"));
-
- //发送请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
- //解析获取到杂乱JSON数据
- SearchHits xxsearchHits = xxresponse.getHits();
- //获取总条数
- long xxtotal = xxsearchHits.getTotalHits().value;
- System.out.println("共搜索到"+xxtotal+"条文档(数据)");
- //获取hits数组
- SearchHit[] xxhits = xxsearchHits.getHits();
- //遍历数组,把hits数组的每个source取出来
- for (SearchHit xxhit : xxhits) {
- String xxjson = xxhit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc xxhotelDoc = JSON.parseObject(xxjson, HotelDoc.class);
-
- //【解析】获取高亮结果
- Map<String, HighlightField> xxhighlightFields = xxhit.getHighlightFields();
- //使用CollectionUtils工具类,进行判空,避免空指针
- if (!CollectionUtils.isEmpty(xxhighlightFields)){
- //根据字段名获取高亮结果
- HighlightField xxhighlightField = xxhighlightFields.get("name");
- //判断name不为空
- if (xxhighlightField != null) {
- //获取高亮值
- String xxname = xxhighlightField.getFragments()[0].string();
- //覆盖非高亮结果
- xxhotelDoc.setName(xxname);
- }
- }
- //最终输出
- System.out.println("每个HotelDoc对象 = " + xxhotelDoc);
- }
- }
这个案例我做了两遍才做出来了,第一遍排了一上午的错,所以很有必要进行环境准备,下面我将带领你对一下我的环境,全网最详细的自创笔记
企业部署一般都是采用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工具,执行如下
- yum install -y yum-utils \
- device-mapper-persistent-data \
- lvm2 --skip-broken
第二步: 更新本地镜像源,执行如下
- # 设置docker镜像源
- yum-config-manager \
- --add-repo \
- https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
-
- sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
-
- yum makecache fast
第三步: 执行如下安装docker,稍等片刻,docker即可安装成功。docker-ce为社区免费版本
yum install -y docker-ce
第四步: 由于Docker应用需要用到各种端口,逐一去修改防火墙设置,会非常麻烦,所以学习期间直接关闭防火墙即可
- # 关闭
- systemctl stop firewalld
- # 禁止开机启动防火墙
- systemctl disable firewalld
第五步: 通过命令启动docker
- systemctl start docker # 启动docker服务
-
- systemctl stop docker # 停止docker服务
-
- systemctl restart docker # 重启docker服务
-
- systemctl status docker # 查看docker的启动状态
-
- docker -v # 查看docker版本
第六步: 配置docker镜像仓库,设置为国内的镜像仓库,以后在docker里面下载东西的时候速度会更快。分别执行如下命令
sudo mkdir -p /etc/docker # 创建文件夹
- sudo tee /etc/docker/daemon.json <<-'EOF'
- {
- "registry-mirrors": ["https://93we6x1g.mirror.aliyuncs.com"]
- }
- EOF # 在刚刚创建的文件夹里面新建daemon.json文件,并写入花括号里面的数据
sudo systemctl daemon-reload # 重新加载daemon.json文件
sudo systemctl restart docker # 重启docker
第一步: 创建网络
- systemctl start docker # 启动docker服务
- 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容器
- docker run -d \
- --name es \
- -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
- -e "discovery.type=single-node" \
- -v es-data:/usr/share/elasticsearch/data \
- -v es-plugins:/usr/share/elasticsearch/plugins \
- --privileged \
- --network es-net \
- -p 9200:9200 \
- -p 9300:9300 \
- elasticsearch:7.12.1
然后,在浏览器中输入:http://你的ip地址:9200 即可看到elasticsearch的响应结果
第一步: 打开database软件,把tb_hotel.sql文件导入进你的数据库
tb_hotel.sql下载: https://cowtransfer.com/s/68c94a66d17248
- create database if not exists elasticsearch;
- use elasticsearch;
第一步: 把下载好的hotel-demo.zip压缩包解压,得到hotel-demo文件夹,在idea打开hotel-demo
hotel-demo.zip下载:https://cowtransfer.com/s/36ac0a9f9d9043
第二步: 修改application.yml文件,配置正确的数据库信息
第三步: 把pom.xml修改为如下
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.3.10.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>cn.itcast.demo</groupId>
- <artifactId>hotel-demo</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>hotel-demo</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>1.8</java.version>
- <elasticsearch.version>7.12.1</elasticsearch.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <!--引入es的RestHighLevelClient,版本要跟你Centos7里面部署的es版本一致-->
- <dependency>
- <groupId>org.elasticsearch.client</groupId>
- <artifactId>elasticsearch-rest-high-level-client</artifactId>
- <version>7.12.1</version>
- </dependency>
-
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.4.2</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- <exclusions>
- <exclusion>
- <groupId>org.junit.vintage</groupId>
- <artifactId>junit-vintage-engine</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <!--FastJson-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.71</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- </dependency>
-
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </exclude>
- </excludes>
- </configuration>
- </plugin>
- </plugins>
- </build>
-
- </project>
把mysql的数据导入进es,我们需要使用前面学的es提供的RestClient,就可以通过java代码创建索引库,并往这个索引库导入文档(文档就是数据的意思)
第一步: 在hotel-demo项目的 src/test/java/cn.itcast.hotel 目录新建 HotelIndexTest 类,用于在es中创建名为hotel的索引库,写入如下
写完就运行xxcreateHotelIndex方法,把索引库创建出来
- package cn.itcast.hotel;
-
- import org.apache.http.HttpHost;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.client.indices.CreateIndexRequest;
- import org.elasticsearch.common.xcontent.XContentType;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import java.io.IOException;
- import static cn.itcast.hotel.constants.HotelConstants.xxMappingTemplate;
-
- public class HotelIndexTest {
-
- private RestHighLevelClient xxclient;
-
- @BeforeEach
- //该注解表示一开始就完成RestHighLevelClient对象的初始化
- void setUp() {
- this.xxclient = new RestHighLevelClient(RestClient.builder(
- //指定你Centos7部署的es的主机地址
- HttpHost.create("http://192.168.127.180:9200")
- ));
- }
-
- @AfterEach
- //该注解表示销毁,当对象运行完之后,就销毁这个对象
- void tearDown() throws IOException {
- this.xxclient.close();
- }
-
- //删除索引库(如果下面创建hotel索引库的时候,出现已存在,那么就执行这里的删除操作,把hotel索引库删掉,再创建)
- @Test
- void xxtestDeleteHotelIndex() throws IOException {
- //创建Request对象,指定要删除哪个索引库
- DeleteIndexRequest gghotel = new DeleteIndexRequest("hotel");
- //发送请求
- xxclient.indices().delete(gghotel, RequestOptions.DEFAULT);
- }
-
- //使用xxclient对象,向es创建索引库
- @Test
- void xxcreateHotelIndex() throws IOException {
- //创建Request对象,自定义索引库名称为hotel
- CreateIndexRequest gghotel = new CreateIndexRequest("hotel");
- //准备请求的参数: DSL语句
- gghotel.source(xxMappingTemplate, XContentType.JSON);
- //发送请求
- xxclient.indices().create(gghotel, RequestOptions.DEFAULT);
- }
- }
第二步: 在hotel-demo项目的 src/main/java/cn.itcast.hotel 目录新建 constants.HotelConstants类,为es准备数据,写入如下
- package cn.itcast.hotel.constants;
-
-
- public class HotelConstants {
-
- public static final String xxMappingTemplate = "{\n" +
- " \"mappings\": {\n" +
- " \"properties\": {\n" +
- " \"id\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"name\":{\n" +
- " \"type\": \"text\",\n" +
- " \"analyzer\": \"ik_max_word\",\n" +
- " \"copy_to\": \"all\"\n" +
- " },\n" +
- " \"address\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": false\n" +
- " },\n" +
- " \"price\":{\n" +
- " \"type\": \"integer\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"score\":{\n" +
- " \"type\": \"integer\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"brand\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true,\n" +
- " \"copy_to\": \"all\"\n" +
- " },\n" +
- " \"city\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"business\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true,\n" +
- " \"copy_to\": \"all\"\n" +
- " },\n" +
- " \"location\":{\n" +
- " \"type\": \"geo_point\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"starName\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": true\n" +
- " },\n" +
- " \"pic\":{\n" +
- " \"type\": \"keyword\",\n" +
- " \"index\": false\n" +
- " },\n" +
- " \"all\":{\n" +
- " \"type\": \"text\",\n" +
- " \"analyzer\": \"ik_max_word\"\n" +
- " }\n" +
- " }\n" +
- " }\n" +
- "}";
- }
第三步(这一步好像项目本身做好了,已做可跳过): 在hotel-demo项目的 src/main/java/cn.itcast.hotel/pojo 目录新建Hotel、HotelDoc类,写入如下
- package cn.itcast.hotel.pojo;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
-
- @Data
- @TableName("tb_hotel")
- public class Hotel {
- @TableId(type = IdType.INPUT)
- private Long id;
- private String name;
- private String address;
- private Integer price;
- private Integer score;
- private String brand;
- private String city;
- private String starName;
- private String business;
- private String longitude;
- private String latitude;
- private String pic;
- }
- package cn.itcast.hotel.pojo;
-
- import lombok.Data;
- import lombok.NoArgsConstructor;
-
- @Data
- @NoArgsConstructor
- public class HotelDoc {
- private Long id;
- private String name;
- private String address;
- private Integer price;
- private Integer score;
- private String brand;
- private String city;
- private String starName;
- private String business;
- private String location;
- private String pic;
-
- public HotelDoc(Hotel hotel) {
- this.id = hotel.getId();
- this.name = hotel.getName();
- this.address = hotel.getAddress();
- this.price = hotel.getPrice();
- this.score = hotel.getScore();
- this.brand = hotel.getBrand();
- this.city = hotel.getCity();
- this.starName = hotel.getStarName();
- this.business = hotel.getBusiness();
- this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
- this.pic = hotel.getPic();
- }
- }
第四步: 在hotel-demo项目的 src/test/java/cn.itcast.hotel 目录新建 HotelDocumentTest类,用于把mysql的数据批量导入进es,写入如下
写完就运行testBulkRequest方法,把数据往索引库里面批量导入
- package cn.itcast.hotel;
-
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.HotelDoc;
- import cn.itcast.hotel.service.IHotelService;
- import com.alibaba.fastjson.JSON;
- import org.apache.http.HttpHost;
- import org.elasticsearch.action.bulk.BulkRequest;
- import org.elasticsearch.action.index.IndexRequest;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.common.xcontent.XContentType;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
-
- import java.io.IOException;
- import java.util.List;
-
- @SpringBootTest
- public class HotelDocumentTest {
-
- private RestHighLevelClient xxclient;
-
- @BeforeEach
- //该注解表示一开始就完成RestHighLevelClient对象的初始化
- void setUp() {
- this.xxclient = new RestHighLevelClient(RestClient.builder(
- //指定你Centos7部署的es的主机地址
- HttpHost.create("http://192.168.127.180:9200")
- ));
- }
-
- @AfterEach
- //该注解表示销毁,当对象运行完之后,就销毁这个对象
- void tearDown() throws IOException {
- this.xxclient.close();
- }
-
- @Autowired
- //注入写好的IHotelService接口,用于去数据库查询数据
- private IHotelService xxhotelService;
-
- @Test
- void testBulkRequest() throws IOException {
-
- //向数据库批量查询酒店数据,list方法表示查询数据库的所有数据
- List<Hotel> kkhotels = xxhotelService.list();
-
- //创建Request
- BulkRequest vvrequest = new BulkRequest();
-
- //准备参数,实际上就是添加多个新增的Request
- for (Hotel kkhotel : kkhotels) {
- //把遍历拿到的每个kkhotels转换为文档类型的数据
- HotelDoc ffhotelDoc = new HotelDoc(kkhotel);//HotelDoc是我们写的一个实体类
- //往哪个索引库批量新增文档、新增后的文档id是什么,文档类型是JSON
- vvrequest.add(new IndexRequest("hotel")
- .id(ffhotelDoc.getId().toString())
- //JSON.parseObject()是com.alibaba.fastjson提供的API,作用是对ffhotelDoc进行反序列化准换为json类型
- .source(JSON.toJSONString(ffhotelDoc),XContentType.JSON));
- }
-
- //发送请求
- xxclient.bulk(vvrequest,RequestOptions.DEFAULT);
- }
-
- }
【请保证网络正常,否则页面的静态资源部分加载不了】
- systemctl start docker # 启动docker服务
- docker restart es #启动elasticsearch容器
- #docker restart kibana #启动kibana容器
实现步骤如下
一、根据前端的请求,定义实体类来接收前端的请求
(1)在pojo目录新建RequestParams类,写入如下
- package cn.itcast.hotel.pojo;
-
- import lombok.Data;
-
-
- @Data
- public class RequestParams {
- //搜索关键字
- private String key;
- //当前页码
- private Integer page;
- //每页大小
- private Integer size;
- //将来的排序字段
- private String sortBy;
- }
二、定义controller接口,接收页面请求,调用IHotelService的search方法
(1)在pojo目录新建PageResult类,写入如下
- package cn.itcast.hotel.pojo;
-
- import lombok.Data;
- import java.util.List;
-
-
- @Data
- public class PageResult {
- //总条数
- private Long total;
- //类型
- private List<HotelDoc> hotels;
-
- //不带参构造函数
- public PageResult() {
- }
-
- //带参构造函数
- public PageResult(Long total, List<HotelDoc> hotels) {
- this.total = total;
- this.hotels = hotels;
- }
- }
(2)、把IHotelService接口,修改为如下
- package cn.itcast.hotel.service;
-
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import com.baomidou.mybatisplus.extension.service.IService;
-
- public interface IHotelService extends IService<Hotel> {
- PageResult search(RequestParams params);
- }
(3)、把HotelDemoApplication启动类,修改为如下
- package cn.itcast.hotel;
-
- import com.baomidou.mybatisplus.annotation.DbType;
- import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
- import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
- import org.apache.http.HttpHost;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.mybatis.spring.annotation.MapperScan;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.context.annotation.Bean;
-
- @MapperScan("cn.itcast.hotel.mapper")
- @SpringBootApplication
- public class HotelDemoApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(HotelDemoApplication.class, args);
- }
-
- @Bean
- //注入es提供的RestHighLevelClient类
- public RestHighLevelClient client(){
- return new RestHighLevelClient(RestClient.builder(
- //指定你Centos7部署的es的主机地址
- HttpHost.create("http://192.168.127.180:9200")
- ));
- }
-
- }
(4)、在src/main/java/cn.itcast.hotel目录新建web.HotelController类,写入如下
- package cn.itcast.hotel.web;
-
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import cn.itcast.hotel.service.IHotelService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- @RequestMapping("/hotel")
- public class HotelController {
-
- //注入项目准备好的IHotelService接口
- @Autowired
- private IHotelService hotelService;
-
- @PostMapping("/list")
- //使用@RequestBody注解,接收前端的请求。PageResult、RequestParams是我们刚刚定义的实体类
- public PageResult search(@RequestBody RequestParams params){
- return hotelService.search(params);
- }
- }
三、定义IHotelService中的search方法,利用match查询实现根据关键字搜索酒店信息
(4)、把HotelService类,修改为如下
- package cn.itcast.hotel.service.impl;
-
- import cn.itcast.hotel.mapper.HotelMapper;
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.HotelDoc;
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import cn.itcast.hotel.service.IHotelService;
- import com.alibaba.fastjson.JSON;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.search.SearchHit;
- import org.elasticsearch.search.SearchHits;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
-
- @Service
- public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
-
- @Autowired
- //注入在引导类声明好的@Bean
- private RestHighLevelClient client;
-
- @Override
- public PageResult search(RequestParams params) {
-
- try {
- //准备Request对象,要查询哪个索引库,
- SearchRequest request = new SearchRequest("hotel");
-
- //【关键字搜索功能】
- String key = params.getKey();//前端传过来的搜索关键字
- //判断前端传的key是否为空,避免空指针
- if (key == null || "".equals(key)) {
- //matchAllQuery方法表示查es的全部文档,不需要条件
- request.source().query(QueryBuilders.matchAllQuery());
- } else {
- //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
- request.source().query(QueryBuilders.matchQuery("all", key));
- }
-
- //【分页功能】
- int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
- int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
- request.source().from((page - 1) * size).size(size);
-
-
- //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
-
-
- //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
- return handleResponse(response);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- //这个方法就是我们抽取出来的,负责解析的
- private PageResult handleResponse(SearchResponse response) {
- //解析获取到杂乱JSON数据
- SearchHits searchHits = response.getHits();
- //获取总条数
- long total = searchHits.getTotalHits().value;
- System.out.println("共搜索到"+total+"条文档(数据)");
- //获取hits数组
- SearchHit[] hits = searchHits.getHits();
- //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
- List<HotelDoc> hotels = new ArrayList<>();
- for (SearchHit hit : hits) {
- String json = hit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
- hotels.add(hotelDoc);
- }
- //封装返回
- return new PageResult(total,hotels);
- }
- }
四、运行HotelDemoApplication引导类,浏览器访问 http://localhost:8089/
先看需求。添加品牌、城市、星级、价格等条件过滤功能
分析:
1、修改RequestParams类,添加brand、city、startName、minPrice、maxPrice等参数
2、修改HotelService类的search方法的实现,在关键字搜索时,如果brand等参数存在,就需要对其做过滤
3、注意多个条件之间是AND关系,组合多条件用BooleanQuery
4、参数存在才需要过滤,做好非空判断
5、city精确匹配,brand精确匹配,startName精确匹配,price范围过滤
第一步: 把RequestParams类,修改为如下
- package cn.itcast.hotel.pojo;
-
- import lombok.Data;
-
-
- @Data
- public class RequestParams {
- //搜索关键字
- private String key;
- //当前页码
- private Integer page;
- //每页大小
- private Integer size;
- //将来的排序字段
- private String sortBy;
-
- //城市
- private String city;
- //品牌
- private String brand;
- //星级
- private String starName;
- //价格最小值
- private Integer minPrice;
- //价格最大值
- private Integer maxPrice;
- }
第二步: 把HotelService类,修改为如下
- package cn.itcast.hotel.service.impl;
-
- import cn.itcast.hotel.mapper.HotelMapper;
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.HotelDoc;
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import cn.itcast.hotel.service.IHotelService;
- import com.alibaba.fastjson.JSON;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.index.query.BoolQueryBuilder;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.search.SearchHit;
- import org.elasticsearch.search.SearchHits;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
-
- @Service
- public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
-
- @Autowired
- //注入在引导类声明好的@Bean
- private RestHighLevelClient client;
-
-
- @Override
- public PageResult search(RequestParams params) {
-
- try {
- //准备Request对象,要查询哪个索引库,
- SearchRequest request = new SearchRequest("hotel");
-
- //【构建BooleanQuery】
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-
- //【关键字搜索】
- //判断前端传的key是否为空,避免空指针
- String key = params.getKey();//前端传过来的搜索关键字
- //用的是must精确查找
- if (key == null || "".equals(key)) {
- //matchAllQuery方法表示查es的全部文档,不需要条件
- boolQuery.must(QueryBuilders.matchAllQuery());
- } else {
- //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
- boolQuery.must(QueryBuilders.matchQuery("all", key));
- }
-
- //【条件过滤】
- //城市,term精确查找,注意判空
- if(params.getCity() != null && !params.getCity().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
- }
- //品牌,term精确查找,注意判空
- if(params.getBrand() != null && !params.getBrand().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
- }
- //星级,term精确查找,注意判空
- if(params.getStarName() != null && !params.getStarName().equals("")){
- //注意下面那行的是starName,不要写成startName
- boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
- }
- //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
- if(params.getMinPrice() != null && params.getMaxPrice() != null){
- boolQuery.filter(QueryBuilders
- .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
- }
-
-
- //这一步必须有
- request.source().query(boolQuery);
-
-
- //【分页功能】
- int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
- int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
- request.source().from((page - 1) * size).size(size);
-
-
- //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
-
-
- //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
- return handleResponse(response);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- //这个方法就是我们抽取出来的,负责解析的
- private PageResult handleResponse(SearchResponse response) {
- //解析获取到杂乱JSON数据
- SearchHits searchHits = response.getHits();
- //获取总条数
- long total = searchHits.getTotalHits().value;
- System.out.println("共搜索到"+total+"条文档(数据)");
- //获取hits数组
- SearchHit[] hits = searchHits.getHits();
- //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
- List<HotelDoc> hotels = new ArrayList<>();
- for (SearchHit hit : hits) {
- String json = hit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
- hotels.add(hotelDoc);
- }
- //封装返回
- return new PageResult(total,hotels);
- }
- }
第三步: 运行HotelDemoApplication引导类,浏览器访问 http://localhost:8089/
第四步: 解决"四星","五星" 无法作为条件进行查询的问题
需求: 实现前端页面点击定位后,会将你所在的位置发送给后台,前端的请求信息如下,会向后端发送location参数。
如果谷歌浏览器发送不了位置请求的话,建议临时换成火狐浏览器
分析:
1、修改RequestParams参数,接收来自前端的location字段
2、修改HotelService类的search方法的业务逻辑,如果location有值,就添加根据geo_distance排序的功能
java代码实现距离排序,对应的DSL语句如下
第一步: 把RequestParams类,修改为如下
- package cn.itcast.hotel.pojo;
-
- import lombok.Data;
-
-
- @Data
- public class RequestParams {
- //搜索关键字
- private String key;
- //当前页码
- private Integer page;
- //每页大小
- private Integer size;
- //将来的排序字段
- private String sortBy;
-
- //城市
- private String city;
- //品牌
- private String brand;
- //星级
- private String starName;
- //价格最小值
- private Integer minPrice;
- //价格最大值
- private Integer maxPrice;
-
- //地理位置查询的字段,前端会把location传给我们
- private String location;
- }
第二步: 把HotelDoc类,修改为如下
- package cn.itcast.hotel.pojo;
-
- import lombok.Data;
- import lombok.NoArgsConstructor;
-
- @Data
- @NoArgsConstructor
- public class HotelDoc {
- private Long id;
- private String name;
- private String address;
- private Integer price;
- private Integer score;
- private String brand;
- private String city;
- private String starName;
- private String business;
- private String location;
- private String pic;
-
- //地理位置查询相关的字段。distance字段用于保存解析后的距离值
- private Object distance;
-
- public HotelDoc(Hotel hotel) {
- this.id = hotel.getId();
- this.name = hotel.getName();
- this.address = hotel.getAddress();
- this.price = hotel.getPrice();
- this.score = hotel.getScore();
- this.brand = hotel.getBrand();
- this.city = hotel.getCity();
- this.starName = hotel.getStarName();
- this.business = hotel.getBusiness();
- this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
- this.pic = hotel.getPic();
- }
- }
第三步: 把HotelService类,修改为如下
- package cn.itcast.hotel.service.impl;
-
- import cn.itcast.hotel.mapper.HotelMapper;
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.HotelDoc;
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import cn.itcast.hotel.service.IHotelService;
- import com.alibaba.fastjson.JSON;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.common.geo.GeoPoint;
- import org.elasticsearch.common.unit.DistanceUnit;
- import org.elasticsearch.index.query.BoolQueryBuilder;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.search.SearchHit;
- import org.elasticsearch.search.SearchHits;
- import org.elasticsearch.search.sort.SortBuilders;
- import org.elasticsearch.search.sort.SortOrder;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
-
- @Service
- public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
-
- @Autowired
- //注入在引导类声明好的@Bean
- private RestHighLevelClient client;
-
-
- @Override
- public PageResult search(RequestParams params) {
-
- try {
- //准备Request对象,要查询哪个索引库,
- SearchRequest request = new SearchRequest("hotel");
-
- //【构建BooleanQuery】
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-
- //【关键字搜索】
- //判断前端传的key是否为空,避免空指针
- String key = params.getKey();//前端传过来的搜索关键字
- //用的是must精确查找
- if (key == null || "".equals(key)) {
- //matchAllQuery方法表示查es的全部文档,不需要条件
- boolQuery.must(QueryBuilders.matchAllQuery());
- } else {
- //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
- boolQuery.must(QueryBuilders.matchQuery("all", key));
- }
-
- //【条件过滤】
- //城市,term精确查找,注意判空
- if(params.getCity() != null && !params.getCity().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
- }
- //品牌,term精确查找,注意判空
- if(params.getBrand() != null && !params.getBrand().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
- }
- //星级,term精确查找,注意判空
- if(params.getStarName() != null && !params.getStarName().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
- }
- //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
- if(params.getMinPrice() != null && params.getMaxPrice() != null){
- boolQuery.filter(QueryBuilders
- .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
- }
-
-
- //这一步必须有
- request.source().query(boolQuery);
-
-
- //【分页功能】
- int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
- int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
- request.source().from((page - 1) * size).size(size);
-
-
- //【地理排序功能】
- String location = params.getLocation();
- //对前端传的location进行判断是否为空
- if (location != null && !location.equals("")){
- //sort排序,指定是geoDistanceSort地理坐标排序,要排序的字段是location,中心点是new GeoPoint(location)
- request.source().sort(SortBuilders
- .geoDistanceSort("location",new GeoPoint(location))
- .order(SortOrder.ASC) //升序排序
- .unit(DistanceUnit.KILOMETERS) //地理坐标的单位
- );
- }
-
-
- //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
-
-
- //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
- return handleResponse(response);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- //这个方法就是我们抽取出来的,负责解析的
- private PageResult handleResponse(SearchResponse response) {
- //解析获取到杂乱JSON数据
- SearchHits searchHits = response.getHits();
- //获取总条数
- long total = searchHits.getTotalHits().value;
- System.out.println("共搜索到"+total+"条文档(数据)");
- //获取hits数组
- SearchHit[] hits = searchHits.getHits();
- //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
- List<HotelDoc> hotels = new ArrayList<>();
- for (SearchHit hit : hits) {
- String json = hit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
-
- //【地理坐标查询的解析】,通过getSortValues方法来获取排序值,得到的是多个值也就是数组,我们只需要一个值
- Object[] sortValues = hit.getSortValues();
- //判断是否为空
- if (sortValues.length>0){
- Object sortValue = sortValues[0];
- //把拿到的sortValue返回到页面,也就是需要把sortValue值放到HoteDoc
- hotelDoc.setDistance(sortValue);
- }
-
- hotels.add(hotelDoc);
- }
- //封装返回
- return new PageResult(total,hotels);
- }
- }
第四步: 重启HotelDemoApplication引导类,浏览器访问http://localhost:8089/。点击定位按钮,查看是否能查询出距离自己最近的酒店并显示米数
需求: 让指定的酒店在搜索结果中排名置顶。我们给需要置顶的酒店文档添加一个标记。然后利用function score给带有标记的文档增加权重
分析:
1、给HotelDoc类添加isAD字段,Boolean类型
2、挑选几个你喜欢的酒店,给它的文档数据添加isAD字段,值为true
3、修改HotelService类的search方法,添加function score功能,给isAD值为true的酒店增加权重
Function Score查询可以控制文档的相关性算分,java代码以及对应DSL语句如下图
第一步: 把HotelDoc类,修改为如下
- package cn.itcast.hotel.pojo;
-
- import lombok.Data;
- import lombok.NoArgsConstructor;
-
- @Data
- @NoArgsConstructor
- public class HotelDoc {
- private Long id;
- private String name;
- private String address;
- private Integer price;
- private Integer score;
- private String brand;
- private String city;
- private String starName;
- private String business;
- private String location;
- private String pic;
-
- //地理位置查询相关的字段。distance字段用于保存解析后的距离值
- private Object distance;
-
- //用于广告置顶的字段
- private Boolean isAD;
-
- public HotelDoc(Hotel hotel) {
- this.id = hotel.getId();
- this.name = hotel.getName();
- this.address = hotel.getAddress();
- this.price = hotel.getPrice();
- this.score = hotel.getScore();
- this.brand = hotel.getBrand();
- this.city = hotel.getCity();
- this.starName = hotel.getStarName();
- this.business = hotel.getBusiness();
- this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
- this.pic = hotel.getPic();
- }
- }
第二步: 使用DSL语句为索引库增加字段,由于使用DSL语句需要在浏览器使用kibana,所以我们把docker里面的kibana容器运行一下
docker restart kibana #启动kibana容器
第三步: 启动kibana之后,浏览器访问 http://你的ip地址:5601
第四步: DSL语句,表示给某个id文档添加新字段。id不一定要跟我一样,随便去mysql数据库找几个id就行
- POST /hotel/_update/1557997004
- {
- "doc":{
- "isAD": true
- }
- }
- POST /hotel/_update/1406627919
- {
- "doc":{
- "isAD": true
- }
- }
第五步: 把HotelService类,修改为如下
- package cn.itcast.hotel.service.impl;
-
- import cn.itcast.hotel.mapper.HotelMapper;
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.HotelDoc;
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import cn.itcast.hotel.service.IHotelService;
- import com.alibaba.fastjson.JSON;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.common.geo.GeoPoint;
- import org.elasticsearch.common.unit.DistanceUnit;
- import org.elasticsearch.index.query.BoolQueryBuilder;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
- import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
- import org.elasticsearch.search.SearchHit;
- import org.elasticsearch.search.SearchHits;
- import org.elasticsearch.search.sort.SortBuilders;
- import org.elasticsearch.search.sort.SortOrder;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
-
- @Service
- public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
-
- @Autowired
- //注入在引导类声明好的@Bean
- private RestHighLevelClient client;
-
-
- @Override
- public PageResult search(RequestParams params) {
-
- try {
- //准备Request对象,要查询哪个索引库,
- SearchRequest request = new SearchRequest("hotel");
-
- //【构建BooleanQuery,下面那行的boolQuery是原始查询】
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-
- //【关键字搜索】
- //判断前端传的key是否为空,避免空指针
- String key = params.getKey();//前端传过来的搜索关键字
- //用的是must精确查找
- if (key == null || "".equals(key)) {
- //matchAllQuery方法表示查es的全部文档,不需要条件
- boolQuery.must(QueryBuilders.matchAllQuery());
- } else {
- //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
- boolQuery.must(QueryBuilders.matchQuery("all", key));
- }
-
- //【条件过滤】
- //城市,term精确查找,注意判空
- if(params.getCity() != null && !params.getCity().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
- }
- //品牌,term精确查找,注意判空
- if(params.getBrand() != null && !params.getBrand().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
- }
- //星级,term精确查找,注意判空
- if(params.getStarName() != null && !params.getStarName().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
- }
- //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
- if(params.getMinPrice() != null && params.getMaxPrice() != null){
- boolQuery.filter(QueryBuilders
- .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
- }
-
- //【构建functionScoreQuery,实现算分查询。对应的是广告置顶功能】
- FunctionScoreQueryBuilder functionScoreQuery =
- //原始查询,需要进行相关性算分的查询
- QueryBuilders.functionScoreQuery(boolQuery,
- //function score的数组,里面有很多function score
- new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
- //一个具体的function score
- new FunctionScoreQueryBuilder.FilterFunctionBuilder(
- //过滤,简单说就是满足isAD字段为true的文档就会参与算分
- QueryBuilders.termQuery("isAD",true),
- //要使用什么算分函数,下面那行使用的是weightFactorFunction加权算分,最终score分数越大,排名就越前
- ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10
- )
- });
-
-
- //这一步必须有
- request.source().query(functionScoreQuery);
-
-
- //【分页功能】
- int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
- int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
- request.source().from((page - 1) * size).size(size);
-
-
- //【地理排序功能】
- String location = params.getLocation();
- //对前端传的location进行判断是否为空
- if (location != null && !location.equals("")){
- //sort排序,指定是geoDistanceSort地理坐标排序,要排序的字段是location,中心点是new GeoPoint(location)
- request.source().sort(SortBuilders
- .geoDistanceSort("location",new GeoPoint(location))
- .order(SortOrder.ASC) //升序排序
- .unit(DistanceUnit.KILOMETERS) //地理坐标的单位
- );
- }
-
-
- //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
-
-
- //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
- return handleResponse(response);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- //这个方法就是我们抽取出来的,负责解析的
- private PageResult handleResponse(SearchResponse response) {
- //解析获取到杂乱JSON数据
- SearchHits searchHits = response.getHits();
- //获取总条数
- long total = searchHits.getTotalHits().value;
- System.out.println("共搜索到"+total+"条文档(数据)");
- //获取hits数组
- SearchHit[] hits = searchHits.getHits();
- //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
- List<HotelDoc> hotels = new ArrayList<>();
- for (SearchHit hit : hits) {
- String json = hit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
-
- //【地理坐标查询的解析】,通过getSortValues方法来获取排序值,得到的是多个值也就是数组,我们只需要一个值
- Object[] sortValues = hit.getSortValues();
- //判断是否为空
- if (sortValues.length>0){
- Object sortValue = sortValues[0];
- //把拿到的sortValue返回到页面,也就是需要把sortValue值放到HoteDoc
- hotelDoc.setDistance(sortValue);
- }
-
- hotels.add(hotelDoc);
- }
- //封装返回
- return new PageResult(total,hotels);
- }
- }
第六步: 重启HotelDemoApplication引导类,浏览器访问http://localhost:8089/。查看我们指定的那两个酒店是否置顶
高亮API包括请求DSL构建和结果解析两部分,API和对应的DSL语句如下图,下图只是构建,再下面还有解析,高亮必须由构建+解析才能实现
解析,如下图
第一步: 把HotelService类,修改为如下
- package cn.itcast.hotel.service.impl;
-
- import cn.itcast.hotel.mapper.HotelMapper;
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.HotelDoc;
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import cn.itcast.hotel.service.IHotelService;
- import com.alibaba.fastjson.JSON;
- import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.common.geo.GeoPoint;
- import org.elasticsearch.common.unit.DistanceUnit;
- import org.elasticsearch.index.query.BoolQueryBuilder;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
- import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
- import org.elasticsearch.search.SearchHit;
- import org.elasticsearch.search.SearchHits;
- import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
- import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
- import org.elasticsearch.search.sort.SortBuilders;
- import org.elasticsearch.search.sort.SortOrder;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
-
- @Service
- public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
-
- @Autowired
- //注入在引导类声明好的@Bean
- private RestHighLevelClient client;
-
-
- @Override
- public PageResult search(RequestParams params) {
-
- try {
- //准备Request对象,要查询哪个索引库,
- SearchRequest request = new SearchRequest("hotel");
-
- //【构建BooleanQuery,下面那行的boolQuery是原始查询】
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-
- //【关键字搜索】
- //判断前端传的key是否为空,避免空指针
- String key = params.getKey();//前端传过来的搜索关键字
- //用的是must精确查找
- if (key == null || "".equals(key)) {
- //matchAllQuery方法表示查es的全部文档,不需要条件
- boolQuery.must(QueryBuilders.matchAllQuery());
- } else {
- //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
- boolQuery.must(QueryBuilders.matchQuery("all", key));
- }
-
- //【条件过滤】
- //城市,term精确查找,注意判空
- if(params.getCity() != null && !params.getCity().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
- }
- //品牌,term精确查找,注意判空
- if(params.getBrand() != null && !params.getBrand().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
- }
- //星级,term精确查找,注意判空
- if(params.getStarName() != null && !params.getStarName().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
- }
- //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
- if(params.getMinPrice() != null && params.getMaxPrice() != null){
- boolQuery.filter(QueryBuilders
- .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
- }
-
- //【构建functionScoreQuery,实现算分查询。对应的是广告置顶功能】
- FunctionScoreQueryBuilder functionScoreQuery =
- //原始查询,需要进行相关性算分的查询
- QueryBuilders.functionScoreQuery(boolQuery,
- //function score的数组,里面有很多function score
- new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
- //一个具体的function score
- new FunctionScoreQueryBuilder.FilterFunctionBuilder(
- //过滤,简单说就是满足isAD字段为true的文档就会参与算分
- QueryBuilders.termQuery("isAD",true),
- //要使用什么算分函数,下面那行使用的是weightFactorFunction加权算分,最终score分数越大,排名就越前
- ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10
- )
- });
-
-
- //【高亮显示】对查询出来的文档,的特定字段进行高亮显示
- request.source().highlighter(new HighlightBuilder().field("all").requireFieldMatch(true).preTags("<em>").postTags("</em>"));
-
-
- //这一步必须有
- request.source().query(functionScoreQuery);
-
-
- //【分页功能】
- int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
- int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
- request.source().from((page - 1) * size).size(size);
-
-
- //【地理排序功能】
- String location = params.getLocation();
- //对前端传的location进行判断是否为空
- if (location != null && !location.equals("")){
- //sort排序,指定是geoDistanceSort地理坐标排序,要排序的字段是location,中心点是new GeoPoint(location)
- request.source().sort(SortBuilders
- .geoDistanceSort("location",new GeoPoint(location))
- .order(SortOrder.ASC) //升序排序
- .unit(DistanceUnit.KILOMETERS) //地理坐标的单位
- );
- }
-
-
- //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
-
-
- //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
- return handleResponse(response);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- //这个方法就是我们抽取出来的,负责解析的
- private PageResult handleResponse(SearchResponse response) {
- //解析获取到杂乱JSON数据
- SearchHits searchHits = response.getHits();
- //获取总条数
- long total = searchHits.getTotalHits().value;
- System.out.println("共搜索到"+total+"条文档(数据)");
- //获取hits数组
- SearchHit[] hits = searchHits.getHits();
- //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
- List<HotelDoc> hotels = new ArrayList<>();
- for (SearchHit hit : hits) {
- String json = hit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
-
- //【地理坐标查询的解析】,通过getSortValues方法来获取排序值,得到的是多个值也就是数组,我们只需要一个值
- Object[] sortValues = hit.getSortValues();
- //判断是否为空
- if (sortValues.length>0){
- Object sortValue = sortValues[0];
- //把拿到的sortValue返回到页面,也就是需要把sortValue值放到HoteDoc
- hotelDoc.setDistance(sortValue);
- }
-
- //【解析】获取高亮结果
- Map<String, HighlightField> xxhighlightFields = hit.getHighlightFields();
- //使用CollectionUtils工具类,进行判空,避免空指针
- if (!CollectionUtils.isEmpty(xxhighlightFields)){
- //根据字段名获取高亮结果
- HighlightField xxhighlightField = xxhighlightFields.get("all");
- //判断name不为空
- if (xxhighlightField != null) {
- //获取高亮值
- String xxname = xxhighlightField.getFragments()[0].string();
- //覆盖非高亮结果
- hotelDoc.setName(xxname);
- }
- }
-
- hotels.add(hotelDoc);
- }
- //封装返回
- return new PageResult(total,hotels);
- }
- }
第二步: 重启HotelDemoApplication引导类,浏览器访问http://localhost:8089/。查看是否能将搜索词高亮显示
写好的项目: 文件下载-奶牛快传 Download |CowTransfer
官方文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
聚合: 可以实现对文档数据的统计、分析、运算。聚合常见的有如下三类。注意,聚合的字段必然是不分词的,原因: 聚合不能是text类型
●桶 (Bucket) 聚合: 用来对文档做分组
●度量 (Metric) 聚合: 用以计算一些值,比如最大值、最小值、平均值
●管道 (Pipeline) 聚合: 其它聚合的结果为基础做聚合。这种用的不多
Bucket聚合,也就是桶聚合
现在,我们要统计所有数据中的酒店品牌有多少种,此时我们可以根据酒店品牌的名称做聚合,由于品牌是字段,也就是要对字段值做分组,采用的是TermAggregation聚合,类型为term类型,DSL示例如下
确保你的环境正常启动
浏览器访问 http://你的ip地址:5601
然后我们使用的索引库是hotel,没有这个索引库的话,可以去前面 '实用篇-ES-黑马旅游案例' 的 '5. 环境准备-同步数据' 进行索引库的创建和添加数据
第一步: 具体操作,浏览器输入如下,表示对不同的品牌进行聚合,也就是不同的品牌为不同的桶,相同的品牌放进一个桶里面
第二步: 如何修改默认的排序规则,我们不希望是按照找出来的文档总条数降序排序。默认情况下,Bucket会统计Bucket内的文档数量,记为_count,并且按照count降序排序。我们如果要修改结果排序方式的话,只需要加一个order属性,如下
第三步: 我们上面是对整个索引库的数据做聚合搜索,如果索引库本身有庞大数据的话,对整个索引库的聚合搜索是对内存消耗非常大,我们希望自定义聚合的搜索范围,也就是限定要聚合的文档范围,只需要添加query条件即可,如下
gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
Metrics聚合,也就是度量聚合。例如,我们要求获取每个品牌的用户评分的min最小值、max最大值、avg平均值。注意不是整个索引库的所有酒店(文档)进行求值,所以要结合上一节的Bucket聚合一起使用
确保你的环境正常启动
浏览器访问 http://你的ip地址:5601
第一步: 具体操作,浏览器输入如下,表示对品牌(父聚合)的评分(子聚合)进行求值
第二步: 如果我们还需要对结果按照评分的平均值,再去做个排序,看一下哪个酒店评价最高,注意我们是在同里面做排序,也就是排序要写在terms里面
确保你的环境正常启动
如何在java代码使用RestClient来实现聚合。java代码以及对应的DSL语句如下图
请求,得到的是json数据
解析,对聚合结果的json数据进行解析
具体操作: 是基于之前的hotel-demo项目上继续编写,前面学的 '实用篇-ES-RestClient查询文档' 的基础上进行编写
第一步: 在HotelSearchTest类,添加如下
- @Test
- void testAggregation() throws IOException {
-
- //准备Request对象
- SearchRequest xxrequest = new SearchRequest("hotel");
-
- //准备DSL。设置size
- xxrequest.source().size(0);
- //准备DSL。聚合语句
- xxrequest.source().aggregation(AggregationBuilders
- .terms("BrandAggMyName") //自定义聚合名称为BrandAggMyName
- .field("brand")
- .size(10)
- );
-
- //发出请求
- SearchResponse xxresponse = xxclient.search(xxrequest, RequestOptions.DEFAULT);
-
- //【解析】
- //解析聚合结果
- Aggregations xxaggregations = xxresponse.getAggregations();
- //根据聚合名称获取聚合结果
- Terms xxbrandTerms = xxaggregations.get("BrandAggMyName");
- //获取桶(buckets),获取的是一个集合
- List<? extends Terms.Bucket> xxbuckets = xxbrandTerms.getBuckets();
- //遍历集合,取出每一个bucket
- for (Terms.Bucket xxbucket : xxbuckets) {
- //获取key。这个key就是品牌信息
- String key = xxbucket.getKeyAsString();
- System.out.println(key);
- }
- }
第二步: 运行testAggregation方法
案例: 在前面的 '黑马路由案例' 中,搜索页面的品牌、城市等信息不应该是在页面写死,而是通过聚合索引库中的酒店数据得来的
确保你的环境正常启动
第一步: 在IHotelService接口添加如下
- /**
- * 查询城市、星级、品牌的聚合结果
- * @return 聚合结果,格式:{"城市": ["上海", "北京"], "品牌": ["如家", "希尔顿"]}
- */
- //在给出来的案例图中,左侧加深的字是key,右侧浅灰的字是value,并且右侧的value有多个值。所以我们使用Map集合,并且value是list集合
- Map<String, List<String>> xxfilters();
第二步: 在HotelService实现类添加如下
- @Override
- public Map<String, List<String>> xxfilters() {
- try {
- //准备Request对象
- SearchRequest xxrequest = new SearchRequest("hotel");
-
- //准备DSL。设置size
- xxrequest.source().size(0);
- //准备DSL。聚合语句。对多个字段进行聚合
- buildAggregation(xxrequest);
-
- //发出请求
- SearchResponse xxresponse = client.search(xxrequest, RequestOptions.DEFAULT);
-
- //【解析】
- //解析聚合结果
- Map<String, List<String>> yyresult = new HashMap<>();
- Aggregations xxaggregations = xxresponse.getAggregations();
- //1、根据名称获取品牌的结果
- List<String> xxbrandList = getAggByName(xxaggregations,"BrandAggMyName");
- //把品牌的结果信息放入map
- yyresult.put("品牌",xxbrandList);
- //2、根据名称获取城市的结果
- List<String> xxcityList = getAggByName(xxaggregations,"cityAggMyName");
- //把城市的结果信息放入map
- yyresult.put("城市",xxcityList);
- //3、根据名称获取星级的结果
- List<String> xxstarList = getAggByName(xxaggregations,"starAggMyName");
- //把星级的结果信息放入map
- yyresult.put("星级",xxstarList);
- //返回yyresult
- return yyresult;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private List<String> getAggByName(Aggregations xxaggregations,String kkaggName) {
- //根据聚合名称获取聚合结果
- Terms xxbrandTerms = xxaggregations.get(kkaggName);
- //获取桶(buckets),获取的是一个集合
- List<? extends Terms.Bucket> xxbuckets = xxbrandTerms.getBuckets();
- //遍历xxbuckets集合,取出每一个key。把取到的key放到xxbrandList集合
- List<String> xxbrandList = new ArrayList<>();
- for (Terms.Bucket xxbucket : xxbuckets) {
- //获取key。这个key就是品牌信息
- String xxkey = xxbucket.getKeyAsString();
- xxbrandList.add(xxkey);
- }
- return xxbrandList;
- }
-
- //把聚合的代码抽取出来
- private void buildAggregation(SearchRequest xxrequest) {
- xxrequest.source().aggregation(AggregationBuilders
- .terms("BrandAggMyName") //自定义聚合名称为BrandAggMyName
- .field("brand")
- .size(100)//聚合结果限制
- );
- xxrequest.source().aggregation(AggregationBuilders
- .terms("cityAggMyName")
- .field("city")
- .size(100)
- );
- xxrequest.source().aggregation(AggregationBuilders
- .terms("starAggMyName")
- .field("starName")
- .size(100)
- );
- }
抽取代码成为方法
第三步: 在HotelDemoApplicationTests添加如下。并运行contextLoads方法
- @Autowired
- private IHotelService hotelService;
-
- @Test
- void contextLoads() {
- Map<String, List<String>> filters = hotelService.xxfilters();
- System.out.println(filters);
- }
对接前端接口,也就是把上面 '5. 多条件聚合' 实现的功能返回到前端页面,达到最终效果
前端页面会向服务端发起请求,查询品牌、城市、星级字段的聚合结果
确保你的环境正常启动
首先,保证你已经学完前面的 '实用篇-ES-黑马旅游案例' 并且在浏览器能打开前端页面
点击页面的搜索,打开浏览器控制台看一下前端向后端请求的参数
分析:
1、可以看到请求参数与之前search时的RequestParam完全一致,这是在限定聚合时的文档范围。用户搜索“外滩”,价格在300~600,那聚合必须是在这个搜索条件基础上完成
2、编写controller接口,接收该请求
3、修改IUserService#getFilters()方法,添加RequestParam参数
3、修改getFilters方法的业务,聚合时添加query条件
具体操作如下
第一步: 把HotelDemoApplicationTests类注释掉
第二步: 把IHotelService接口修改为如下,原本xxfilters方法是没有参数的,现在我们要给xxfilters方法添加参数
- package cn.itcast.hotel.service;
-
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import com.baomidou.mybatisplus.extension.service.IService;
- import java.util.List;
- import java.util.Map;
-
- public interface IHotelService extends IService<Hotel> {
- PageResult search(RequestParams params);
-
- /**
- * 查询城市、星级、品牌的聚合结果
- * @return 聚合结果,格式:{"城市": ["上海", "北京"], "品牌": ["如家", "希尔顿"]}
- */
- //在给出来的案例图中,左侧加深的字是key,右侧浅灰的字是value,并且右侧的value有多个值。所以我们使用Map集合,并且value是list集合
- Map<String, List<String>> xxfilters(RequestParams params);
- }
第三步: 把HotelService实现类修改为如下
- package cn.itcast.hotel.service.impl;
-
- import cn.itcast.hotel.mapper.HotelMapper;
- import cn.itcast.hotel.pojo.Hotel;
- import cn.itcast.hotel.pojo.HotelDoc;
- import cn.itcast.hotel.pojo.PageResult;
- import cn.itcast.hotel.pojo.RequestParams;
- import cn.itcast.hotel.service.IHotelService;
- import com.alibaba.fastjson.JSON;
- import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.apache.http.HttpHost;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.common.geo.GeoPoint;
- import org.elasticsearch.common.unit.DistanceUnit;
- import org.elasticsearch.index.query.BoolQueryBuilder;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
- import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
- import org.elasticsearch.search.SearchHit;
- import org.elasticsearch.search.SearchHits;
- import org.elasticsearch.search.aggregations.AggregationBuilders;
- import org.elasticsearch.search.aggregations.Aggregations;
- import org.elasticsearch.search.aggregations.bucket.terms.Terms;
- import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
- import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
- import org.elasticsearch.search.sort.SortBuilders;
- import org.elasticsearch.search.sort.SortOrder;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- @Service
- public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
-
- @Autowired
- //注入在引导类声明好的@Bean
- private RestHighLevelClient client;
-
-
- @Override
- public PageResult search(RequestParams params) {
-
- try {
- //准备Request对象,要查询哪个索引库,
- SearchRequest request = new SearchRequest("hotel");
- buildBasicQuery(params, request);
-
-
- //【分页功能】
- int page = params.getPage();//前端传过来的当前页面值,注意为了参与运算,我们将原来的Integer类型拆箱为int类型,不用包装类
- int size = params.getSize();//前端传过来的每页大小。其实拆箱就是把默认的包装类类型,改成基本类型
- request.source().from((page - 1) * size).size(size);
-
-
- //【地理排序功能】
- String location = params.getLocation();
- //对前端传的location进行判断是否为空
- if (location != null && !location.equals("")){
- //sort排序,指定是geoDistanceSort地理坐标排序,要排序的字段是location,中心点是new GeoPoint(location)
- request.source().sort(SortBuilders
- .geoDistanceSort("location",new GeoPoint(location))
- .order(SortOrder.ASC) //升序排序
- .unit(DistanceUnit.KILOMETERS) //地理坐标的单位
- );
- }
-
-
- //发送请求。下面那行的search报红线,我们不能抛出,要捕获一下
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
-
-
- //调用下面抽取后的方法,我们是把解析的代码抽取出去了。并把解析作为结果返回
- return handleResponse(response);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private SearchRequest buildBasicQuery(RequestParams params,SearchRequest request) {
-
- //【构建BooleanQuery,下面那行的boolQuery是原始查询】
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-
- //【关键字搜索】
- //判断前端传的key是否为空,避免空指针
- String key = params.getKey();//前端传过来的搜索关键字
- //用的是must精确查找
- if (key == null || "".equals(key)) {
- //matchAllQuery方法表示查es的全部文档,不需要条件
- boolQuery.must(QueryBuilders.matchAllQuery());
- } else {
- //matchQuery表示按照分词查询es的文档,需要条件,这个条件我们进行了判空
- boolQuery.must(QueryBuilders.matchQuery("all", key));
- }
-
- //【条件过滤】
- //城市,term精确查找,注意判空
- if(params.getCity() != null && !params.getCity().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
- }
- //品牌,term精确查找,注意判空
- if(params.getBrand() != null && !params.getBrand().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
- }
- //星级,term精确查找,注意判空
- if(params.getStarName() != null && !params.getStarName().equals("")){
- boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
- }
- //价格,range范围过滤,注意判空。gt表示大于,gte表示大于等于,lt表示小于,lte表示小于等于
- if(params.getMinPrice() != null && params.getMaxPrice() != null){
- boolQuery.filter(QueryBuilders
- .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
- }
-
- //【构建functionScoreQuery,实现算分查询。对应的是广告置顶功能】
- FunctionScoreQueryBuilder functionScoreQuery =
- //原始查询,需要进行相关性算分的查询
- QueryBuilders.functionScoreQuery(boolQuery,
- //function score的数组,里面有很多function score
- new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
- //一个具体的function score
- new FunctionScoreQueryBuilder.FilterFunctionBuilder(
- //过滤,简单说就是满足isAD字段为true的文档就会参与算分
- QueryBuilders.termQuery("isAD",true),
- //要使用什么算分函数,下面那行使用的是weightFactorFunction加权算分,最终score分数越大,排名就越前
- ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10
- )
- });
-
-
- //【高亮显示】对查询出来的文档,的特定字段进行高亮显示
- request.source().highlighter(new HighlightBuilder().field("all").requireFieldMatch(true).preTags("<em>").postTags("</em>"));
-
-
- //这一步必须有
- request.source().query(functionScoreQuery);
- return request;
- }
-
- //这个方法就是我们抽取出来的,负责解析的
- private PageResult handleResponse(SearchResponse response) {
- //解析获取到杂乱JSON数据
- SearchHits searchHits = response.getHits();
- //获取总条数
- long total = searchHits.getTotalHits().value;
- System.out.println("共搜索到"+total+"条文档(数据)");
- //获取hits数组
- SearchHit[] hits = searchHits.getHits();
- //遍历数组,把hits数组的每个source取出来,把遍历到的每条数据添加到lisi集合
- List<HotelDoc> hotels = new ArrayList<>();
- for (SearchHit hit : hits) {
- String json = hit.getSourceAsString();
- //此时可以直接打印,也可以使用fastjson工具类进行反序列化,从而转为HotelDoc类型,HotelDoc类是我们写的实体类
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
-
- //【地理坐标查询的解析】,通过getSortValues方法来获取排序值,得到的是多个值也就是数组,我们只需要一个值
- Object[] sortValues = hit.getSortValues();
- //判断是否为空
- if (sortValues.length>0){
- Object sortValue = sortValues[0];
- //把拿到的sortValue返回到页面,也就是需要把sortValue值放到HoteDoc
- hotelDoc.setDistance(sortValue);
- }
-
- //【解析】获取高亮结果
- Map<String, HighlightField> xxhighlightFields = hit.getHighlightFields();
- //使用CollectionUtils工具类,进行判空,避免空指针
- if (!CollectionUtils.isEmpty(xxhighlightFields)){
- //根据字段名获取高亮结果
- HighlightField xxhighlightField = xxhighlightFields.get("all");
- //判断name不为空
- if (xxhighlightField != null) {
- //获取高亮值
- String xxname = xxhighlightField.getFragments()[0].string();
- //覆盖非高亮结果
- hotelDoc.setName(xxname);
- }
- }
-
- hotels.add(hotelDoc);
- }
- //封装返回
- return new PageResult(total,hotels);
- }
-
-
-
- //--------------------------------------------下面是多条件聚合-----------------------------------------
-
- @Override
- public Map<String, List<String>> xxfilters(RequestParams params) {
- try {
- //准备Request对象
- SearchRequest xxrequest = new SearchRequest("hotel");
-
- //添加query查询信息,也就是限定聚合的范围
- buildBasicQuery(params, xxrequest);
- //准备DSL。设置size
- xxrequest.source().size(0);
- //准备DSL。聚合语句。对多个字段进行聚合
- buildAggregation(xxrequest);
-
- //发出请求
- SearchResponse xxresponse = client.search(xxrequest, RequestOptions.DEFAULT);
-
- //【解析】
- //解析聚合结果
- Map<String, List<String>> yyresult = new HashMap<>();
- Aggregations xxaggregations = xxresponse.getAggregations();
- //1、根据名称获取品牌的结果
- List<String> xxbrandList = getAggByName(xxaggregations,"BrandAggMyName");
- //把品牌的结果信息放入map
- yyresult.put("brand",xxbrandList);
- //2、根据名称获取城市的结果
- List<String> xxcityList = getAggByName(xxaggregations,"cityAggMyName");
- //把城市的结果信息放入map
- yyresult.put("city",xxcityList);
- //3、根据名称获取星级的结果
- List<String> xxstarList = getAggByName(xxaggregations,"starAggMyName");
- //把星级的结果信息放入map
- yyresult.put("starName",xxstarList);
- //返回yyresult
- return yyresult;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private List<String> getAggByName(Aggregations xxaggregations,String kkaggName) {
- //根据聚合名称获取聚合结果
- Terms xxbrandTerms = xxaggregations.get(kkaggName);
- //获取桶(buckets),获取的是一个集合
- List<? extends Terms.Bucket> xxbuckets = xxbrandTerms.getBuckets();
- //遍历xxbuckets集合,取出每一个key。把取到的key放到xxbrandList集合
- List<String> xxbrandList = new ArrayList<>();
- for (Terms.Bucket xxbucket : xxbuckets) {
- //获取key。这个key就是品牌信息
- String xxkey = xxbucket.getKeyAsString();
- xxbrandList.add(xxkey);
- }
- return xxbrandList;
- }
-
- //把聚合的代码抽取出来
- private void buildAggregation(SearchRequest xxrequest) {
- xxrequest.source().aggregation(AggregationBuilders
- .terms("BrandAggMyName") //自定义聚合名称为BrandAggMyName
- .field("brand")
- .size(100)//聚合结果限制
- );
- xxrequest.source().aggregation(AggregationBuilders
- .terms("cityAggMyName")
- .field("city")
- .size(100)
- );
- xxrequest.source().aggregation(AggregationBuilders
- .terms("starAggMyName")
- .field("starName")
- .size(100)
- );
- }
-
- //--------------------------------------------上面是多条件聚合-----------------------------------------
-
- }
第四步: 重新运行HotelDemoApplication引导类,浏览器查看是否功能正常
elasticsearch的拼音分词插件的官方地址: https://www.wpsshop.cn/w/Monodyee/article/detail/455709
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。