96SEO 2026-02-19 16:38 14
的时候#xff0c;写过一次ES笔记#xff0c;可以结合一起看一下。

之前在SpringBoot里面写的相关ES笔记是基于Windows的#xff0c;现在我们是基于docker容器来使用#xff0c;需要你们提…实用篇-ES-环境搭建
ES是elasticsearch的简称。
我在SpringBoot学习
之前在SpringBoot里面写的相关ES笔记是基于Windows的现在我们是基于docker容器来使用需要你们提前准备好自己的docker容器以及掌握docker操作
随着业务发展数据量越来越庞大传统的MySQL数据库难以满足我们的需求所以在微服务架构下一般都会用到一种分布式搜索的技术下面我们会学分布式搜索中最流行的一种也就是elasticsearch的用法。
包括学习elasticsearch的概念、安装、使用。
其中学习elasticsearch的使用的时候主要通过两个方面一方面是elasticsearch对于索引库(类似于数据库把数据导入进索引库导入的数据就是所谓的文档我们要实现文档的增删改查)的操作另一方面我们还会学习elasticsearch官方提供的Restful的API(也就是Java客户端)来更方便的操作elasticsearch
nǎ)elasticsearch是一款非常强大的开源搜索引擎技术可以帮助我们从海量数据中快速找到需要的内容
stack的核心负责存储、搜索、分析数据。
我们主要学习这个elasticsearch底层实现是基于Lucene技术
2、Kibana是数据可视化的组件也就是展示搜索出来的数据。
elasticsearch的相关技术了解即可
3、Logstash、Beats是负责数据抓取的组件。
elasticsearch的相关技术了解即可
Lucene是一个Java语言的搜索引擎类库(其实就是一个jar包)是Apache公司的顶级项目由DougCutting于1999年研发
由于Lucene的缺点诞生出了elasticsearch与Lucene相比elasticsearch(基于Lucene且Compass是elasticsearch的前身)具有以下优点
2、提供Restful接口可被任何语言调用elasticsearch的核心技术是倒排索引下面会学
传统数据库(例如MySQL)采用正向索引例如给下表(tb_goods)中的id创建索引
elasticsearch采用倒排索引例如给下表(tb_goods)中的id创建索引
基于文档id来创建索引。
查询词条时必须先找到文档而后判断是否包含词条
对文档内容进行分词对词条创建索引并记录词条所在文档的信息。
查询时先根据词条去查询文档id然后获取到文档
elasticsearch是面向文档存储的可以是数据库中的一条商品数据一个订单信息。
注意elasticsearch的文档是以json形式存储的也就是说我们把数据(也叫文档)存储进elasticsearch时这些文档数据就会自动被序列化为json格式然后才存储进elasticsearch
下面的表格是介绍elasticsearch中的各个概念以及含义看的时候重点看第二、三列第一列是为了让你更理解第二列的意思所以在第一列拿MySQL的概念来做匹配。
例如elasticsearch的Index表示索引也就是文档的集合就相当于MySQL的Table(也就是表)
文档(Document)就是一条条的数据类似数据库中的行(Row)。
这里的文档都是JSON格式
字段(Field)就是JSON文档中的字段类似数据库中的列(Column)
Mapping(映射)是索引中文档的约束例如字段类型约束。
类似数据库的表结构(Schema)
DSL是elasticsearch提供的JSON风格的请求语句用来操作elasticsearch实现CRUD
我们下面会学习映射的创建以及文档的增删改查。
这些操作在MySQL里面是通过SQL语句实现但我们在elasticsearch中会使用的是DSL语句来操作。
在elasticsearch中当我们写好DSL语句要通过http请求发给elasticsearchelasticsearch才会响应原因是在elasticsearch对外暴露的是Restful接口
上面基本都是在讲elasticsearch那么是不是elasticsearch已经完全代码MySQL答案并不是两者擅长的事情不一样如下
擅长事务类型的操作可以确保数据的安全和一致性。
一般用于增删改2、Elasticsearch:
擅长海量数据的搜索、分析、计算。
一般用于查询两者是互补关系不是替代关系因此在业务系统架构中两者都会存在让用户在MySQL里面增删改数据然后MySQL把数据同步给elasticsearch用户要查询的时候就在elasticsearch里面进行查询
chǐ)。
注意elasticsearch、kibana、IK分词器这三者通常是一起使用的
我们学习elasticsearch是基于docker容器来使用需要你们提前准备好自己的docker容器以及掌握docker操作。
elasticsearch一般都是搭配kibana(下节会学如何安装)来使用kibana的作用是让我们非常方便的去编写elasticsearch中的DSL语句从而去操作elasticsearch
创建网络。
因为我们还需要部署kibana容器因此需要让es和kibana容器互联
加载es镜像。
采用elasticsearch的7.12.1版本的镜像这个镜像体积有800多MB所以需要在Windows上下载链接安装包下载下来是一个es的镜像tar包然后传到CentOS7的/root目录
https://cowtransfer.com/s/c84ac851b9ba44
https://cowtransfer.com/s/a76d8339d7ba4d
把在CentOS7的/root目录的es镜像导入到docker
创建并运行es容器容器名称就叫es。
在docker(也叫Docker大容器、Docker主机、宿主机)根据es镜像来创建es容器
es-data:/usr/share/elasticsearch/data
es-plugins:/usr/share/elasticsearch/plugins
cluster.namees-docker-cluster设置集群名称
discovery.typesingle-node运行模式例如非集群模式
es-data:/usr/share/elasticsearch/data挂载数据卷绑定es的数据
es-plugins:/usr/share/elasticsearch/plugins挂载数据卷绑定es的插件目录
9200:9200端口映射配置向外暴露的http请求端口用于用户访问
9300:9300端口映射配置是es容器各个节点之间互相访问的端口由于我们是单节点部署所以用不到
一起操作也就是说同一个实验。
注意elasticsearch、kibana、IK分词器这三者通常是一起使用的
让我们非常方便的去编写elasticsearch中的DSL语句从而去操作elasticsearch(读
加载kibana镜像。
这个镜像体积有1.04G所以需要在Windows上下载链接安装包下载下来是一个es的镜像tar包然后传到CentOS7的/root目录
https://cowtransfer.com/s/1c16f55edf2341
把在CentOS7的/root目录的kibana镜像导入到docker
创建并运行kibana容器容器名称就叫kibana。
在docker(也叫Docker大容器、Docker主机、宿主机)根据kibana镜像来创建kibana容器
ELASTICSEARCH_HOSTShttp://es:9200
加入一个名为es-net的网络中与elasticsearch在同一个网络中
由于kibana和es会被我们设置在同一个网络所以这里的kibana可以通过容器名直接访问eses的容器名我们在上一节设置的是es
设置elasticsearch的地址因为kibana已经与elasticsearch在一个网络因此可以用容器名直接访问elasticsearch
https://github.com/medcl/elasticsearch-analysis-ik。
注意elasticsearch、kibana、IK分词器这三者通常是一起使用的
es在创建倒排索引时需要对文档进行分词。
在搜索时需要对用户输入的内容进行分词。
但默认的分词规则不支持中文处理默认是只支持对英文进行分词但是在正常业务中我们需要处理的文档大多是中文所以我们需要对中文进行分词所以就需要安装IK分词器
为了直观的体现es的分词规则不支持英文我们可以做下面的小演示如下
上图就算分词器名称改成chinese或standard对于中文的分词也是一字一分。
解决:
创建elasticsearch容器时指定了数据卷目录其中有个数据卷指定了自定义名称为es-plugins表示存放插件的数据卷
我们使用inspect命令把es-plugins数据卷的路径信息查询出来
下载ik.zip压缩包到Windows下载后解压出来是ik文件夹
根据上面查询出来的es-plugins数据卷的路径把ik文件夹上传到CentOS7的
/var/lib/docker/volumes/es-plugins/_data
/var/lib/docker/volumes/es-plugins/_data
创建elasticsearch容器时指定了自定义容器名称为es
●ik_smart最少切分根据语义分词正常分词●ik_max_word最细切分也是根据语义分词分的词语更多更细
Ik分词器的分词底层是一个字典在字典里面会有各种各样的词语当ik分词器需要对分词文本进行分词时ik分词器就会拿着这个文本(乱拆成多个词或词语)一个个去字典里面匹配如果能匹配到证明某个词(乱拆成多个词或词语)是词就把这个证明后的词分出来作为一个词
字典的分词效果是有限的只能对日常生活中常见的语义相关的词进行分词由于字典的词汇量少所以我们需要对字典进行扩展。
字典的分词效果往往存在违禁词我们不希望IK分词器能匹配并成功把词典里的违禁词作为分词解决:
1、要拓展或禁用ik分词器的词库只需要修改一个分词器中的IKAnalyzer.cfg.xml文件如下
/var/lib/docker/volumes/es-plugins/_data/ik/config
http://java.sun.com/dtd/properties.dtd
扩展配置/comment!--用户可以在这里配置自己的扩展字典--entry
keyext_dictext.dic/entry!--用户可以在这里配置自己的扩展停止词字典
keyext_stopwordsstopword.dic/entry
2、在config目录新建myext.dic文件写入自己想要的特定词也就是扩展词。
新建mystopword.dic文件写入自己想要禁用的特定词也就是不参与分词的词
/var/lib/docker/volumes/es-plugins/_data/ik/config
●ik_smart最少切分根据语义分词正常分词●ik_max_word最细切分也是根据语义分词分的词语更多更细
根据上图确实可以根据我们指定的扩展词进行分析违禁词也确实被禁用没有被分词
https://elastic.co/guide/en/elasticsearch/reference/current/index.html
下面的表格是介绍elasticsearch中的各个概念以及含义看的时候重点看第二、三列第一列是为了让你更理解第二列的意思所以在第一列拿MySQL的概念来做匹配。
例如elasticsearch的Index表示索引也就是文档的集合就相当于MySQL的Table(也就是表)
文档(Document)就是一条条的数据类似数据库中的行(Row)。
这里的文档都是JSON格式
字段(Field)就是JSON文档中的字段类似数据库中的列(Column)
Mapping(映射)是索引中文档的约束例如字段类型约束。
类似数据库的表结构(Schema)
DSL是elasticsearch提供的JSON风格的请求语句用来操作elasticsearch实现CRUD
mapping是对索引库中文档(es中的文档是json风格)的约束常见的mapping属性包括如下
text可分词的文本、keyword精确值例如品牌、国家、ip地址等不可分词的词语
long、integer、short、byte、double、float
是否创建倒排索引默认为true(也就是可参与分词搜索)改成false的话别人就搜索不到你
分词器当字段类型是text时必须指定分词器。
如果字段类型是keyword那么不需要指定分词器
ES中通过Restful请求操作索引库、文档。
请求内容用DSL语句来表示。
创建索引库和mapping的DSL语法如下
//false表示这个字段不参与搜索该字段不会创建为倒排索引false不加双引号},字段名3:{properties:
//不参与搜索用户不能通过搜索搜到xxemail字段},name:
//参与搜索用户通过可搜索到firstName字段},lastName:
索引库是无法被修改的但是可以添加新字段(不能和已有的重复否则报错)
1、新增文档的DSL语法其实就是告诉kibana我们要把文档添加到es的哪个索引库如果省略文档id的话es会默认随机生成一个建议自己指定文档id
123huanfqc.cn,name:{firstName:张,lastName:三}
然后开始下面的操作。
并且已经创建了名为huanfqc的索引库、文档id为1的文档
这种操作是直接用新值覆盖掉旧的如果只put一个字段那么其它字段就没了所以你不想修改的字段也要原样写出来不然就没了
如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作
123huanfqc.cn,name:{firstName:修改你了-张,lastName:三,xxupdate:我还加了一个}
如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作
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
https://cowtransfer.com/s/716f049850a849
打开database软件把tb_hotel.sql文件导入进你的数据库
把下载好的hotel-demo.zip压缩包解压得到hotel-demo文件夹在idea打开hotel-demo
字段名、数据类型、是否参与搜索、是否分词、如果分词那么分词器是什么。
我们刚刚在mysql导入了tb_hotel.sql里面有很多数据我们需要基于这些数据结构去分析并尝试编写对应的es的mapping映射
根据mysql的数据类型等信息编写es(没有自己对着上面的sql写的)。
注意经纬度在es里面是geo_point类型且经纬度是写在一起的
操作主要是在idea的hotel-demo项目进行hotel-demo项目(不是springcloud项目只是springboot项目)是前面
elasticsearch.version7.12.1/elasticsearch.version!--引入es的RestHighLevelClient版本要跟你Centos7里面部署的es版本一致--
dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactIdversion7.12.1/version
在hotel-demo项目的src/test/java/cn.itcast.hotel目录新建HotelIndexTest类写入如下
org.elasticsearch.client.RestClient;
org.elasticsearch.client.RestHighLevelClient;
org.junit.jupiter.api.AfterEach;
org.junit.jupiter.api.BeforeEach;
org.junit.jupiter.api.Test;import
xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void
RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.200.231:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void
{this.xxclient.close();}Test//现在才是测试代码对象已经在上面初始化并且有销毁的步骤了下面直接打印void
{System.out.println(xxclient);}
不是通过kibana的浏览器控制台通过DSL语句来进行操作es在es里面创建索引库
而是通过上一节初始化的RestClient对象在Java里面去操作es创建es的索引库。
根本不需要kibana做中间者
在src/main/java/cn.itcast.hotel目录新建constants.HotelConstants类里面写DSL语句如下
cn.itcast.hotel.constants;public
在hotel-demo项目的HotelIndexTest类添加如下
{//创建Request对象自定义索引库名称为gghotelCreateIndexRequest
CreateIndexRequest(gghotel);//准备请求的参数:
DSL语句request.source(xxMappingTemplate,
XContentType.JSON);//注意xxMappingTemplate是第一步定义的的静态常量导包别导错了//发送请求xxclient.indices().create(request,
验证。
运行HotelIndexTest类的xxcreateHotelIndex测试方法
如何更直观地验证es里面确实有刚刚创建的索引库(刚刚创建的索引库是叫gghotel)
那就不得不运行kibana了这样才能打开web浏览器页面进行查询
然后开始下面的操作。
不需要浏览器操作es所以不需要启动kibana容器
1、删除索引库。
在hotel-demo项目的HotelIndexTest类添加如下。
然后运行xxtestDeleteHotelIndex方法
{//创建Request对象指定要删除哪个索引库DeleteIndexRequest
DeleteIndexRequest(gghotel);//发送请求xxclient.indices().delete(request,
2、判断索引库是否存在。
在hotel-demo项目的HotelIndexTest类添加如下。
然后运行xxtestDeleteHotelIndex方法
{//创建Request对象判断哪个索引库是否存在在esGetIndexRequest
GetIndexRequest(gghotel);//发送请求boolean
xxclient.indices().exists(request,
RequestOptions.DEFAULT);//输出一下看是否存在System.out.println(ffexists
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
去数据库查询酒店数据把查询到的结果导入到hotel索引库(上一节我们已经创建一个名为gghotel的索引库)实现酒店数据的增删改查
简单说就是先去数据查酒店数据把结果转换成索引库所需要的格式(新增文档的DSL语法)然后写到索引库然后在索引库对这些酒店数据进行增删改查
你们拿到代码的时候这些操作已经做好不需要再去做我只是写出来方便后续复习
(1)、在pojo目录里面有一个Hotel类作用是指定根数据库交互的字段写入了如下
com.baomidou.mybatisplus.annotation.IdType;
com.baomidou.mybatisplus.annotation.TableId;
com.baomidou.mybatisplus.annotation.TableName;
(2)、在pojo目录里面有一个HotelDoc类作用是跟es的索引库交互的字段也就是跟我们索引库里面的字段类型联调写入了如下
hotel.getStarName();this.business
hotel.getBusiness();this.xxlocation
(3)、在service新建了IHotelService接口作用是写mybatis-plus向数据库发送请求用于查询数据库的数据
com.baomidou.mybatisplus.extension.service.IService;public
(4)、在service新建了impl里面有一个HotelService类是IHotelService接口的实现类
cn.itcast.hotel.service.impl;import
cn.itcast.hotel.mapper.HotelMapper;
cn.itcast.hotel.service.IHotelService;
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
org.springframework.stereotype.Service;Service
在hotel-demo项目的src/test/java/cn.itcast.hotel目录新建HotelDocumentTest类写入如下
cn.itcast.hotel.service.IHotelService;
org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
org.elasticsearch.action.index.IndexRequest;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestClient;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.client.indices.CreateIndexRequest;
org.elasticsearch.client.indices.GetIndexRequest;
org.elasticsearch.common.xcontent.XContentType;
org.junit.jupiter.api.AfterEach;
org.junit.jupiter.api.BeforeEach;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.boot.test.context.SpringBootTest;import
cn.itcast.hotel.constants.HotelConstants.xxMappingTemplate;/***
xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void
RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void
{this.xxclient.close();}//-----------------------------上面是初始化下面是操作文档的测试-------------------------------------------Autowired//注入写好的IHotelService接口用于去数据库查询数据private
xxhotelService;Test//新增文档到gghotel索引库请保证你的es里面已经存在gghotel索引库void
{//去数据库查询数据我们简单查询一下id为61083的数据。
由于在实体类里面定义的id是Long类型所以要加L表示该数字是Long类型Hotel
xxhotelService.getById(61083L);//把上一行数据库查询出来的字段类型转为es的索引库的文档类型才能往索引库里面新增文档HotelDoc
HotelDoc(xxdataExample);//准备Request对象往哪个索引库添加文档文档的id需要自定义xxdataExample.getId().toString()表示文档id跟数据库的id一致IndexRequest
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浏览器页面进行查询
我们在刚刚为了直观地验证是否成功新增文档需要启动kibana然后去浏览器页面进行查询非常的麻烦下面就来学习通过Java代码进行查询文档
根据id查询到的文档数据类型是json需要反序列化为java对象
{//准备Request对象要查询哪个索引库要查询的文档i我们上面指定的文档id是跟数据库字段的id一致上面新增的那条文档的id是61083GetRequest
RequestOptions.DEFAULT);//解析响应结果。
getSourceAsString方法的作用是把得到的JSON结果转为StringString
yyresponse.getSourceAsString();//JSON.parseObject()是com.alibaba.fastjson提供的API作用是对上面那行的yyjson进行反序列化//第一个参数是你要对谁进行反序列化第二个参数是你想要的数据类型HotelDoc
HotelDoc.class);//输出一下查询结果System.out.println(yyhotelDoc);}
运行HotelDocumentTest类的xxtestGetDocumentById方法
根据id修改酒店数据。
修改es的索引库的文档的数据有两种方式前面在学kibana操作文档的时候学过可前去
这种操作是直接用新值覆盖掉旧的如果只put一个字段那么其它字段就没了所以你不想修改的字段也要原样写出来不然就没了。
如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作
2、增量修改(我们学习这种)。
修改指定字段的值。
如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作
以及上面的五小节然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
{//准备Request对象要修改哪个索引库要修改的文档idUpdateRequest
61083);//准备请求参数要修改哪些字段修改成什么zzrequest.doc(name,我修改了你3个文档字段,price,999,city,北京);//发送请求获取响应结果xxclient.update(zzrequest,RequestOptions.DEFAULT);
先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)的数据。
运行HotelDocumentTest类的xxtestGetDocumentById方法
运行HotelDocumentTest类的xxtestUpdateDocument方法作用是修改数据也就是我们第一步写的代码
在去查一下文档的数据验证第三步是否修改成功。
运行HotelDocumentTest类的xxtestGetDocumentById方法
以及上面的五小节然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
{//准备Request对象要删除哪个索引库要删除的文档idDeleteRequest
61083);//发送请求xxclient.delete(wwrequest,RequestOptions.DEFAULT);
先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)能不能查询到。
运行HotelDocumentTest类的xxtestGetDocumentById方法
删除id为61083的文档(相当于删除mysql中id为某个数的那一行)。
运行HotelDocumentTest类的wwtestDeleteDocument方法
验证。
再次执行第二步也就是运行HotelDocumentTest类的xxtestGetDocumentById方法
在上面的6、7、8、9节中我们一直都是操作一条id为61083的文档(相当于数据库表的某一行)。
我们如何把mysql的更多数据导入进es的索引库(相当于mysql的表)呢下面就来学习批量把文档导入进索引库
1、利用mybatis-plus把MySQL中的酒店数据查询出来
3、利用RestClient中bulk批处理方法实现批量新增文档
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
{//向数据库批量查询酒店数据list方法表示查询数据库的所有数据ListHotel
xxhotelService.list();//创建RequestBulkRequest
BulkRequest();//准备参数实际上就是添加多个新增的Requestfor
{//把遍历拿到的每个kkhotels转换为文档类型的数据HotelDoc
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浏览器页面进行查询
上面我们导入了很多文档(相当于数据库的行很多行)下面我们将着重学习使用DSL对这些文档数据进行查询
https://elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl。
DSL是用来查询文档的
Elasticsearch提供了基于JSON的DSL来定义查询简单说就是用json来描述查询条件然后发送给es服务最后es服务基于查询条件把结果返回给我们
根据精确的词条值去查找数据一般是查找keyword、数值、日期、boolean等类型的字段。
这些字段是不需要分词的但是依旧会建立倒排索引把字段的整体内容作为一个词条并存入倒排索引。
在查找的时候也就不需要分词直接把搜索的内容去跟倒排索引匹配即可
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
存在一个问题我们明明查询的是所有文档查询结果也显示查询出所有的文档了为什么上图右侧鼠标往下拉最多才只有10条文档数据呢
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
全文检索查询分为下面两种会对用户输入内容进行分词之后再进行匹配。
也就是利用分词器对用户输入内容进行分词然后去倒排索引库中匹配。
全文检索查询的一种会对用户输入的内容进行分词然后去倒排索引库检索
具体操作如下为了让大家知道gghotel索引库有哪些字段我把当初建立gghotel索引库的类先放出来
我要解释一下上面有个字段叫xxALL那个字段是当时自定义的不清楚的话可回去看
xxALL的作用如下图相当于一个大的字段里面存放了几个小字段优点是我们可以在这个大的字段里面搜索到多个小字段的信息
然后我们就正式开始全文检索查询输入如下。
注意xxALL换成其它字段也没事例如换成name字段。
正常来说我们检索name字段就只在那么字段检索匹配的分词文档但是在XXALL字段里面检索时也会检索到name、brand、business字段原因如上面那个图的copy_to属性
multi_match(也就是multi_match_query查询):
输入如下DSL语句表示查询查询business、brand、name字段中包含如家的文档满足一个字段即可
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器
精确查询一般是查找keyword、数值、日期、boolean等类型字段。
所以不会对搜索条件分词。
精确查询常见的有两种:
输入如下DSL语句表示查找price字段满足200~300数值的文档注意字段类型不能是binary也就是price字段的类型不能是binary
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器
查询附近酒店、附近出租车、搜索附近的人。
使用方式有很多种介绍如下
查询geo_point值落在某个矩形范围的所有文档用两个点来围成的矩形范围
具体操作如下但是为了让大家知道gghotel索引库有哪些字段我把当初建立gghotel索引库的类先放出来
上面的xxlocation字段类型必须是geo_point否则该字段不能用于地理查询
输入如下DSL语句。
表示查找xxlocation字段在(31.25±15km,121.5±15km)范围内的文档
上面学的全文检索查询、精确查询、地理查询这三种查询在es当中都称为简单查询下面我们将学习复合查询。
复合查询可以其它简单查询组合起来实现更复杂的搜索逻辑其中就有
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器
词条时某个文档要是都能匹配这两个词条那么在所有被搜索出来的文档当中这个文档的位置就最靠前简单说就是越匹配就排名越靠前
输入如下DSL语句表示在name字段哪个文档的匹配度高排名就靠前
上面只是简单演了相关性打分中的函数算分查询文档与搜索关键字的相关度越高打分就越高排名就越靠前。
不过有的时候我们希望人为地去控制控制文档的排名例如某些文档我们就希望排名靠前一点算分高一点此时就需要使用函数算分查询下面就来学习
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器
query)’可以在原始的相关性算分的基础上加以修改得到一个想要的算分从而去影响文档的排名语法如下
布尔查询不会去修改算分而是把多个查询语句组合成一起形成新查询这些被组合的查询语句被称为子查询。
子查询的组合方式有如下四种
#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
输入如下DSL语句表示搜索名字包含如家价格不高于400在坐标31.21,121.5周围10km范围内的文档
must表示匹配条件(注意写在must里面就会参与算分也就是查询出来的score值会更高)must_not表示取反filter表示过滤
elasticsearch(称为es)支持对搜索的结果进行排序默认是根据
#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
输入如下DSL语句表示对所有的文档根据评分(score)进行降序排序如果评分相同就根据价格(price)升序排序
上图的_score算分为null是因为我们如果做了排序那么打分就没有意义了所以es就会放弃打分不再做相关性算分提高效率
输入如下DSL语句表示找到(121.66053,28.28811)周围的文档并按照距离进行升序排序
下面两种写法都是一样的注意第二种写法前面写的是纬度后面写的是经度
上图的_score算分为null是因为我们如果做了排序那么打分就没有意义了所以es就会放弃打分不再做相关性算分提高效率
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作elasticsearch(称为es)默认情况下只返回前10
条数据。
而如果要查询更多数据就需要修改分页参数分页参数包括from和size语法如下
输入如下DSL语句表示对所有的文档根据价格(price)进行升序排序每次分页显示20条数据看的是第六页size默认是10表示一页显示多少条文档。
from默认是0表示你要看的是第一页
上面是基础的分页用法下面来详细了解es的分页。
es的底层使用的是倒排索引是不利于做分页的es采用的是逻辑上的分页就会导致当是分布式的时候就会产生下面的问题因此es限制结果集最多为10000
ES是分布式的所以会面临深度分页的问题。
例如按price排序后获取from990size10的数据如下图
百度在这方面最多能查76页每页显示十条。
京东在这方面最多能查第100页所以深度分页我们不需要担心10000的限制足够了。
但是如果说一定要去解决深度分页问题的话ES提供了两种解决方案(两种分页方式)如下
https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html
分页时需要排序原理是从上一次的排序值开始查询下一页数据。
官方推荐使用的方式。
缺点:
没有随机翻页需求的搜索例如手机向下滚动翻页。
虽然没有查询上限但是size不能超过10000
创建了名为gghotel的索引库导入了批量文档。
然后开始下面的操作
要一致。
如果我们写的是不一致的话就需要修改为falsepre_tags:
用来标记高亮字段的前置标签es会帮我们把标签加在关键字上。
默认是empost_tags:
用来标记高亮字段的后置标签es会帮我们把标签加在关键字上。
默认是/em}}}
上面的查询文档都是依赖kibana在浏览器页面使用DSL语句去查询es如何用java去查询es里面的文档(数据)呢
我们通过match_all查询来演示基本的API注意下面演示的是
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
在src/test/java/cn.itcast.hotel目录新建HotelSearchTest类写入如下
org.elasticsearch.action.search.SearchRequest;
org.elasticsearch.action.search.SearchResponse;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestClient;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.index.query.QueryBuilders;
org.junit.jupiter.api.AfterEach;
org.junit.jupiter.api.BeforeEach;
org.junit.jupiter.api.Test;import
xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void
RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void
{this.xxclient.close();}//-----------------------------上面是初始化下面是查询文档-快速入门的测试-------------------------------------------Testvoid
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。
QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.matchAllQuery());//发送请求SearchResponse
RequestOptions.DEFAULT);//在控制台输出结果System.out.println(xxresponse);}
把控制台里面我们需要的数据解析出来。
返回的数据很多我们主要是解析hits里面的数据就行了
把HotelSearchTest类修改为如下主要的修改是sout之前做了一次解析拿到我们想要的数据
org.elasticsearch.action.search.SearchRequest;
org.elasticsearch.action.search.SearchResponse;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestClient;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.index.query.QueryBuilders;
org.elasticsearch.search.SearchHit;
org.elasticsearch.search.SearchHits;
org.junit.jupiter.api.AfterEach;
org.junit.jupiter.api.BeforeEach;
org.junit.jupiter.api.Test;import
xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void
RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void
{this.xxclient.close();}//-----------------------------上面是初始化下面是查询文档-快速入门的测试-------------------------------------------Testvoid
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。
QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.matchAllQuery());//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。
QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.matchAllQuery());//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。
QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.matchQuery(name,如家));//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。
QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.multiMatchQuery(如家,name,business));//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
查询的解析部分的代码都是相同的所以我们可以对解析部分的代码进行抽取(ctrlaltm)如下
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
精确查询一般是查找keyword、数值、日期、boolean等类型字段。
所以不会对搜索条件分词。
精确查询常见的有两种:
java代码和DSL语句的对应关系如下图。
gt表示大于gte表示大于等于lt表示小于lte表示小于等于
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。
QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.termQuery(city,上海));//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。
QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.rangeQuery(price).gte(100).lte(150));//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//创建布尔查询BoolQueryBuilder
QueryBuilders.boolQuery();//添加must条件xxboolQuery.must(QueryBuilders.termQuery(city,上海));//添加filter条件xxboolQuery.filter(QueryBuilders.rangeQuery(price).lte(200));//把上面的布尔对象传进来就可以生效了xxrequest.source().query(xxboolQuery);//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
【geo_distance查询】在HotelSearchTest类添加如下
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//创建一个地理位置查询构造器指定了要查询字段的是xxlocationGeoDistanceQueryBuilder
QueryBuilders.geoDistanceQuery(xxlocation);xxgeoQuery.point(31.25,
DistanceUnit.KILOMETERS);//设置查询的半径距离和单位这里的
将查询条件添加到查询构造器对象searchSourceBuilder.query(xxgeoQuery);//
将查询构造器的对象添加到查询请求对象xxrequest中就可以生效了xxrequest.source(searchSourceBuilder);//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
上面是各种查询的学习当我们把文档查询出来的时候接下来就是对文档的处理也就是你要把查询结果怎么展示出来。
API以及对应的DSL语句如下图
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
{//页面、每页大小。
如果你要翻第二页就把下面的xxpage改成2int
5;//准备Request对象要查询哪个索引库SearchRequest
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
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象
高亮API包括请求DSL构建和结果解析两部分API和对应的DSL语句如下图下图只是构建再下面还有解析高亮必须由构建解析才能实现
创建了名为gghotel的索引库然后开始下面的操作。
如果需要浏览器操作es那就不需要启动kibana容器
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(gghotel);//【构建】//查询name字段的文档xxrequest.source().query(QueryBuilders.matchQuery(name,上海));//对查询出来的文档的特定字段进行高亮显示xxrequest.source().highlighter(new
HighlightBuilder().field(name).requireFieldMatch(true).preTags(em).postTags(/em));//发送请求SearchResponse
RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits
xxresponse.getHits();//获取总条数long
xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]
xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for
xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//【解析】获取高亮结果MapString,
xxhit.getHighlightFields();//使用CollectionUtils工具类进行判空避免空指针if
(!CollectionUtils.isEmpty(xxhighlightFields)){//根据字段名获取高亮结果HighlightField
xxhighlightFields.get(name);//判断name不为空if
xxhighlightField.getFragments()[0].string();//覆盖非高亮结果xxhotelDoc.setName(xxname);}}//最终输出System.out.println(每个HotelDoc对象
这个案例我做了两遍才做出来了第一遍排了一上午的错所以很有必要进行环境准备下面我将带领你对一下我的环境全网最详细的自创笔记
企业部署一般都是采用Linux操作系统而其中又数CentOS发行版占比最多因此我们接下来会在CentOS下安装Docker
https://cowtransfer.com/s/56423adc78374f
https://cowtransfer.com/s/b4c8fcb5c15244
https://cowtransfer.com/s/7dcb0c66154d45
https://cowtransfer.com/s/567413055c9a4f
在VMware虚拟机安装CentOS7系统安装完成之后使用finalshell远程软件进行远程连接然后安装yum工具执行如下
\https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.reposed
s/download.docker.com/mirrors.aliyun.com\/docker-ce/g
/etc/yum.repos.d/docker-ce.repoyum
执行如下安装docker稍等片刻docker即可安装成功。
docker-ce为社区免费版本
由于Docker应用需要用到各种端口逐一去修改防火墙设置会非常麻烦所以学习期间直接关闭防火墙即可
配置docker镜像仓库设置为国内的镜像仓库以后在docker里面下载东西的时候速度会更快。
分别执行如下命令
[https://93we6x1g.mirror.aliyuncs.com]
在刚刚创建的文件夹里面新建daemon.json文件并写入花括号里面的数据
加载es镜像。
采用elasticsearch的7.12.1版本的镜像这个镜像体积有800多MB所以需要在Windows上下载链接安装包下载下来是一个es的镜像tar包然后传到CentOS7的/root目录
https://cowtransfer.com/s/c84ac851b9ba44
https://cowtransfer.com/s/a76d8339d7ba4d
把在CentOS7的/root目录的es镜像导入到docker
创建并运行es容器容器名称就叫es。
在docker(也叫Docker大容器、Docker主机、宿主机)根据es镜像来创建es容器
es-data:/usr/share/elasticsearch/data
es-plugins:/usr/share/elasticsearch/plugins
打开database软件把tb_hotel.sql文件导入进你的数据库
https://cowtransfer.com/s/68c94a66d17248
把下载好的hotel-demo.zip压缩包解压得到hotel-demo文件夹在idea打开hotel-demo
hotel-demo.zip下载:https://cowtransfer.com/s/36ac0a9f9d9043
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/
--/parentgroupIdcn.itcast.demo/groupIdartifactIdhotel-demo/artifactIdversion0.0.1-SNAPSHOT/versionnamehotel-demo/namedescriptionDemo
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
把mysql的数据导入进es我们需要使用前面学的es提供的RestClient就可以通过java代码创建索引库并往这个索引库导入文档(文档就是数据的意思)
写完就运行xxcreateHotelIndex方法把索引库创建出来
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestClient;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.client.indices.CreateIndexRequest;
org.elasticsearch.common.xcontent.XContentType;
org.junit.jupiter.api.AfterEach;
org.junit.jupiter.api.BeforeEach;
cn.itcast.hotel.constants.HotelConstants.xxMappingTemplate;public
xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void
RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void
{this.xxclient.close();}//删除索引库(如果下面创建hotel索引库的时候出现已存在那么就执行这里的删除操作把hotel索引库删掉再创建)Testvoid
{//创建Request对象指定要删除哪个索引库DeleteIndexRequest
DeleteIndexRequest(hotel);//发送请求xxclient.indices().delete(gghotel,
RequestOptions.DEFAULT);}//使用xxclient对象向es创建索引库Testvoid
{//创建Request对象自定义索引库名称为hotelCreateIndexRequest
CreateIndexRequest(hotel);//准备请求的参数:
DSL语句gghotel.source(xxMappingTemplate,
XContentType.JSON);//发送请求xxclient.indices().create(gghotel,
constants.HotelConstants类为es准备数据写入如下
cn.itcast.hotel.constants;public
src/main/java/cn.itcast.hotel/pojo
com.baomidou.mybatisplus.annotation.IdType;
com.baomidou.mybatisplus.annotation.TableId;
com.baomidou.mybatisplus.annotation.TableName;
hotel.getStarName();this.business
hotel.getBusiness();this.location
HotelDocumentTest类用于把mysql的数据批量导入进es写入如下
写完就运行testBulkRequest方法把数据往索引库里面批量导入
cn.itcast.hotel.service.IHotelService;
org.elasticsearch.action.bulk.BulkRequest;
org.elasticsearch.action.index.IndexRequest;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestClient;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.common.xcontent.XContentType;
org.junit.jupiter.api.AfterEach;
org.junit.jupiter.api.BeforeEach;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.boot.test.context.SpringBootTest;import
xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void
RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void
{this.xxclient.close();}Autowired//注入写好的IHotelService接口用于去数据库查询数据private
{//向数据库批量查询酒店数据list方法表示查询数据库的所有数据ListHotel
xxhotelService.list();//创建RequestBulkRequest
BulkRequest();//准备参数实际上就是添加多个新增的Requestfor
{//把遍历拿到的每个kkhotels转换为文档类型的数据HotelDoc
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);}}
二、定义controller接口接收页面请求调用IHotelService的search方法
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
com.baomidou.mybatisplus.extension.service.IService;public
(3)、把HotelDemoApplication启动类修改为如下
com.baomidou.mybatisplus.annotation.DbType;
com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
org.elasticsearch.client.RestClient;
org.elasticsearch.client.RestHighLevelClient;
org.mybatis.spring.annotation.MapperScan;
org.springframework.boot.SpringApplication;
org.springframework.boot.autoconfigure.SpringBootApplication;
org.springframework.context.annotation.Bean;MapperScan(cn.itcast.hotel.mapper)
{SpringApplication.run(HotelDemoApplication.class,
args);}Bean//注入es提供的RestHighLevelClient类public
RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}}
(4)、在src/main/java/cn.itcast.hotel目录新建web.HotelController类写入如下
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
cn.itcast.hotel.service.IHotelService;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.web.bind.annotation.PostMapping;
org.springframework.web.bind.annotation.RequestBody;
org.springframework.web.bind.annotation.RequestMapping;
org.springframework.web.bind.annotation.RestController;RestController
{//注入项目准备好的IHotelService接口Autowiredprivate
hotelService;PostMapping(/list)//使用RequestBody注解接收前端的请求。
PageResult、RequestParams是我们刚刚定义的实体类public
三、定义IHotelService中的search方法利用match查询实现根据关键字搜索酒店信息
cn.itcast.hotel.service.impl;import
cn.itcast.hotel.mapper.HotelMapper;
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
cn.itcast.hotel.service.IHotelService;
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
org.elasticsearch.action.search.SearchRequest;
org.elasticsearch.action.search.SearchResponse;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.index.query.QueryBuilders;
org.elasticsearch.search.SearchHit;
org.elasticsearch.search.SearchHits;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Service;
{Autowired//注入在引导类声明好的Beanprivate
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(hotel);//【关键字搜索功能】String
params.getKey();//前端传过来的搜索关键字//判断前端传的key是否为空避免空指针if
{//matchAllQuery方法表示查es的全部文档不需要条件request.source().query(QueryBuilders.matchAllQuery());}
{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空request.source().query(QueryBuilders.matchQuery(all,
params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int
params.getSize();//前端传过来的每页大小。
其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page
size).size(size);//发送请求。
下面那行的search报红线我们不能抛出要捕获一下SearchResponse
RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。
并把解析作为结果返回return
RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private
searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]
searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc
hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);hotels.add(hotelDoc);}//封装返回return
四、运行HotelDemoApplication引导类浏览器访问
1、修改RequestParams类添加brand、city、startName、minPrice、maxPrice等参数
2、修改HotelService类的search方法的实现在关键字搜索时如果brand等参数存在就需要对其做过滤
3、注意多个条件之间是AND关系组合多条件用BooleanQuery
5、city精确匹配brand精确匹配startName精确匹配price范围过滤
cn.itcast.hotel.service.impl;import
cn.itcast.hotel.mapper.HotelMapper;
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
cn.itcast.hotel.service.IHotelService;
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
org.elasticsearch.action.search.SearchRequest;
org.elasticsearch.action.search.SearchResponse;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.index.query.BoolQueryBuilder;
org.elasticsearch.index.query.QueryBuilders;
org.elasticsearch.search.SearchHit;
org.elasticsearch.search.SearchHits;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Service;
{Autowired//注入在引导类声明好的Beanprivate
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(hotel);//【构建BooleanQuery】BoolQueryBuilder
QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String
params.getKey();//前端传过来的搜索关键字//用的是must精确查找if
{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}
{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,
key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()
!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()
!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()
!params.getStarName().equals()){//注意下面那行的是starName不要写成startNameboolQuery.filter(QueryBuilders.termQuery(starName,params.getStarName()));}//价格range范围过滤注意判空。
gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()
null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//这一步必须有request.source().query(boolQuery);//【分页功能】int
params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int
params.getSize();//前端传过来的每页大小。
其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page
size).size(size);//发送请求。
下面那行的search报红线我们不能抛出要捕获一下SearchResponse
RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。
并把解析作为结果返回return
RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private
searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]
searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc
hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);hotels.add(hotelDoc);}//封装返回return
实现前端页面点击定位后会将你所在的位置发送给后台前端的请求信息如下会向后端发送location参数。
1、修改RequestParams参数接收来自前端的location字段
2、修改HotelService类的search方法的业务逻辑如果location有值就添加根据geo_distance排序的功能
maxPrice;//地理位置查询的字段前端会把location传给我们private
pic;//地理位置查询相关的字段。
distance字段用于保存解析后的距离值private
hotel.getStarName();this.business
hotel.getBusiness();this.location
cn.itcast.hotel.service.impl;import
cn.itcast.hotel.mapper.HotelMapper;
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
cn.itcast.hotel.service.IHotelService;
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
org.elasticsearch.action.search.SearchRequest;
org.elasticsearch.action.search.SearchResponse;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.common.geo.GeoPoint;
org.elasticsearch.common.unit.DistanceUnit;
org.elasticsearch.index.query.BoolQueryBuilder;
org.elasticsearch.index.query.QueryBuilders;
org.elasticsearch.search.SearchHit;
org.elasticsearch.search.SearchHits;
org.elasticsearch.search.sort.SortBuilders;
org.elasticsearch.search.sort.SortOrder;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Service;
{Autowired//注入在引导类声明好的Beanprivate
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(hotel);//【构建BooleanQuery】BoolQueryBuilder
QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String
params.getKey();//前端传过来的搜索关键字//用的是must精确查找if
{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}
{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,
key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()
!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()
!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()
!params.getStarName().equals()){boolQuery.filter(QueryBuilders.termQuery(starName,params.getStarName()));}//价格range范围过滤注意判空。
gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()
null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//这一步必须有request.source().query(boolQuery);//【分页功能】int
params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int
params.getSize();//前端传过来的每页大小。
其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page
size).size(size);//【地理排序功能】String
params.getLocation();//对前端传的location进行判断是否为空if
!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
RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。
并把解析作为结果返回return
RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private
searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]
searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc
hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//【地理坐标查询的解析】,通过getSortValues方法来获取排序值得到的是多个值也就是数组我们只需要一个值Object[]
sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}hotels.add(hotelDoc);}//封装返回return
重启HotelDemoApplication引导类浏览器访问http://localhost:8089/。
点击定位按钮查看是否能查询出距离自己最近的酒店并显示米数
让指定的酒店在搜索结果中排名置顶。
我们给需要置顶的酒店文档添加一个标记。
然后利用function
2、挑选几个你喜欢的酒店给它的文档数据添加isAD字段值为true
3、修改HotelService类的search方法添加function
Score查询可以控制文档的相关性算分java代码以及对应DSL语句如下图
pic;//地理位置查询相关的字段。
distance字段用于保存解析后的距离值private
hotel.getStarName();this.business
hotel.getBusiness();this.location
使用DSL语句为索引库增加字段由于使用DSL语句需要在浏览器使用kibana所以我们把docker里面的kibana容器运行一下
DSL语句表示给某个id文档添加新字段。
id不一定要跟我一样随便去mysql数据库找几个id就行
cn.itcast.hotel.service.impl;import
cn.itcast.hotel.mapper.HotelMapper;
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
cn.itcast.hotel.service.IHotelService;
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
org.elasticsearch.action.search.SearchRequest;
org.elasticsearch.action.search.SearchResponse;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.common.geo.GeoPoint;
org.elasticsearch.common.unit.DistanceUnit;
org.elasticsearch.index.query.BoolQueryBuilder;
org.elasticsearch.index.query.QueryBuilders;
org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
org.elasticsearch.search.SearchHit;
org.elasticsearch.search.SearchHits;
org.elasticsearch.search.sort.SortBuilders;
org.elasticsearch.search.sort.SortOrder;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Service;
{Autowired//注入在引导类声明好的Beanprivate
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(hotel);//【构建BooleanQuery下面那行的boolQuery是原始查询】BoolQueryBuilder
QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String
params.getKey();//前端传过来的搜索关键字//用的是must精确查找if
{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}
{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,
key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()
!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()
!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()
!params.getStarName().equals()){boolQuery.filter(QueryBuilders.termQuery(starName,params.getStarName()));}//价格range范围过滤注意判空。
gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()
null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//【构建functionScoreQuery实现算分查询。
对应的是广告置顶功能】FunctionScoreQueryBuilder
//原始查询需要进行相关性算分的查询QueryBuilders.functionScoreQuery(boolQuery,//function
FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function
FunctionScoreQueryBuilder.FilterFunctionBuilder(//过滤简单说就是满足isAD字段为true的文档就会参与算分QueryBuilders.termQuery(isAD,true),//要使用什么算分函数下面那行使用的是weightFactorFunction加权算分最终score分数越大排名就越前ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10)
});//这一步必须有request.source().query(functionScoreQuery);//【分页功能】int
params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int
params.getSize();//前端传过来的每页大小。
其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page
size).size(size);//【地理排序功能】String
params.getLocation();//对前端传的location进行判断是否为空if
!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
RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。
并把解析作为结果返回return
RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private
searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]
searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc
hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//【地理坐标查询的解析】,通过getSortValues方法来获取排序值得到的是多个值也就是数组我们只需要一个值Object[]
sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}hotels.add(hotelDoc);}//封装返回return
重启HotelDemoApplication引导类浏览器访问http://localhost:8089/。
查看我们指定的那两个酒店是否置顶
高亮API包括请求DSL构建和结果解析两部分API和对应的DSL语句如下图下图只是构建再下面还有解析高亮必须由构建解析才能实现
cn.itcast.hotel.service.impl;import
cn.itcast.hotel.mapper.HotelMapper;
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
cn.itcast.hotel.service.IHotelService;
com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
org.elasticsearch.action.search.SearchRequest;
org.elasticsearch.action.search.SearchResponse;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.common.geo.GeoPoint;
org.elasticsearch.common.unit.DistanceUnit;
org.elasticsearch.index.query.BoolQueryBuilder;
org.elasticsearch.index.query.QueryBuilders;
org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
org.elasticsearch.search.SearchHit;
org.elasticsearch.search.SearchHits;
org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
org.elasticsearch.search.sort.SortBuilders;
org.elasticsearch.search.sort.SortOrder;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Service;
{Autowired//注入在引导类声明好的Beanprivate
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(hotel);//【构建BooleanQuery下面那行的boolQuery是原始查询】BoolQueryBuilder
QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String
params.getKey();//前端传过来的搜索关键字//用的是must精确查找if
{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}
{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,
key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()
!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()
!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()
!params.getStarName().equals()){boolQuery.filter(QueryBuilders.termQuery(starName,params.getStarName()));}//价格range范围过滤注意判空。
gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()
null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//【构建functionScoreQuery实现算分查询。
对应的是广告置顶功能】FunctionScoreQueryBuilder
//原始查询需要进行相关性算分的查询QueryBuilders.functionScoreQuery(boolQuery,//function
FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function
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
params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int
params.getSize();//前端传过来的每页大小。
其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page
size).size(size);//【地理排序功能】String
params.getLocation();//对前端传的location进行判断是否为空if
!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
RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。
并把解析作为结果返回return
RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private
searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]
searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc
hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//【地理坐标查询的解析】,通过getSortValues方法来获取排序值得到的是多个值也就是数组我们只需要一个值Object[]
sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}//【解析】获取高亮结果MapString,
hit.getHighlightFields();//使用CollectionUtils工具类进行判空避免空指针if
(!CollectionUtils.isEmpty(xxhighlightFields)){//根据字段名获取高亮结果HighlightField
xxhighlightFields.get(all);//判断name不为空if
xxhighlightField.getFragments()[0].string();//覆盖非高亮结果hotelDoc.setName(xxname);}}hotels.add(hotelDoc);}//封装返回return
重启HotelDemoApplication引导类浏览器访问http://localhost:8089/。
查看是否能将搜索词高亮显示
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
可以实现对文档数据的统计、分析、运算。
聚合常见的有如下三类。
注意聚合的字段必然是不分词的原因:
按照文档字段值分组。
(我们下面会演示这个按照品牌进行分桶)○Date
同时求max、min、avg、sum。
(我们下面会演示这个按照品牌进行求评分最值和平均值)
现在我们要统计所有数据中的酒店品牌有多少种此时我们可以根据酒店品牌的名称做聚合由于品牌是字段也就是要对字段值做分组采用的是TermAggregation聚合类型为term类型DSL示例如下
具体操作浏览器输入如下表示对不同的品牌进行聚合也就是不同的品牌为不同的桶相同的品牌放进一个桶里面
如何修改默认的排序规则我们不希望是按照找出来的文档总条数降序排序。
默认情况下Bucket会统计Bucket内的文档数量记为_count并且按照count降序排序。
我们如果要修改结果排序方式的话只需要加一个order属性如下
我们上面是对整个索引库的数据做聚合搜索如果索引库本身有庞大数据的话对整个索引库的聚合搜索是对内存消耗非常大我们希望自定义聚合的搜索范围也就是限定要聚合的文档范围只需要添加query条件即可如下
Metrics聚合也就是度量聚合。
例如我们要求获取每个品牌的用户评分的min最小值、max最大值、avg平均值。
注意不是整个索引库的所有酒店(文档)进行求值所以要结合上一节的Bucket聚合一起使用
具体操作浏览器输入如下表示对品牌(父聚合)的评分(子聚合)进行求值
如果我们还需要对结果按照评分的平均值再去做个排序看一下哪个酒店评价最高注意我们是在同里面做排序也就是排序要写在terms里面
如何在java代码使用RestClient来实现聚合。
java代码以及对应的DSL语句如下图
SearchRequest(hotel);//准备DSL。
设置sizexxrequest.source().size(0);//准备DSL。
聚合语句xxrequest.source().aggregation(AggregationBuilders.terms(BrandAggMyName)
//自定义聚合名称为BrandAggMyName.field(brand).size(10));//发出请求SearchResponse
RequestOptions.DEFAULT);//【解析】//解析聚合结果Aggregations
xxresponse.getAggregations();//根据聚合名称获取聚合结果Terms
xxaggregations.get(BrandAggMyName);//获取桶(buckets)获取的是一个集合List?
xxbrandTerms.getBuckets();//遍历集合取出每一个bucketfor
xxbucket.getKeyAsString();System.out.println(key);}
中搜索页面的品牌、城市等信息不应该是在页面写死而是通过聚合索引库中的酒店数据得来的
//在给出来的案例图中左侧加深的字是key右侧浅灰的字是value并且右侧的value有多个值。
所以我们使用Map集合并且value是list集合
SearchRequest(hotel);//准备DSL。
设置sizexxrequest.source().size(0);//准备DSL。
聚合语句。
对多个字段进行聚合buildAggregation(xxrequest);//发出请求SearchResponse
RequestOptions.DEFAULT);//【解析】//解析聚合结果MapString,
xxresponse.getAggregations();//1、根据名称获取品牌的结果ListString
getAggByName(xxaggregations,BrandAggMyName);//把品牌的结果信息放入mapyyresult.put(品牌,xxbrandList);//2、根据名称获取城市的结果ListString
getAggByName(xxaggregations,cityAggMyName);//把城市的结果信息放入mapyyresult.put(城市,xxcityList);//3、根据名称获取星级的结果ListString
getAggByName(xxaggregations,starAggMyName);//把星级的结果信息放入mapyyresult.put(星级,xxstarList);//返回yyresultreturn
xxaggregations.get(kkaggName);//获取桶(buckets)获取的是一个集合List?
xxbrandTerms.getBuckets();//遍历xxbuckets集合取出每一个key。
把取到的key放到xxbrandList集合ListString
xxbucket.getKeyAsString();xxbrandList.add(xxkey);}return
{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方法
hotelService.xxfilters();System.out.println(filters);
1、可以看到请求参数与之前search时的RequestParam完全一致这是在限定聚合时的文档范围。
用户搜索“外滩”价格在300~600那聚合必须是在这个搜索条件基础上完成
3、修改IUserService#getFilters()方法添加RequestParam参数
3、修改getFilters方法的业务聚合时添加query条件
把IHotelService接口修改为如下原本xxfilters方法是没有参数的现在我们要给xxfilters方法添加参数
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
com.baomidou.mybatisplus.extension.service.IService;
希尔顿]}*///在给出来的案例图中左侧加深的字是key右侧浅灰的字是value并且右侧的value有多个值。
所以我们使用Map集合并且value是list集合MapString,
cn.itcast.hotel.service.impl;import
cn.itcast.hotel.mapper.HotelMapper;
cn.itcast.hotel.pojo.PageResult;
cn.itcast.hotel.pojo.RequestParams;
cn.itcast.hotel.service.IHotelService;
com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
org.elasticsearch.action.search.SearchRequest;
org.elasticsearch.action.search.SearchResponse;
org.elasticsearch.client.RequestOptions;
org.elasticsearch.client.RestClient;
org.elasticsearch.client.RestHighLevelClient;
org.elasticsearch.common.geo.GeoPoint;
org.elasticsearch.common.unit.DistanceUnit;
org.elasticsearch.index.query.BoolQueryBuilder;
org.elasticsearch.index.query.QueryBuilders;
org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
org.elasticsearch.search.SearchHit;
org.elasticsearch.search.SearchHits;
org.elasticsearch.search.aggregations.AggregationBuilders;
org.elasticsearch.search.aggregations.Aggregations;
org.elasticsearch.search.aggregations.bucket.terms.Terms;
org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
org.elasticsearch.search.sort.SortBuilders;
org.elasticsearch.search.sort.SortOrder;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Service;
{Autowired//注入在引导类声明好的Beanprivate
{//准备Request对象要查询哪个索引库SearchRequest
SearchRequest(hotel);buildBasicQuery(params,
params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int
params.getSize();//前端传过来的每页大小。
其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page
size).size(size);//【地理排序功能】String
params.getLocation();//对前端传的location进行判断是否为空if
!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
RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。
并把解析作为结果返回return
{//【构建BooleanQuery下面那行的boolQuery是原始查询】BoolQueryBuilder
QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String
params.getKey();//前端传过来的搜索关键字//用的是must精确查找if
{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}
{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,
key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()
!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,
params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()
!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,
params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()
!params.getStarName().equals()){boolQuery.filter(QueryBuilders.termQuery(starName,
params.getStarName()));}//价格range范围过滤注意判空。
gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()
null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//【构建functionScoreQuery实现算分查询。
对应的是广告置顶功能】FunctionScoreQueryBuilder
//原始查询需要进行相关性算分的查询QueryBuilders.functionScoreQuery(boolQuery,//function
FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function
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
searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]
searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc
hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc
HotelDoc.class);//【地理坐标查询的解析】,通过getSortValues方法来获取排序值得到的是多个值也就是数组我们只需要一个值Object[]
sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}//【解析】获取高亮结果MapString,
hit.getHighlightFields();//使用CollectionUtils工具类进行判空避免空指针if
(!CollectionUtils.isEmpty(xxhighlightFields)){//根据字段名获取高亮结果HighlightField
xxhighlightFields.get(all);//判断name不为空if
xxhighlightField.getFragments()[0].string();//覆盖非高亮结果hotelDoc.setName(xxname);}}hotels.add(hotelDoc);}//封装返回return
PageResult(total,hotels);}//--------------------------------------------下面是多条件聚合-----------------------------------------Overridepublic
SearchRequest(hotel);//添加query查询信息也就是限定聚合的范围buildBasicQuery(params,
xxrequest);//准备DSL。
设置sizexxrequest.source().size(0);//准备DSL。
聚合语句。
对多个字段进行聚合buildAggregation(xxrequest);//发出请求SearchResponse
RequestOptions.DEFAULT);//【解析】//解析聚合结果MapString,
xxresponse.getAggregations();//1、根据名称获取品牌的结果ListString
getAggByName(xxaggregations,BrandAggMyName);//把品牌的结果信息放入mapyyresult.put(brand,xxbrandList);//2、根据名称获取城市的结果ListString
getAggByName(xxaggregations,cityAggMyName);//把城市的结果信息放入mapyyresult.put(city,xxcityList);//3、根据名称获取星级的结果ListString
getAggByName(xxaggregations,starAggMyName);//把星级的结果信息放入mapyyresult.put(starName,xxstarList);//返回yyresultreturn
xxaggregations.get(kkaggName);//获取桶(buckets)获取的是一个集合List?
xxbrandTerms.getBuckets();//遍历xxbuckets集合取出每一个key。
把取到的key放到xxbrandList集合ListString
xxbucket.getKeyAsString();xxbrandList.add(xxkey);}return
xxbrandList;}//把聚合的代码抽取出来private
{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引导类浏览器查看是否功能正常
下载下来是py.zip压缩包解压之后得到一个py文件夹把这个文件夹上传到你CentOS7的
/var/lib/docker/volumes/es-plugins/_data
#启动kibana容器。
由于在网页使用DSL去操作es所以就需要kibana
输入如下DSL语句会把文本的每个中文分成对应的拼音也会把整段文本的拼音首字母拼在一起
刚才我们看到了拼音分词器的效果但是并不能用于生产环境还存在一些问题
Elasticsearch中的分词器(analyzer)的组成包含三部分
在tokenizer之前对文本进行处理。
例如删除字符、替换字符
将tokenizer输出的词条做进一步处理。
例如大小写转换、同义词处理、拼音处理等
拼音分词器不会分词那么就先用ik分词器分好词后再交给拼音分词器
解决如下注意必须在创建索引库的时候做例如创建一个名为test的索引库
过滤器类型这里是pinyinkeep_full_pinyin:
//不允许单个字来分拼音keep_joined_full_pinyin:
//全拼给什么字或词就把什么写成拼音keep_original:
//保留中文解决了拼音分词器分词后全是拼音的问题limit_first_letter_length:
true,none_chinese_pinyin_tokenize:
具体操作如下注意自定义分词器只对当前索引库有效因为我们是写在settings属性里面
#启动kibana容器因为我们需要在浏览器执行DSL语句去操作es
输入如下DSL语句表示创建名为test的索引库实现自定义分词器也就是对分词器做一些限制、开放一些限制
true,limit_first_letter_length:
true,none_chinese_pinyin_tokenize:
测试自定义分词器(也就是ik分词器拼音分词器)。
第一种测试方法
测试自定义分词器(也就是ik分词器拼音分词器)。
第二种测试方法通过往test库插入文档、然后查询插入的文档的方式来测试
其实还是有个问题如下图在搜索中文时却搜索出了同音字我们不想搜索出同音字解决在下一节会学
因此字段在创建倒排索引时应该用自定义分词器(my_analyzer);
字段在搜索时应该使用is_smart分词器。
对应的DSL语句如下
//在mappings里面指定两个分词器properties:
//analyzer表示创建索引时使用这个my_analyzer分词器search_analyzer:
//search_analyzer表示在搜索时使用这个search_analyzer分词器}}}
#启动kibana容器因为我们需要在浏览器执行DSL语句去操作es
输入如下DSL语句重新创建test索引库这次我们在mappings属性里面多添加了一个分词器search_analyzer:
true,limit_first_letter_length:
true,none_chinese_pinyin_tokenize:
测试。
同样是往test库创建两个文档然后使用中文去匹配如果不出现同音字就表示验证通过
Suggester查询来实现自动补全功能。
这个查询会匹配以用户输入内容开头的词条并返回。
为了提高补全查询的效率对于文档中字段的类型有一些约束
//不是query而是suggest表示自动补全title_suggest:
要自动补全查询的字段注意该字段必须是completion类型skip_duplicates:
具体操作如下注意自定义分词器只对当前索引库有效因为我们是写在settings属性里面
#启动kibana容器因为我们需要在浏览器执行DSL语句去操作es
输入如下DSL语句表示重新创建test索引库并为索引库添加3条文档数据
我们接下来要把自动补全功能应用在前面学习的黑马旅游案例中这节我们就先学习如下几点
3、索引库添加一个新字段suggestion类型为completion类型使用自定义的分词器
输入如下DSL语句。
重新创建一个hotel索引库只是创建一个空的hotel索引库
true,limit_first_letter_length:
true,none_chinese_pinyin_tokenize:
由于某个文档(酒店)可能包括多个商圈如下图所以在下一步我们会在HotelDoc类进行切割后存入suggestion
在上面的DSL语句中我们还多创建了一个suggestion字段所以对应的我们需要在黑马旅游案例的java代码中修改实体类
lombok.NoArgsConstructor;import
pic;//地理位置查询相关的字段。
distance字段用于保存解析后的距离值private
isAD;//用于自动补全的类型该类型在es中必须是completion类型该类型在java中就要写成数组(我们写成集合)private
hotel.getStarName();this.business
hotel.getBusiness();this.location
hotel.getPic();if(this.business.contains(/)){//如果同一文档的business商圈有多个为了自动补全的分词效果更明显我们需要分割一下String[]
this.business.split(/);this.suggestion
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类修改为如下
lombok.NoArgsConstructor;import
pic;//地理位置查询相关的字段。
distance字段用于保存解析后的距离值private
isAD;//用于自动补全的类型该类型在es中必须是completion类型该类型在java中就要写成数组(我们写成集合)private
hotel.getStarName();this.business
hotel.getBusiness();this.location
hotel.getPic();if(this.business.contains(/)){//如果同一文档的business商圈有多个为了自动补全的分词效果更明显我们需要分割一下String[]
this.business.split(/);this.suggestion
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索引库
在HotelSearchTest类添加如下并运行xxtestSuggest方法
{//准备Request查的是哪个索引库SearchRequest
SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new
SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi,//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(h)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse
RequestOptions.DEFAULT);//解析结果System.out.println(xxresponse);
结果解析。
把HotelSearchTest类的xxtestSuggest方法修改为如下并运行xxtestSuggest方法
{//准备Request查的是哪个索引库SearchRequest
SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new
SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi,
//为这个自定义补全查询起一个名字//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(h)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse
RequestOptions.DEFAULT);//解析结果Suggest
xxresponse.getSuggest();//根据补全查询名称获取补全结果CompletionSuggestion
xxsuggest.getSuggestion(zidingyizidongbuquanmingzi);//获取xxsuggestionsListCompletionSuggestion.Entry.Option
xxsuggestions.getOptions();//遍历for
(CompletionSuggestion.Entry.Option
xxoption.getText().toString();System.out.println(text);}
我们刚刚使用RestAPI实现了自动补全查询现在我们要把这个功能应用在
{//准备Request查的是哪个索引库SearchRequest
SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new
SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi,
//为这个自定义补全查询起一个名字//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(xxprefix)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse
RequestOptions.DEFAULT);//解析结果Suggest
xxresponse.getSuggest();//根据补全查询名称获取补全结果CompletionSuggestion
xxsuggest.getSuggestion(zidingyizidongbuquanmingzi);//获取xxsuggestionsListCompletionSuggestion.Entry.Option
xxsuggestions.getOptions();//准备好一个集合当自动补全时相关词就会在这个集合并返回给前端ListString
ArrayList(xxoptions.size());//遍历for
(CompletionSuggestion.Entry.Option
xxoption.getText().toString();//把自动补全时查到的相关词放到我们准备好的集合里面xxlist.add(text);}return
xxgetSuggestions(RequestParam(key)
hotelService.xxgetSuggestions(xxprefix);
测试。
查看自动补全查询是否在搜索框生效访问http://localhost:8089/
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback