工信部网站备案查不到,自己的电脑做服务器 并建网站,用腾讯云做网站的好处,做网页和做网站的区别实用篇-ES-环境搭建
ES是elasticsearch的简称。我在SpringBoot学习 数据层解决方案 的时候#xff0c;写过一次ES笔记#xff0c;可以结合一起看一下。 之前在SpringBoot里面写的相关ES笔记是基于Windows的#xff0c;现在我们是基于docker容器来使用#xff0c;需要你们提…实用篇-ES-环境搭建
ES是elasticsearch的简称。我在SpringBoot学习 数据层解决方案 的时候写过一次ES笔记可以结合一起看一下。 之前在SpringBoot里面写的相关ES笔记是基于Windows的现在我们是基于docker容器来使用需要你们提前准备好自己的docker容器以及掌握docker操作 常见的分布式搜索的技术如下
1、Elasticsearch: 开源的分布式搜索引擎2、Splunk: 商业项目收费3、Solr: Apache的开源搜索引擎 随着业务发展数据量越来越庞大传统的MySQL数据库难以满足我们的需求所以在微服务架构下一般都会用到一种分布式搜索的技术下面我们会学分布式搜索中最流行的一种也就是elasticsearch的用法。包括学习elasticsearch的概念、安装、使用。其中学习elasticsearch的使用的时候主要通过两个方面一方面是elasticsearch对于索引库(类似于数据库把数据导入进索引库导入的数据就是所谓的文档我们要实现文档的增删改查)的操作另一方面我们还会学习elasticsearch官方提供的Restful的API(也就是Java客户端)来更方便的操作elasticsearch 1. 什么是elasticsearch elasticsearch(读 yī læ sī tǐ kě sè chǐ) kibana (读 kī bā nǎ)elasticsearch是一款非常强大的开源搜索引擎技术可以帮助我们从海量数据中快速找到需要的内容 1、elasticsearch是elastic stack的核心负责存储、搜索、分析数据。我们主要学习这个elasticsearch底层实现是基于Lucene技术 2、Kibana是数据可视化的组件也就是展示搜索出来的数据。elasticsearch的相关技术了解即可 3、Logstash、Beats是负责数据抓取的组件。elasticsearch的相关技术了解即可 Lucene是一个Java语言的搜索引擎类库(其实就是一个jar包)是Apache公司的顶级项目由DougCutting于1999年研发 Lucene官网: https://lucene.apache.org Lucene的优势 1、易扩展 2、高性能 (基于倒排索引) Lucene的缺点 1、只限于Java语言开发 2、学习曲线陡峭也就是API复杂不利于学习 3、不支持水平扩展只负责如何实现搜索不支持高并发、集群扩展 由于Lucene的缺点诞生出了elasticsearch与Lucene相比elasticsearch(基于Lucene且Compass是elasticsearch的前身)具有以下优点 1、支持分布式可水平扩展 2、提供Restful接口可被任何语言调用elasticsearch的核心技术是倒排索引下面会学 2. 倒排索引 传统数据库(例如MySQL)采用正向索引例如给下表(tb_goods)中的id创建索引 elasticsearch采用倒排索引例如给下表(tb_goods)中的id创建索引 总结1、正向索引: 基于文档id来创建索引。查询词条时必须先找到文档而后判断是否包含词条 2、倒排索引: 对文档内容进行分词对词条创建索引并记录词条所在文档的信息。查询时先根据词条去查询文档id然后获取到文档 3. elasticsearch对比mysql elasticsearch elasticsearch是面向文档存储的可以是数据库中的一条商品数据一个订单信息。注意elasticsearch的文档是以json形式存储的也就是说我们把数据(也叫文档)存储进elasticsearch时这些文档数据就会自动被序列化为json格式然后才存储进elasticsearch elasticsearch的索引: 相同类型的文档的集合。索引和映射的概念如下图 下面的表格是介绍elasticsearch中的各个概念以及含义看的时候重点看第二、三列第一列是为了让你更理解第二列的意思所以在第一列拿MySQL的概念来做匹配。例如elasticsearch的Index表示索引也就是文档的集合就相当于MySQL的Table(也就是表) MySQL Elasticsearch 说明 Table Index 索引(index)就是文档的集合类似数据库的表(table) Row Document 文档(Document)就是一条条的数据类似数据库中的行(Row)。这里的文档都是JSON格式 Column Field 字段(Field)就是JSON文档中的字段类似数据库中的列(Column) Schema Mapping Mapping(映射)是索引中文档的约束例如字段类型约束。类似数据库的表结构(Schema) SQL DSL DSL是elasticsearch提供的JSON风格的请求语句用来操作elasticsearch实现CRUD
我们下面会学习映射的创建以及文档的增删改查。这些操作在MySQL里面是通过SQL语句实现但我们在elasticsearch中会使用的是DSL语句来操作。 在elasticsearch中当我们写好DSL语句要通过http请求发给elasticsearchelasticsearch才会响应原因是在elasticsearch对外暴露的是Restful接口 上面基本都是在讲elasticsearch那么是不是elasticsearch已经完全代码MySQL答案并不是两者擅长的事情不一样如下
1、MySQL: 擅长事务类型的操作可以确保数据的安全和一致性。一般用于增删改2、Elasticsearch: 擅长海量数据的搜索、分析、计算。一般用于查询两者是互补关系不是替代关系因此在业务系统架构中两者都会存在让用户在MySQL里面增删改数据然后MySQL把数据同步给elasticsearch用户要查询的时候就在elasticsearch里面进行查询 4. 安装elasticsearch elasticsearch(读 yī læ sī tǐ kě sè chǐ)。注意elasticsearch、kibana、IK分词器这三者通常是一起使用的 注意: 我们学习elasticsearch是基于docker容器来使用需要你们提前准备好自己的docker容器以及掌握docker操作。elasticsearch一般都是搭配kibana(下节会学如何安装)来使用kibana的作用是让我们非常方便的去编写elasticsearch中的DSL语句从而去操作elasticsearch 【安装elasticsearch简称es】 第一步: 创建网络。因为我们还需要部署kibana容器因此需要让es和kibana容器互联 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.typesingle-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.namees-docker-cluster设置集群名称 ●-e http.host0.0.0.0监听的地址可以外网访问 ●-e ES_JAVA_OPTS-Xms512m -Xmx512m内存大小不能低于512 ●-e discovery.typesingle-node运行模式例如非集群模式 ●-v es-data:/usr/share/elasticsearch/data挂载数据卷绑定es的数据目录 ●-v es-logs:/usr/share/elasticsearch/logs挂载数据卷绑定es的日志目录 ●-v es-plugins:/usr/share/elasticsearch/plugins挂载数据卷绑定es的插件目录 ●--privileged授予数据卷访问权 ●--network es-net 加入一个名为es-net的网络中 ●-p 9200:9200端口映射配置向外暴露的http请求端口用于用户访问 ●-p 9300:9300端口映射配置是es容器各个节点之间互相访问的端口由于我们是单节点部署所以用不到 ●elasticsearch:7.12.1: 镜像名称要把哪个镜像创建为容器注意带版本号 然后在浏览器中输入http://你的ip地址:9200 即可看到elasticsearch的响应结果
http://192.168.200.231:9200/ 5. 安装kibana 注意是跟上一节的 4. 安装elasticsearch 一起操作也就是说同一个实验。注意elasticsearch、kibana、IK分词器这三者通常是一起使用的 kibana (读 kī bā nǎ)的作用: 让我们非常方便的去编写elasticsearch中的DSL语句从而去操作elasticsearch(读 yī læ sī tǐ kě sè chǐ) 第一步: 确保docker是启动的 # 启动docker服务
systemctl start docker 第二步: 加载kibana镜像。这个镜像体积有1.04G所以需要在Windows上下载链接安装包下载下来是一个es的镜像tar包然后传到CentOS7的/root目录
es镜像: https://cowtransfer.com/s/1c16f55edf2341 第三步: 把在CentOS7的/root目录的kibana镜像导入到docker
docker load -i kibana.tar 第四步: 创建并运行kibana容器容器名称就叫kibana。在docker(也叫Docker大容器、Docker主机、宿主机)根据kibana镜像来创建kibana容器
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTShttp://es:9200 \
--networkes-net \
-p 5601:5601 \
kibana:7.12.1
# --name: 指定容器的名字例如kibana # --network es-net: 加入一个名为es-net的网络中与elasticsearch在同一个网络中 # -e ELASTICSEARCH_HOSTS: 由于kibana和es会被我们设置在同一个网络所以这里的kibana可以通过容器名直接访问eses的容器名我们在上一节设置的是es # -e ELASTICSEARCH_HOSTS: 设置elasticsearch的地址因为kibana已经与elasticsearch在一个网络因此可以用容器名直接访问elasticsearch # -p 5601:5601: 端口映射配置向外暴露的http请求端口用于用户访问 第五步: kibana启动一般比较慢需要多等待一会可以通过命令
docker logs -f kibana #查看运行日志当查看到下面的日志说明成功 第六步: 测试。在浏览器中输入http://你的ip地址:5601 即可看到elasticsearch的响应结果 注意我们在浏览器写DSL语句的时候是带有提示功能的非常好用 6. 安装IK分词器 IK分词器官网: https://github.com/medcl/elasticsearch-analysis-ik。注意elasticsearch、kibana、IK分词器这三者通常是一起使用的 es在创建倒排索引时需要对文档进行分词。在搜索时需要对用户输入的内容进行分词。但默认的分词规则不支持中文处理默认是只支持对英文进行分词但是在正常业务中我们需要处理的文档大多是中文所以我们需要对中文进行分词所以就需要安装IK分词器 为了直观的体现es的分词规则不支持英文我们可以做下面的小演示如下
#测试分词器
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_smart最少切分根据语义分词正常分词●ik_max_word最细切分也是根据语义分词分的词语更多更细 7. 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 version1.0 encodingUTF-8?
!DOCTYPE properties SYSTEM http://java.sun.com/dtd/properties.dtd
propertiescommentIK Analyzer 扩展配置/comment!--用户可以在这里配置自己的扩展字典--entry keyext_dictext.dic/entry!--用户可以在这里配置自己的扩展停止词字典 *** 添加停用词词典--entry keyext_stopwordsstopword.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分词器包含两种模式
●ik_smart最少切分根据语义分词正常分词●ik_max_word最细切分也是根据语义分词分的词语更多更细 根据上图确实可以根据我们指定的扩展词进行分析违禁词也确实被禁用没有被分词 实用篇-ES-DSL操作文档 1. mapping属性 mapping属性的官方文档: https://elastic.co/guide/en/elasticsearch/reference/current/index.html 下面的表格是介绍elasticsearch中的各个概念以及含义看的时候重点看第二、三列第一列是为了让你更理解第二列的意思所以在第一列拿MySQL的概念来做匹配。例如elasticsearch的Index表示索引也就是文档的集合就相当于MySQL的Table(也就是表) MySQL Elasticsearch 说明 Table Index 索引(index)就是文档的集合类似数据库的表(table) Row Document 文档(Document)就是一条条的数据类似数据库中的行(Row)。这里的文档都是JSON格式 Column Field 字段(Field)就是JSON文档中的字段类似数据库中的列(Column) Schema Mapping Mapping(映射)是索引中文档的约束例如字段类型约束。类似数据库的表结构(Schema) SQL DSL DSL是elasticsearch提供的JSON风格的请求语句用来操作elasticsearch实现CRUD mapping是对索引库中文档(es中的文档是json风格)的约束常见的mapping属性包括如下 ●type: 字段数据类型 ○字符串(分两种): text可分词的文本、keyword精确值例如品牌、国家、ip地址等不可分词的词语 ○数值: long、integer、short、byte、double、float ○布尔: boolean ○日期: date ○对象object ●index: 是否创建倒排索引默认为true(也就是可参与分词搜索)改成false的话别人就搜索不到你 ●analyzer: 分词器当字段类型是text时必须指定分词器。如果字段类型是keyword那么不需要指定分词器 ●properties: 子字段也就是属性和子属性 2. 创建索引库
ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下 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}}}}}
} 3. 查询、修改、删除索引库 具体操作: 首先保证你已经做好了 实用篇-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 /索引库名 4. 新增、查询、删除文档 具体操作: 首先保证你已经做好了 实用篇-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: 123huanfqc.cn,name:{firstName:张,lastName:三}
} 2、查询文档。语法: GET /索引库名/_doc/文档id 。例如如下
#查询文档
GET /huanfqc/_doc/1 3、删除文档。语法: DELETE/索引库名/_doc/文档id 。例如如下
#删除文档
DELETE /huanfqc/_doc/1 5. 修改文档 具体操作: 首先保证你已经做好了 实用篇-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: 123huanfqc.cn,name:{firstName:修改你了-张,lastName:三,xxupdate:我还加了一个}
} 方式二: 增量修改。修改指定字段的值 注意: 如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作
#修改文档
POST /索引库名/_update/文档id
{doc: {要修改的字段名: 新的值,}
}
#修改文档
POST /huanfqc/_update/1
{doc: {firstName: 修改-法外狂徒张三}
} 实用篇-ES-RestClient操作文档 下面的全部内容都是连续的请不要跳过某一小节 1. RestClient案例准备 对es概念不熟悉的话先去看上面的 实用篇-ES-索引库和文档不建议基础不牢就直接往下学 ES官方提供了各种不同语言的客户端用来操作ES。这些客户端的本质就是组装DSL语句通过http请求来发送给ES。 官方文档地址: https://www.elastic.co/guide/en/elasticsearch/client/index.html 下面就使用java程序进行操作es不再像上面那样使用浏览器页面进行操作es 在下面会逐步完成一个案例: 下载提供的hotel-demo.zip压缩包解压后是hotel-demo文件夹是一个java项目工程文件按照条件创建索引库索引库名为hotelmapping属性根据数据库结构定义。还要下载一个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文件配置正确的数据库信息 2. hotel数据结构分析 在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)
) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 根据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}}}
} 3. 初始化RestClient 操作主要是在idea的hotel-demo项目进行hotel-demo项目(不是springcloud项目只是springboot项目)是前面 1. RestClient案例准备跳过的可回去补 第一步: 在hotel-demo项目的pom.xml添加如下
elasticsearch.version7.12.1/elasticsearch.version!--引入es的RestHighLevelClient版本要跟你Centos7里面部署的es版本一致--
dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactIdversion7.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方法 4. 创建索引库 不是通过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对象自定义索引库名称为gghotelCreateIndexRequest 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 5. 删除和判断索引库 首先保证你已经做好了 实用篇-ES-环境搭建 然后开始下面的操作。不需要浏览器操作es所以不需要启动kibana容器
systemctl start docker # 启动docker服务
docker restart es #启动elasticsearch容器
#docker restart kibana #启动kibana容器 1、删除索引库。在hotel-demo项目的HotelIndexTest类添加如下。然后运行xxtestDeleteHotelIndex方法
//删除索引库
Testvoid xxtestDeleteHotelIndex() throws IOException {//创建Request对象指定要删除哪个索引库DeleteIndexRequest request new DeleteIndexRequest(gghotel);//发送请求xxclient.indices().delete(request, RequestOptions.DEFAULT);
} 2、判断索引库是否存在。在hotel-demo项目的HotelIndexTest类添加如下。然后运行xxtestDeleteHotelIndex方法
//判断索引库是否存在
Testvoid xxtestExistsHotelIndex() throws IOException {//创建Request对象判断哪个索引库是否存在在esGetIndexRequest request new GetIndexRequest(gghotel);//发送请求boolean ffexists xxclient.indices().exists(request, RequestOptions.DEFAULT);//输出一下看是否存在System.out.println(ffexists ? 索引库已经存在 : 索引库不存在);
} 6. 新增文档 首先保证你已经做好了 实用篇-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 IServiceHotel {} (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 ServiceImplHotelMapper, 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转为Stringxxrequest.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 7. 查询文档 我们在刚刚为了直观地验证是否成功新增文档需要启动kibana然后去浏览器页面进行查询非常的麻烦下面就来学习通过Java代码进行查询文档 难点: 根据id查询到的文档数据类型是json需要反序列化为java对象 第一步: 在HotelDocumentTest类添加如下
Test
void xxtestGetDocumentById() throws IOException {//准备Request对象要查询哪个索引库要查询的文档i我们上面指定的文档id是跟数据库字段的id一致上面新增的那条文档的id是61083GetRequest yyrequest new GetRequest(gghotel, 61083);//发送请求获取响应结果GetResponse yyresponse xxclient.get(yyrequest, RequestOptions.DEFAULT);//解析响应结果。getSourceAsString方法的作用是把得到的JSON结果转为StringString yyjson yyresponse.getSourceAsString();//JSON.parseObject()是com.alibaba.fastjson提供的API作用是对上面那行的yyjson进行反序列化//第一个参数是你要对谁进行反序列化第二个参数是你想要的数据类型HotelDoc yyhotelDoc JSON.parseObject(yyjson, HotelDoc.class);//输出一下查询结果System.out.println(yyhotelDoc);} 第二步: 运行HotelDocumentTest类的xxtestGetDocumentById方法 8. 修改文档 根据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对象要修改哪个索引库要修改的文档idUpdateRequest 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方法 9. 删除文档 首先保证你已经做好了 实用篇-ES-环境搭建 以及上面的五小节然后开始下面的操作。如果需要浏览器操作es那就不需要启动kibana容器
systemctl start docker # 启动docker服务
docker restart es #启动elasticsearch容器
#docker restart kibana #启动kibana容器 第一步: 在HotelDocumentTest类添加如下
Test
void wwtestDeleteDocument() throws IOException {//准备Request对象要删除哪个索引库要删除的文档idDeleteRequest wwrequest new DeleteRequest(gghotel, 61083);//发送请求xxclient.delete(wwrequest,RequestOptions.DEFAULT);
} 第二步: 先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)能不能查询到。运行HotelDocumentTest类的xxtestGetDocumentById方法 第三步: 删除id为61083的文档(相当于删除mysql中id为某个数的那一行)。运行HotelDocumentTest类的wwtestDeleteDocument方法 第四步: 验证。再次执行第二步也就是运行HotelDocumentTest类的xxtestGetDocumentById方法 10. 批量导入文档 建议去前面的 6. 新增文档 复习一下在索引库里面新增一条文档是怎么实现的 在上面的6、7、8、9节中我们一直都是操作一条id为61083的文档(相当于数据库表的某一行)。我们如何把mysql的更多数据导入进es的索引库(相当于mysql的表)呢下面就来学习批量把文档导入进索引库
思路:
1、利用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方法表示查询数据库的所有数据ListHotel kkhotels xxhotelService.list();//创建RequestBulkRequest vvrequest new BulkRequest();//准备参数实际上就是添加多个新增的Requestfor (Hotel kkhotel : kkhotels) {//把遍历拿到的每个kkhotels转换为文档类型的数据HotelDoc ffhotelDoc new HotelDoc(kkhotel);//HotelDoc是我们写的一个实体类//往哪个索引库批量新增文档、新增后的文档id是什么文档类型是JSONvvrequest.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对这些文档数据进行查询
实用篇-ES-DSL查询文档
官方文档: https://elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl。DSL是用来查询文档的 Elasticsearch提供了基于JSON的DSL来定义查询简单说就是用json来描述查询条件然后发送给es服务最后es服务基于查询条件把结果返回给我们 常见的查询类型包括如下: 1、查询所有: 查询出所有数据一般在测试的时候使用
match_all 2、全文检索查询: 利用分词器对用户输入内容进行分词然后去倒排索引库中匹配
match_query
multi_match_query 3、精确查询: 根据精确的词条值去查找数据一般是查找keyword、数值、日期、boolean等类型的字段。这些字段是不需要分词的但是依旧会建立倒排索引把字段的整体内容作为一个词条并存入倒排索引。在查找的时候也就不需要分词直接把搜索的内容去跟倒排索引匹配即可
ids表示根据id进行精确匹配
range表示根据数值范围进行精确匹配
term表示根据数据的值进行精确匹配 4、地理查询: 根据经纬度查询
geo_distance
geo_bounding_box 5、复合查询: 复合查询可将上述各种查询条件组合一起合并查询条件
bool利用逻辑运算把其它查询条件组合起来
function_score用于控制相关度算分算分会影响性能 下面会一个个学 1. DSL基本语法 查询的基本语法 #查询所有
GET /hotel/_search
{query:{match_all: {}}
} 【具体操作】 首先保证你已经做好了 实用篇-ES-环境搭建 创建了名为gghotel的索引库导入了批量文档。然后开始下面的操作 浏览器访问 http://你的ip地址:5601 输入如下 存在一个问题我们明明查询的是所有文档查询结果也显示查询出所有的文档了为什么上图右侧鼠标往下拉最多才只有10条文档数据呢 原因: 受默认的分页条件限制后面学习的时候会进行解决 2. 全文检索查询 首先保证你已经做好了 实用篇-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字段中包含如家的文档满足一个字段即可 3. 精确查询 首先保证你已经做好了 实用篇-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: 上海}}}
} 4. 地理查询
首先保证你已经做好了 实用篇-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)范围内的文档 5. 相关性算分 上面学的全文检索查询、精确查询、地理查询这三种查询在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天连锁酒店}}}
} 6. 函数算分查询 这是第一种复合查询 上面只是简单演了相关性打分中的函数算分查询文档与搜索关键字的相关度越高打分就越高排名就越靠前。不过有的时候我们希望人为地去控制控制文档的排名例如某些文档我们就希望排名靠前一点算分高一点此时就需要使用函数算分查询下面就来学习 函数算分查询 首先保证你已经做好了 实用篇-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}}
} 7. 布尔查询 这是第二种复合查询 布尔查询不会去修改算分而是把多个查询语句组合成一起形成新查询这些被组合的查询语句被称为子查询。子查询的组合方式有如下四种 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}}}]}}
} 8. 搜索结果处理-排序 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就会放弃打分不再做相关性算分提高效率 9. 搜索结果处理-分页 首先保证你已经做好了 实用篇-ES-环境搭建 创建了名为gghotel的索引库导入了批量文档。然后开始下面的操作elasticsearch(称为es)默认情况下只返回前10 条数据。而如果要查询更多数据就需要修改分页参数分页参数包括from和size语法如下
GET /索引库名/_search
{query: {要查询的字段: {}},from: 要查第几页, // 分页开始的位置默认为0size: 每页显示多少条文档, // 期望获取的文档总数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排序后获取from990size10的数据如下图 深度分页查询的演示输入如下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开始不推荐 我们上面用的分页方式是 fromsize 。优点: 支持随机翻页。缺点: 存在深度分页问题。场景: 百度、京东、谷歌、淘宝 10. 搜索结果处理-高亮 高亮: 就是在搜索结果中把搜索关键字突出显示。高亮显示的原理如下 1、将搜索结果中的关键字用标签标记出来 2、在页面中给标签添加css样式 首先保证你已经做好了 实用篇-ES-环境搭建 创建了名为gghotel的索引库导入了批量文档。然后开始下面的操作 语法
GET /索引库名/_search
{query: {match: { //match表示带关键字的查询字段: TEXT}},highlight: {fields: {字段名: {require_field_match: false,//默认是true表示 字段 要和 字段名 要一致。如果我们写的是不一致的话就需要修改为falsepre_tags: em, // 用来标记高亮字段的前置标签es会帮我们把标签加在关键字上。默认是empost_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}}}
} 11. 搜索结果处理-总结 搜索结果处理的整体语法
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 // 用来标记高亮字段的后置标签}}}
}
实用篇-ES-RestClient查询文档 1. 快速入门
上面的查询文档都是依赖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();}//-----------------------------上面是初始化下面是查询文档-快速入门的测试-------------------------------------------Testvoid 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();}//-----------------------------上面是初始化下面是查询文档-快速入门的测试-------------------------------------------Testvoid 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语句如下图 2. match的三种查询 首先保证你已经做好了 实用篇-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类添加如下
Testvoid 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类即可 3. 解析代码的抽取 首先保证你已经做好了 实用篇-ES-环境搭建 创建了名为gghotel的索引库然后开始下面的操作。如果需要浏览器操作es那就不需要启动kibana容器 我们发现对于 match、multi_match、match_all 查询的解析部分的代码都是相同的所以我们可以对解析部分的代码进行抽取(ctrlaltm)如下
//这个方法就是我们抽取出来的负责解析的
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);}
} 4. term、range精确查询 首先保证你已经做好了 实用篇-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类即可 5. bool复合查询 首先保证你已经做好了 实用篇-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类即可 6. geo_distance地理查询 首先保证你已经做好了 实用篇-ES-环境搭建 创建了名为gghotel的索引库然后开始下面的操作。如果需要浏览器操作es那就不需要启动kibana容器 【geo_distance查询】在HotelSearchTest类添加如下
Test
void xxtestGeoDistance() throws IOException {//准备Request对象要查询哪个索引库SearchRequest xxrequest new SearchRequest(gghotel);//创建一个地理位置查询构造器指定了要查询字段的是xxlocationGeoDistanceQueryBuilder xxgeoQuery QueryBuilders.geoDistanceQuery(xxlocation);xxgeoQuery.point(31.25, 121.5);//设置查询的中心点坐标这里的经度和纬度分别为 31.25 和 121.5xxgeoQuery.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类即可 7. 排序和分页 上面是各种查询的学习当我们把文档查询出来的时候接下来就是对文档的处理也就是你要把查询结果怎么展示出来。API以及对应的DSL语句如下图 首先保证你已经做好了 实用篇-ES-环境搭建 创建了名为gghotel的索引库然后开始下面的操作。如果需要浏览器操作es那就不需要启动kibana容器 【排序、分页】在HotelSearchTest类添加如下
Test
void xxtestPageAndSort() throws IOException {//页面、每页大小。如果你要翻第二页就把下面的xxpage改成2int 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);}
} 8. 高亮显示 高亮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);//【解析】获取高亮结果MapString, 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);}
} 实用篇-ES-黑马旅游案例 这个案例我做了两遍才做出来了第一遍排了一上午的错所以很有必要进行环境准备下面我将带领你对一下我的环境全网最详细的自创笔记 1. 环境准备-docker 企业部署一般都是采用Linux操作系统而其中又数CentOS发行版占比最多因此我们接下来会在CentOS下安装Docker CentOS7镜像快速下载我正在用的
https://cowtransfer.com/s/56423adc78374f
远程软件FinalShell快速下载我正在用的
https://cowtransfer.com/s/b4c8fcb5c15244 ideajdk下载
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.reposed -i s/download.docker.com/mirrors.aliyun.com\/docker-ce/g /etc/yum.repos.d/docker-ce.repoyum 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 2. 环境准备-elasticsearch 第一步: 创建网络
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.typesingle-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的响应结果 3. 环境准备-mysql 第一步: 打开database软件把tb_hotel.sql文件导入进你的数据库
tb_hotel.sql下载: https://cowtransfer.com/s/68c94a66d17248
create database if not exists elasticsearch;
use elasticsearch; 4. 环境准备-项目导入 第一步: 把下载好的hotel-demo.zip压缩包解压得到hotel-demo文件夹在idea打开hotel-demo
hotel-demo.zip下载:https://cowtransfer.com/s/36ac0a9f9d9043 第二步: 修改application.yml文件配置正确的数据库信息 第三步: 把pom.xml修改为如下
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.3.10.RELEASE/versionrelativePath/ !-- lookup parent from repository --/parentgroupIdcn.itcast.demo/groupIdartifactIdhotel-demo/artifactIdversion0.0.1-SNAPSHOT/versionnamehotel-demo/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.versionelasticsearch.version7.12.1/elasticsearch.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--引入es的RestHighLevelClient版本要跟你Centos7里面部署的es版本一致--dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactIdversion7.12.1/version/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.4.2/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scopeexclusionsexclusiongroupIdorg.junit.vintage/groupIdartifactIdjunit-vintage-engine/artifactId/exclusion/exclusions/dependency!--FastJson--dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.71/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexcludesexcludegroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/exclude/excludes/configuration/plugin/plugins/build/project 5. 环境准备-同步数据 把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索引库删掉再创建)Testvoid xxtestDeleteHotelIndex() throws IOException {//创建Request对象指定要删除哪个索引库DeleteIndexRequest gghotel new DeleteIndexRequest(hotel);//发送请求xxclient.indices().delete(gghotel, RequestOptions.DEFAULT);}//使用xxclient对象向es创建索引库Testvoid xxcreateHotelIndex() throws IOException {//创建Request对象自定义索引库名称为hotelCreateIndexRequest 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;Testvoid testBulkRequest() throws IOException {//向数据库批量查询酒店数据list方法表示查询数据库的所有数据ListHotel kkhotels xxhotelService.list();//创建RequestBulkRequest vvrequest new BulkRequest();//准备参数实际上就是添加多个新增的Requestfor (Hotel kkhotel : kkhotels) {//把遍历拿到的每个kkhotels转换为文档类型的数据HotelDoc ffhotelDoc new HotelDoc(kkhotel);//HotelDoc是我们写的一个实体类//往哪个索引库批量新增文档、新增后的文档id是什么文档类型是JSONvvrequest.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);}} 6. 搜索、分页 【请保证网络正常否则页面的静态资源部分加载不了】
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 ListHotelDoc hotels;//不带参构造函数public PageResult() {}//带参构造函数public PageResult(Long total, ListHotelDoc 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 IServiceHotel {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接口Autowiredprivate 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 ServiceImplHotelMapper, Hotel implements IHotelService {Autowired//注入在引导类声明好的Beanprivate RestHighLevelClient client;Overridepublic 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集合ListHotelDoc 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/ 7. 条件过滤 先看需求。添加品牌、城市、星级、价格等条件过滤功能 分析: 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 ServiceImplHotelMapper, Hotel implements IHotelService {Autowired//注入在引导类声明好的Beanprivate RestHighLevelClient client;Overridepublic 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不要写成startNameboolQuery.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集合ListHotelDoc 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/ 第四步: 解决四星,五星 无法作为条件进行查询的问题 8. 我附近的酒店 需求: 实现前端页面点击定位后会将你所在的位置发送给后台前端的请求信息如下会向后端发送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 ServiceImplHotelMapper, Hotel implements IHotelService {Autowired//注入在引导类声明好的Beanprivate RestHighLevelClient client;Overridepublic 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集合ListHotelDoc 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.length0){Object sortValue sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}hotels.add(hotelDoc);}//封装返回return new PageResult(total,hotels);}
}
第四步: 重启HotelDemoApplication引导类浏览器访问http://localhost:8089/。点击定位按钮查看是否能查询出距离自己最近的酒店并显示米数 9. 广告置顶
需求: 让指定的酒店在搜索结果中排名置顶。我们给需要置顶的酒店文档添加一个标记。然后利用function score给带有标记的文档增加权重 分析: 1、给HotelDoc类添加isAD字段Boolean类型 2、挑选几个你喜欢的酒店给它的文档数据添加isAD字段值为true 3、修改HotelService类的search方法添加function score功能给isAD值为true的酒店增加权重 Function Score查询可以控制文档的相关性算分java代码以及对应DSL语句如下图 第一步: 把HotelDoc类修改为如下
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 ServiceImplHotelMapper, Hotel implements IHotelService {Autowired//注入在引导类声明好的Beanprivate RestHighLevelClient client;Overridepublic 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 scorenew FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function scorenew 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集合ListHotelDoc 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.length0){Object sortValue sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}hotels.add(hotelDoc);}//封装返回return new PageResult(total,hotels);}
} 第六步: 重启HotelDemoApplication引导类浏览器访问http://localhost:8089/。查看我们指定的那两个酒店是否置顶 10. 高亮显示 高亮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 ServiceImplHotelMapper, Hotel implements IHotelService {Autowired//注入在引导类声明好的Beanprivate RestHighLevelClient client;Overridepublic 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 scorenew FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function scorenew 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集合ListHotelDoc 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.length0){Object sortValue sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}//【解析】获取高亮结果MapString, 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 实用篇-ES-数据聚合 官方文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
1. 聚合的分类
聚合: 可以实现对文档数据的统计、分析、运算。聚合常见的有如下三类。注意聚合的字段必然是不分词的原因: 聚合不能是text类型 ●桶 (Bucket) 聚合: 用来对文档做分组
○Term Aggregation聚合: 按照文档字段值分组。(我们下面会演示这个按照品牌进行分桶)○Date Histogram聚合: 按照日期阶梯分组例如一周为一组或者一月为一组
●度量 (Metric) 聚合: 用以计算一些值比如最大值、最小值、平均值
○avg: 求平均值○max: 求最大值○min: 求最小值○stats: 同时求max、min、avg、sum。(我们下面会演示这个按照品牌进行求评分最值和平均值)
●管道 (Pipeline) 聚合: 其它聚合的结果为基础做聚合。这种用的不多 2. DSL实现Bucket聚合 Bucket聚合也就是桶聚合 现在我们要统计所有数据中的酒店品牌有多少种此时我们可以根据酒店品牌的名称做聚合由于品牌是字段也就是要对字段值做分组采用的是TermAggregation聚合类型为term类型DSL示例如下 确保你的环境正常启动 浏览器访问 http://你的ip地址:5601 然后我们使用的索引库是hotel没有这个索引库的话可以去前面 实用篇-ES-黑马旅游案例 的 5. 环境准备-同步数据 进行索引库的创建和添加数据 第一步: 具体操作浏览器输入如下表示对不同的品牌进行聚合也就是不同的品牌为不同的桶相同的品牌放进一个桶里面 第二步: 如何修改默认的排序规则我们不希望是按照找出来的文档总条数降序排序。默认情况下Bucket会统计Bucket内的文档数量记为_count并且按照count降序排序。我们如果要修改结果排序方式的话只需要加一个order属性如下 第三步: 我们上面是对整个索引库的数据做聚合搜索如果索引库本身有庞大数据的话对整个索引库的聚合搜索是对内存消耗非常大我们希望自定义聚合的搜索范围也就是限定要聚合的文档范围只需要添加query条件即可如下 gt表示大于gte表示大于等于lt表示小于lte表示小于等于 3. DSL实现Metrics聚合 Metrics聚合也就是度量聚合。例如我们要求获取每个品牌的用户评分的min最小值、max最大值、avg平均值。注意不是整个索引库的所有酒店(文档)进行求值所以要结合上一节的Bucket聚合一起使用 确保你的环境正常启动 浏览器访问 http://你的ip地址:5601 第一步: 具体操作浏览器输入如下表示对品牌(父聚合)的评分(子聚合)进行求值 第二步: 如果我们还需要对结果按照评分的平均值再去做个排序看一下哪个酒店评价最高注意我们是在同里面做排序也就是排序要写在terms里面 4. RestClient实现聚合 确保你的环境正常启动 如何在java代码使用RestClient来实现聚合。java代码以及对应的DSL语句如下图 请求得到的是json数据 解析对聚合结果的json数据进行解析 具体操作: 是基于之前的hotel-demo项目上继续编写前面学的 实用篇-ES-RestClient查询文档 的基础上进行编写 第一步: 在HotelSearchTest类添加如下
Test
void testAggregation() throws IOException {//准备Request对象SearchRequest xxrequest new SearchRequest(hotel);//准备DSL。设置sizexxrequest.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();//遍历集合取出每一个bucketfor (Terms.Bucket xxbucket : xxbuckets) {//获取key。这个key就是品牌信息String key xxbucket.getKeyAsString();System.out.println(key);}
} 第二步: 运行testAggregation方法 5. 多条件聚合 案例: 在前面的 黑马路由案例 中搜索页面的品牌、城市等信息不应该是在页面写死而是通过聚合索引库中的酒店数据得来的 确保你的环境正常启动 第一步: 在IHotelService接口添加如下
/*** 查询城市、星级、品牌的聚合结果* return 聚合结果格式{城市: [上海, 北京], 品牌: [如家, 希尔顿]}*/
//在给出来的案例图中左侧加深的字是key右侧浅灰的字是value并且右侧的value有多个值。所以我们使用Map集合并且value是list集合
MapString, ListString xxfilters(); 第二步: 在HotelService实现类添加如下
Override
public MapString, ListString xxfilters() {try {//准备Request对象SearchRequest xxrequest new SearchRequest(hotel);//准备DSL。设置sizexxrequest.source().size(0);//准备DSL。聚合语句。对多个字段进行聚合buildAggregation(xxrequest);//发出请求SearchResponse xxresponse client.search(xxrequest, RequestOptions.DEFAULT);//【解析】//解析聚合结果MapString, ListString yyresult new HashMap();Aggregations xxaggregations xxresponse.getAggregations();//1、根据名称获取品牌的结果ListString xxbrandList getAggByName(xxaggregations,BrandAggMyName);//把品牌的结果信息放入mapyyresult.put(品牌,xxbrandList);//2、根据名称获取城市的结果ListString xxcityList getAggByName(xxaggregations,cityAggMyName);//把城市的结果信息放入mapyyresult.put(城市,xxcityList);//3、根据名称获取星级的结果ListString xxstarList getAggByName(xxaggregations,starAggMyName);//把星级的结果信息放入mapyyresult.put(星级,xxstarList);//返回yyresultreturn yyresult;} catch (IOException e) {throw new RuntimeException(e);}
}private ListString getAggByName(Aggregations xxaggregations,String kkaggName) {//根据聚合名称获取聚合结果Terms xxbrandTerms xxaggregations.get(kkaggName);//获取桶(buckets)获取的是一个集合List? extends Terms.Bucket xxbuckets xxbrandTerms.getBuckets();//遍历xxbuckets集合取出每一个key。把取到的key放到xxbrandList集合ListString 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() {MapString, ListString filters hotelService.xxfilters();System.out.println(filters);
} 6. hm-带过滤条件的聚合 对接前端接口也就是把上面 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 IServiceHotel {PageResult search(RequestParams params);/*** 查询城市、星级、品牌的聚合结果* return 聚合结果格式{城市: [上海, 北京], 品牌: [如家, 希尔顿]}*///在给出来的案例图中左侧加深的字是key右侧浅灰的字是value并且右侧的value有多个值。所以我们使用Map集合并且value是list集合MapString, ListString 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 ServiceImplHotelMapper, Hotel implements IHotelService {Autowired//注入在引导类声明好的Beanprivate RestHighLevelClient client;Overridepublic 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 scorenew FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function scorenew 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集合ListHotelDoc 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.length0){Object sortValue sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}//【解析】获取高亮结果MapString, 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);}//--------------------------------------------下面是多条件聚合-----------------------------------------Overridepublic MapString, ListString xxfilters(RequestParams params) {try {//准备Request对象SearchRequest xxrequest new SearchRequest(hotel);//添加query查询信息也就是限定聚合的范围buildBasicQuery(params, xxrequest);//准备DSL。设置sizexxrequest.source().size(0);//准备DSL。聚合语句。对多个字段进行聚合buildAggregation(xxrequest);//发出请求SearchResponse xxresponse client.search(xxrequest, RequestOptions.DEFAULT);//【解析】//解析聚合结果MapString, ListString yyresult new HashMap();Aggregations xxaggregations xxresponse.getAggregations();//1、根据名称获取品牌的结果ListString xxbrandList getAggByName(xxaggregations,BrandAggMyName);//把品牌的结果信息放入mapyyresult.put(brand,xxbrandList);//2、根据名称获取城市的结果ListString xxcityList getAggByName(xxaggregations,cityAggMyName);//把城市的结果信息放入mapyyresult.put(city,xxcityList);//3、根据名称获取星级的结果ListString xxstarList getAggByName(xxaggregations,starAggMyName);//把星级的结果信息放入mapyyresult.put(starName,xxstarList);//返回yyresultreturn yyresult;} catch (IOException e) {throw new RuntimeException(e);}}private ListString getAggByName(Aggregations xxaggregations,String kkaggName) {//根据聚合名称获取聚合结果Terms xxbrandTerms xxaggregations.get(kkaggName);//获取桶(buckets)获取的是一个集合List? extends Terms.Bucket xxbuckets xxbrandTerms.getBuckets();//遍历xxbuckets集合取出每一个key。把取到的key放到xxbrandList集合ListString 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引导类浏览器查看是否功能正常 实用篇-ES-自动补全 1. 安装拼音分词器 elasticsearch的拼音分词插件的官方地址: GitHub - infinilabs/analysis-pinyin: This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin. 拼音分词插件下载: 文件下载-奶牛快传 Download CowTransfer 第一步: 下载下来是py.zip压缩包解压之后得到一个py文件夹把这个文件夹上传到你CentOS7的 /var/lib/docker/volumes/es-plugins/_data 目录 第二步: 重启es。注意我们的拼音分词器的版本是跟es版本一致的 第三步: 验证拼音分词器是否生效
docker restart kibana #启动kibana容器。由于在网页使用DSL去操作es所以就需要kibana 浏览器访问 http://你的ip地址:5601 输入如下DSL语句会把文本的每个中文分成对应的拼音也会把整段文本的拼音首字母拼在一起
POST /_analyze
{text: [如家酒店],analyzer: pinyin
} 2. 自定义分词器 刚才我们看到了拼音分词器的效果但是并不能用于生产环境还存在一些问题 Elasticsearch中的分词器(analyzer)的组成包含三部分 ●character filters: 在tokenizer之前对文本进行处理。例如删除字符、替换字符 ●tokenizer: 将文本按照一定的规则切割成词条 (term)。例如keyword, 就是不分词; 还有ik_smart。这部分是真正的分词器 ●tokenizer filter: 将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等 我们可以按照下图的作用顺序解决拼音分词器的不足: 拼音分词器不会分词那么就先用ik分词器分好词后再交给拼音分词器 解决如下注意必须在创建索引库的时候做例如创建一个名为test的索引库
PUT /test
{settings: {analysis: {analyzer: { // 自定义分词器my_analyzer: { // 分词器名称tokenizer: ik_max_word,filter: py //过滤器名称}},filter: { // 自定义tokenizer filterpy: { // 自定义过滤器名称type: pinyin, // 过滤器类型这里是pinyinkeep_full_pinyin: false, //不允许单个字来分拼音keep_joined_full_pinyin: true, //全拼给什么字或词就把什么写成拼音keep_original: true, //保留中文解决了拼音分词器分词后全是拼音的问题limit_first_letter_length: 16,remove_duplicated_term: true,none_chinese_pinyin_tokenize: false}}}}
} 具体操作如下注意自定义分词器只对当前索引库有效因为我们是写在settings属性里面 确保你的环境正常启动
systemctl start docker # 启动docker服务
docker restart es #启动elasticsearch容器
docker restart kibana #启动kibana容器因为我们需要在浏览器执行DSL语句去操作es 第一步: 浏览器访问 http://你的ip地址:5601 输入如下DSL语句表示创建名为test的索引库实现自定义分词器也就是对分词器做一些限制、开放一些限制
PUT /test
{settings: {analysis: {analyzer: { my_analyzer: { tokenizer: ik_max_word,filter: py}},filter: {py: { type: pinyin,keep_full_pinyin: false,keep_joined_full_pinyin: true,keep_original: true,limit_first_letter_length: 16,remove_duplicated_term: true,none_chinese_pinyin_tokenize: false}}}},mappings: {properties: {name: {type: text,analyzer: my_analyzer}}}
} 第二步: 测试自定义分词器(也就是ik分词器拼音分词器)。第一种测试方法
POST /test/_analyze
{text: [如家酒店],analyzer: my_analyzer
} 第三步: 测试自定义分词器(也就是ik分词器拼音分词器)。第二种测试方法通过往test库插入文档、然后查询插入的文档的方式来测试
POST /test/_doc/1
{id: 1,name: 狮子
}
POST /test/_doc/2
{id: 2,name: 虱子
}GET /test/_search
{query: {match: {name: shizi}}
} 第四步: 其实还是有个问题如下图在搜索中文时却搜索出了同音字我们不想搜索出同音字解决在下一节会学 3. 解决自定义分词器的问题 拼音分词器适合在创建倒排索引的时候使用但不能在搜索的时候使用 因此字段在创建倒排索引时应该用自定义分词器(my_analyzer); 字段在搜索时应该使用is_smart分词器。对应的DSL语句如下
PUT /test
{settings: {analysis: {analyzer: {my_analyzer: {tokenizer: ik_max_word, filter: py}},filter: {py: { ... }}}},mappings: { //在mappings里面指定两个分词器properties: {name: {type: text,analyzer: my_analyzer, //analyzer表示创建索引时使用这个my_analyzer分词器search_analyzer: ik_smart //search_analyzer表示在搜索时使用这个search_analyzer分词器}}}
} 具体操作如下 确保你的环境正常启动
systemctl start docker # 启动docker服务
docker restart es #启动elasticsearch容器
docker restart kibana #启动kibana容器因为我们需要在浏览器执行DSL语句去操作es 第一步: 浏览器访问 http://你的ip地址:5601 第二步: 输入如下DSL语句先删除刚刚上面创建的test索引库
DELETE /test 第三步: 输入如下DSL语句重新创建test索引库这次我们在mappings属性里面多添加了一个分词器search_analyzer: ik_smart
PUT /test
{settings: {analysis: {analyzer: { my_analyzer: { tokenizer: ik_max_word,filter: py}},filter: {py: { type: pinyin,keep_full_pinyin: false,keep_joined_full_pinyin: true,keep_original: true,limit_first_letter_length: 16,remove_duplicated_term: true,none_chinese_pinyin_tokenize: false}}}},mappings: {properties: {name: {type: text,analyzer: my_analyzer,search_analyzer: ik_smart}}}
} 第四步: 测试。同样是往test库创建两个文档然后使用中文去匹配如果不出现同音字就表示验证通过
POST /test/_doc/1
{id: 1,name: 狮子
}
POST /test/_doc/2
{id: 2,name: 虱子
}GET /test/_search
{query: {match: {name: 狮子}}
} 4. DSL实现自动补全查询 elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率对于文档中字段的类型有一些约束 es在实现自动补全功能时对查询的字段有以下两个要求 1、参与补全查询的字段必须是completion类型 2、字段的内容一般是用来补全的多个词条形成的数组 查询语法如下
// 自动补全查询
GET /test/_search
{suggest: { //不是query而是suggest表示自动补全title_suggest: { //自定义自动补全查询的名字text: s, // 关键字例如用户输入s就会触发completion: { //自动补全的类型field: title, // 要自动补全查询的字段注意该字段必须是completion类型skip_duplicates: true, // 跳过重复的size: 10 // 获取前10条结果}}}
} 具体操作如下注意自定义分词器只对当前索引库有效因为我们是写在settings属性里面 确保你的环境正常启动
systemctl start docker # 启动docker服务
docker restart es #启动elasticsearch容器
docker restart kibana #启动kibana容器因为我们需要在浏览器执行DSL语句去操作es 第一步: 浏览器访问 http://你的ip地址:5601 第二步: 输入如下DSL语句先删除刚刚上面创建的test索引库
DELETE /test 第三步: 输入如下DSL语句表示重新创建test索引库并为索引库添加3条文档数据
PUT test
{mappings: {properties: {title:{type: completion}}}
}POST test/_doc
{title: [Sony, WH-1000XM3]
}
POST test/_doc
{title: [SK-II, PITERA]
}
POST test/_doc
{title: [Nintendo, switch]
} 第四步: 测试。自动补全查询。输入如下DSL语句
GET /test/_search
{suggest: {zidingyichaxunmingcheng: {text: s,completion: {field: title,skip_duplicates: true,size: 10}}}
} 5. hm-修改酒店索引库数据结构 请把前面的 1. 安装拼音分词器 和 2. 自定义分词器 和 3. 解决自定义分词器的问题 做完才能进行下面的操作 我们接下来要把自动补全功能应用在前面学习的黑马旅游案例中这节我们就先学习如下几点 1、修改hotel索引库结构设置自定义拼音分词器 2、修改索引库的name、all字段使用自定义分词器 3、索引库添加一个新字段suggestion类型为completion类型使用自定义的分词器 确保你的环境正常启动 第一步: 浏览器访问 http://你的ip地址:5601 第二步: 输入如下DSL语句先删除已有的hotel索引库
DELETE /hotel 第三步: 输入如下DSL语句。重新创建一个hotel索引库只是创建一个空的hotel索引库
PUT /hotel
{settings: {analysis: {analyzer: {text_anlyzer: {tokenizer: ik_max_word,filter: py},completion_analyzer: {tokenizer: keyword,filter: py}},filter: {py: {type: pinyin,keep_full_pinyin: false,keep_joined_full_pinyin: true,keep_original: true,limit_first_letter_length: 16,remove_duplicated_term: true,none_chinese_pinyin_tokenize: false}}}},mappings: {properties: {id:{type: keyword},name:{type: text,analyzer: text_anlyzer,search_analyzer: ik_smart,copy_to: all},address:{type: keyword,index: false},price:{type: integer},score:{type: integer},brand:{type: keyword,copy_to: all},city:{type: keyword},starName:{type: keyword},business:{type: keyword,copy_to: all},location:{type: geo_point},pic:{type: keyword,index: false},all:{type: text,analyzer: text_anlyzer,search_analyzer: ik_smart},suggestion:{type: completion,analyzer: completion_analyzer}}}
} 第四步: 由于某个文档(酒店)可能包括多个商圈如下图所以在下一步我们会在HotelDoc类进行切割后存入suggestion 第五步: 在上面的DSL语句中我们还多创建了一个suggestion字段所以对应的我们需要在黑马旅游案例的java代码中修改实体类
package cn.itcast.hotel.pojo;import lombok.Data;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;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;//用于自动补全的类型该类型在es中必须是completion类型该类型在java中就要写成数组(我们写成集合)private ListString suggestion;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();if(this.business.contains(/)){//如果同一文档的business商圈有多个为了自动补全的分词效果更明显我们需要分割一下String[] arr this.business.split(/);this.suggestion new ArrayList();this.suggestion.add(this.brand);//Collections是工具类可以把数组数据逐个放进suggestion集合Collections.addAll(this.suggestion,arr);}else{//自动补全我们可以让品牌商圈来做自动补全用户触发自动补全时补全到的就是品牌商圈字段的文档this.suggestion Arrays.asList(this.brand,this.business);}}
} 打开hotel-demo项目(继续做前面学的黑马旅游案例)把HotelDoc类修改为如下
package cn.itcast.hotel.pojo;import lombok.Data;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;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;//用于自动补全的类型该类型在es中必须是completion类型该类型在java中就要写成数组(我们写成集合)private ListString suggestion;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();if(this.business.contains(/)){//如果同一文档的business商圈有多个为了自动补全的分词效果更明显我们需要分割一下String[] arr this.business.split(/);this.suggestion new ArrayList();this.suggestion.add(this.brand);//Collections是工具类可以把数组数据逐个放进suggestion集合Collections.addAll(this.suggestion,arr);}else{//自动补全我们可以让品牌商圈来做自动补全用户触发自动补全时补全到的就是品牌商圈字段的文档this.suggestion Arrays.asList(this.brand,this.business);}}
} 第六步: 运行HotelDocumentTest类的testBulkRequest方法重新把mysql的数据导入到es的hotel索引库 第七步: 验证。自动补全功能 6. RestAPI实现自动补全查询 查询API和对应的DSL关系如下图 解析API和对应的DSL关系如下图 确保你的环境正常启动 具体操作如下 第一步: 在HotelSearchTest类添加如下并运行xxtestSuggest方法
Test
void xxtestSuggest() throws IOException {//准备Request查的是哪个索引库SearchRequest xxrequest new SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi,//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(h)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse xxresponse xxclient.search(xxrequest, RequestOptions.DEFAULT);//解析结果System.out.println(xxresponse);
} 第二步: 结果解析。把HotelSearchTest类的xxtestSuggest方法修改为如下并运行xxtestSuggest方法
Test
void xxtestSuggest() throws IOException {//准备Request查的是哪个索引库SearchRequest xxrequest new SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi, //为这个自定义补全查询起一个名字//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(h)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse xxresponse xxclient.search(xxrequest, RequestOptions.DEFAULT);//解析结果Suggest xxsuggest xxresponse.getSuggest();//根据补全查询名称获取补全结果CompletionSuggestion xxsuggestions xxsuggest.getSuggestion(zidingyizidongbuquanmingzi);//获取xxsuggestionsListCompletionSuggestion.Entry.Option xxoptions xxsuggestions.getOptions();//遍历for (CompletionSuggestion.Entry.Option xxoption : xxoptions) {String text xxoption.getText().toString();System.out.println(text);}
} 7. hm-搜索框自动补全查询 确保你的环境正常启动 我们刚刚使用RestAPI实现了自动补全查询现在我们要把这个功能应用在 黑马旅游案例 的搜索框首先来看一下前端给我们传来的参数如下图 我们需要编写接口接收该请求返回补全结果的集合类型为List 具体操作如下 第一步: 在IHotelService接口添加如下
ListString xxgetSuggestions(String xxprefix); 第二步: 在HotelService类添加如下
Override
public ListString xxgetSuggestions(String xxprefix) {try {//准备Request查的是哪个索引库SearchRequest xxrequest new SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi, //为这个自定义补全查询起一个名字//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(xxprefix)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse xxresponse client.search(xxrequest, RequestOptions.DEFAULT);//解析结果Suggest xxsuggest xxresponse.getSuggest();//根据补全查询名称获取补全结果CompletionSuggestion xxsuggestions xxsuggest.getSuggestion(zidingyizidongbuquanmingzi);//获取xxsuggestionsListCompletionSuggestion.Entry.Option xxoptions xxsuggestions.getOptions();//准备好一个集合当自动补全时相关词就会在这个集合并返回给前端ListString xxlist new ArrayList(xxoptions.size());//遍历for (CompletionSuggestion.Entry.Option xxoption : xxoptions) {String text xxoption.getText().toString();//把自动补全时查到的相关词放到我们准备好的集合里面xxlist.add(text);}return xxlist;} catch (IOException e) {throw new RuntimeException(e);}
} 第三步: 在HotelController类添加如下
GetMapping(suggestion)
public ListString xxgetSuggestions(RequestParam(key) String xxprefix){return hotelService.xxgetSuggestions(xxprefix);
}
第四步: 重新启动HotelDemoApplication引导类 第四步: 测试。查看自动补全查询是否在搜索框生效访问http://localhost:8089/ 实用篇-ES-数据同步