当前位置: 首页 > news >正文

网站建设设计公司 知乎网站开发 居易国际

网站建设设计公司 知乎,网站开发 居易国际,上海软件有限公司,做网站怎么更新静态页视频地址#xff1a;微服务#xff08;SpringCloudRabbitMQDockerRedis搜索分布式#xff09; 初识ES-什么是elasticsearch#xff08;P77#xff0c;P78#xff09; 1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎#xff0c;具备非常多强大功能…  视频地址微服务SpringCloudRabbitMQDockerRedis搜索分布式  初识ES-什么是elasticsearchP77P78 1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎具备非常多强大功能可以帮助我们从海量数据中快速找到需要的内容 例如 在GitHub搜索代码 在电商网站搜索商品 在百度搜索答案 在打车软件搜索附近的车 2.ELK技术栈 elasticsearch结合kibana、Logstash、Beats也就是elastic stackELK。被广泛应用在日志数据分析、实时监控等领域 而elasticsearch是elastic stack的核心负责存储、搜索、分析数据。 3.elasticsearch和lucene elasticsearch底层是基于lucene来实现的。 Lucene是一个Java语言的搜索引擎类库是Apache公司的顶级项目由DougCutting于1999年研发。官网地址Apache Lucene - Welcome to Apache Lucene 。 elasticsearch的发展历史 2004年Shay Banon基于Lucene开发了Compass 2010年Shay Banon 重写了Compass取名为Elasticsearch。 4.为什么不是其他搜索技术 目前比较知名的搜索引擎技术排名 虽然在早期Apache Solr是最主要的搜索引擎技术但随着发展elasticsearch已经渐渐超越了Solr独占鳌头 5.总结 什么是elasticsearch 一个开源的分布式搜索引擎可以用来实现搜索、日志统计、分析、系统监控等功能 什么是elastic stackELK 是以elasticsearch为核心的技术栈包括beats、Logstash、kibana、elasticsearch 什么是Lucene 是Apache的开源搜索引擎类库提供了搜索引擎的核心API 结论 初识ES-倒排索引P79 倒排索引的概念是基于MySQL这样的正向索引而言的。 1.正向索引 那么什么是正向索引呢例如给下表tb_goods中的id创建索引 如果是根据id查询那么直接走索引查询速度非常快。 但如果是基于title做模糊查询只能是逐行扫描数据流程如下 1用户搜索数据条件是title符合%手机% 2逐行获取数据比如id为1的数据 3判断数据中的title是否符合用户搜索条件 4如果符合则放入结果集不符合则丢弃。回到步骤1 逐行扫描也就是全表扫描随着数据量增加其查询效率也会越来越低。当数据量达到数百万时就是一场灾难。 2.倒排索引 倒排索引中有两个非常重要的概念 文档Document用来搜索的数据其中的每一条数据就是一个文档。例如一个网页、一个商品信息 词条Term对文档数据或用户搜索数据利用某种算法分词得到的具备含义的词语就是词条。例如我是中国人就可以分为我、是、中国人、中国、国人这样的几个词条 创建倒排索引是对正向索引的一种特殊处理流程如下 将每一个文档的数据利用算法分词得到一个个词条 创建表每行数据包括词条、词条所在文档id、位置等信息 因为词条唯一性可以给词条创建索引例如hash表结构索引 如图 倒排索引的搜索流程如下以搜索华为手机为例 1用户输入条件华为手机进行搜索。 2对用户输入内容分词得到词条华为、手机。 3拿着词条在倒排索引中查找可以得到包含词条的文档id1、2、3。 4拿着文档id到正向索引中查找具体文档。 如图 虽然要先查询倒排索引再查询倒排索引但是无论是词条、还是文档id都建立了索引查询速度非常快无需全表扫描。 3.正向和倒排 那么为什么一个叫做正向索引一个叫做倒排索引呢 正向索引是最传统的根据id索引的方式。但根据词条查询时必须先逐条获取每个文档然后判断文档中是否包含所需要的词条是根据文档找词条的过程。 而倒排索引则相反是先找到用户要搜索的词条根据词条得到保护词条的文档的id然后根据id获取文档。是根据词条找文档的过程。 是不是恰好反过来了 那么两者方式的优缺点是什么呢 正向索引 优点 可以给多个字段创建索引 根据索引字段搜索、排序速度非常快 缺点 根据非索引字段或者索引字段中的部分词条查找时只能全表扫描。 倒排索引 优点 根据词条搜索、模糊搜索时速度非常快 缺点 只能给词条创建索引而不是字段 无法根据字段做排序 初识ES-es与mysql的概念对比P80 elasticsearch中有很多独有的概念与mysql中略有差别但也有相似之处。 1.文档和字段 elasticsearch是面向文档Document存储的可以是数据库中的一条商品数据一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中 而Json文档中往往包含很多的字段Field类似于数据库中的列。 2.索引和映射 索引Index就是相同类型的文档的集合。 例如 所有用户文档就可以组织在一起称为用户的索引 所有商品的文档可以组织在一起称为商品的索引 所有订单的文档可以组织在一起称为订单的索引 因此我们可以把索引当做是数据库中的表。 数据库的表会有约束信息用来定义表的结构、字段的名称、类型等信息。因此索引库中就有映射mapping是索引中文档的字段约束信息类似表的结构约束。 3.mysql与elasticsearch 我们统一的把mysql与elasticsearch的概念做一下对比 MySQLElasticsearch说明TableIndex索引(index)就是文档的集合类似数据库的表(table)RowDocument文档Document就是一条条的数据类似数据库中的行Row文档都是JSON格式ColumnField字段Field就是JSON文档中的字段类似数据库中的列ColumnSchemaMappingMapping映射是索引中文档的约束例如字段类型约束。类似数据库的表结构SchemaSQLDSLDSL是elasticsearch提供的JSON风格的请求语句用来操作elasticsearch实现CRUD 是不是说我们学习了elasticsearch就不再需要mysql了呢 并不是如此两者各自有自己的擅长支出 Mysql擅长事务类型操作可以确保数据的安全和一致性 Elasticsearch擅长海量数据的搜索、分析、计算 因此在企业中往往是两者结合使用 对安全性要求较高的写操作使用mysql实现 对查询性能要求较高的搜索需求使用elasticsearch实现 两者再基于某种方式实现数据的同步保证一致性 初识ES-安装esP81 链接安装es  初识ES-安装kibanaP82 链接安装kibana  初识ES-安装IK分词器P83 链接安装IK分词器  初识ES-IK分词器的拓展和停用词典P84 总结 操作索引库-mapping属性P85 索引库就类似数据库表mapping映射就类似表的结构。 我们要向es中存储数据必须先创建“库”和“表”。 mapping映射属性 mapping是对索引库中文档的约束常见的mapping属性包括 type字段数据类型常见的简单类型有 字符串text可分词的文本、keyword精确值例如品牌、国家、ip地址 数值long、integer、short、byte、double、float、允许某个字段有多个值例score字段多个值但是只有一种数据类型 布尔boolean 日期date 对象object index是否创建索引默认为true analyzer使用哪种分词器 properties该字段的子字段 对应的每个字段映射mapping age类型为 integer参与搜索因此需要index为true无需分词器 weight类型为float参与搜索因此需要index为true无需分词器 isMarried类型为boolean参与搜索因此需要index为true无需分词器 info类型为字符串需要分词因此是text参与搜索因此需要index为true分词器可以用ik_smart email类型为字符串但是不需要分词因此是keyword不参与搜索因此需要index为false无需分词器 score虽然是数组但是我们只看元素的类型类型为float参与搜索因此需要index为true无需分词器 name类型为object需要定义多个子属性 name.firstName类型为字符串但是不需要分词因此是keyword参与搜索因此需要index为true无需分词器 name.lastName类型为字符串但是不需要分词因此是keyword参与搜索因此需要index为true无需分词器 操作索引库-创建索引库P86 这里我们统一使用Kibana编写DSL的方式来演示。 创建索引库和映射 基本语法 请求方式PUT 请求路径/索引库名可以自定义 请求参数mapping映射 格式 PUT /索引库名称 {mappings: {properties: {字段名:{type: text,analyzer: ik_smart},字段名2:{type: keyword,index: false},字段名3:{properties: {子字段: {type: keyword}}},// ...略}} } 示例 PUT /heima {mappings: {properties: {info:{type: text,analyzer: ik_smart},email:{type: keyword,index: falsae},name:{properties: {firstName: {type: keyword}}},// ... 略}} } properties包含的子字段 操作索引库-查询、删除、修改索引库P87 1.查询索引库 基本语法 请求方式GET 请求路径/索引库名 请求参数无 格式 GET /索引库名 示例 2.修改索引库 倒排索引结构虽然不复杂但是一旦数据结构改变比如改变了分词器就需要重新创建倒排索引这简直是灾难。因此索引库一旦创建无法修改mapping。 虽然无法修改mapping中已有的字段但是却允许添加新的字段到mapping中因为不会对倒排索引产生影响。 语法说明 PUT /索引库名/_mapping {properties: {新字段名:{type: integer}} } 示例 3.删除索引库 语法 请求方式DELETE 请求路径/索引库名 请求参数无 格式 DELETE /索引库名 在kibana中测试 总结 索引库操作有哪些 创建索引库PUT /索引库名 查询索引库GET /索引库名 删除索引库DELETE /索引库名 添加字段PUT /索引库名/_mapping 文档操作-新增、查询、删除文档P88 1.新增文档 语法 POST /索引库名/_doc/文档id {字段1: 值1,字段2: 值2,字段3: {子属性1: 值3,子属性2: 值4},// ... } 示例 POST /heima/_doc/1 {info: 黑马程序员Java讲师,email: zyitcast.cn,name: {firstName: 云,lastName: 赵} } 响应 2.查询文档 根据rest风格新增是post查询应该是get不过查询一般都需要条件这里我们把文档id带上。 语法 GET /{索引库名称}/_doc/{id} 通过kibana查看数据 GET /heima/_doc/1 查看结果 3.删除文档 删除使用DELETE请求同样需要根据id进行删除 语法 DELETE /{索引库名}/_doc/id值 示例 # 根据id删除数据 DELETE /heima/_doc/1 结果 文档操作-修改文档P89 4.修改文档 修改有两种方式 全量修改直接覆盖原来的文档 增量修改修改文档中的部分字段 1.全量修改 全量修改是覆盖原来的文档其本质是 根据指定的id删除文档 新增一个相同id的文档 注意如果根据id删除时id不存在第二步的新增也会执行也就从修改变成了新增操作了。 语法 PUT /{索引库名}/_doc/文档id {字段1: 值1,字段2: 值2,// ... 略 } 示例 PUT /heima/_doc/1 {info: 黑马程序员高级Java讲师,email: zyitcast.cn,name: {firstName: 云,lastName: 赵} } 2.增量修改 增量修改是只修改指定id匹配的文档中的部分字段。 语法 POST /{索引库名}/_update/文档id {doc: {字段名: 新的值,} } 示例 POST /heima/_update/1 {doc: {email: ZhaoYunitcast.cn} } 5.总结 文档操作有哪些 创建文档POST /{索引库名}/_doc/文档id { json文档 } 查询文档GET /{索引库名}/_doc/文档id 删除文档DELETE /{索引库名}/_doc/文档id 修改文档 全量修改PUT /{索引库名}/_doc/文档id { json文档 } 增量修改POST /{索引库名}/_update/文档id { doc: {字段}} RestClient操作索引库-导入demoP90 ES官方提供了各种不同语言的客户端用来操作ES。这些客户端的本质就是组装DSL语句通过http请求发送给ES。官方文档地址Elasticsearch Clients | Elastic 其中的Java Rest Client又包括两种 Java Low Level Rest Client Java High Level Rest Client 我们学习的是Java HighLevel Rest Client客户端API 4.0.导入Demo工程 1.导入数据 首先导入课前资料提供的数据库数据 数据结构如下 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; 2.导入项目 然后导入课前资料提供的项目: 项目结构如图 RestClient操作索引库-hotel数据结构分析P91 3.mapping映射分析 创建索引库最关键的是mapping映射而mapping映射要考虑的信息包括 字段名 字段数据类型 是否参与搜索 是否需要分词 如果分词分词器是什么 其中 字段名、字段数据类型可以参考数据表结构的名称和类型 是否参与搜索要分析业务来判断例如图片地址就无需参与搜索 是否分词呢要看内容内容如果是一个整体就无需分词反之则要分词 分词器我们可以统一使用ik_max_word 来看下酒店数据的索引库结构: PUT /hotel --新建索引 {mappings: {properties: {id: { --id在es中就是字符串类型且整体不可分割不分词type: keyword --文本类型与text区别text可分词。keyword是精确值可以直接做索引查询},name:{type: text,analyzer: ik_max_word, --分词器copy_to: all --将字段copy进all中基于all创建索引},address:{type: keyword,index: false --不搜索},price:{ type: integer --不写indexfalse表示参与搜索},score:{type: integer},brand:{type: keyword,copy_to: all },city:{type: keyword,copy_to: all},starName:{type: keyword},business:{type: keyword},location:{type: geo_point --坐标点下面有讲解},pic:{type: keyword,index: false},all:{ --copy进的alltype: text,analyzer: ik_max_word --分词器}}} } 几个特殊字段说明 location地理坐标里面包含精度、纬度 all一个组合字段其目的是将多字段的值 利用copy_to合并提供给用户搜索 地理坐标说明 copy_to说明 RestClient操作索引库-初始化RestClientP92 4.初始化RestClient 在elasticsearch提供的API中与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中必须先完成这个对象的初始化建立与elasticsearch的连接。 分为三步 1引入es的RestHighLevelClient依赖 dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactId /dependency 2因为SpringBoot默认的ES版本是7.6.2所以我们需要覆盖默认的ES版本 propertiesjava.version1.8/java.versionelasticsearch.version7.12.1/elasticsearch.version /properties 3初始化RestHighLevelClient 初始化的代码如下 RestHighLevelClient client new RestHighLevelClient(RestClient.builder(HttpHost.create(http://192.168.150.101:9200) )); 这里为了单元测试方便我们创建一个测试类HotelIndexTest然后将初始化的代码编写在BeforeEach方法中 package cn.itcast.hotel; ​ import org.apache.http.HttpHost; 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 client; ​BeforeEachvoid setUp() {this.client new RestHighLevelClient(RestClient.builder(HttpHost.create(http://192.168.150.101:9200)));} ​AfterEachvoid tearDown() throws IOException {this.client.close();} } RestClient操作索引库-创建索引库P93 1.代码解读 代码分为三步 1创建Request对象。因为是创建索引库的操作因此Request是CreateIndexRequest。 2添加请求参数其实就是DSL的JSON参数部分。因为json字符串很长这里是定义了静态字符串常量MAPPING_TEMPLATE让代码看起来更加优雅。 3发送请求client.indices()方法的返回值是IndicesClient类型封装了所有与索引库操作有关的方法。 创建索引库的API如下 2.完整示例 在hotel-demo的cn.itcast.hotel.constants包下创建一个类定义mapping映射的JSON字符串常量 package cn.itcast.hotel.constants; ​ public class HotelConstants {public static final String MAPPING_TEMPLATE {\n \mappings\: {\n   \properties\: {\n     \id\: {\n       \type\: \keyword\\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     },\n     \score\:{\n       \type\: \integer\\n     },\n     \brand\:{\n       \type\: \keyword\,\n       \copy_to\: \all\\n     },\n     \city\:{\n       \type\: \keyword\,\n       \copy_to\: \all\\n     },\n     \starName\:{\n       \type\: \keyword\\n     },\n     \business\:{\n       \type\: \keyword\\n     },\n     \location\:{\n       \type\: \geo_point\\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测试类中编写单元测试实现创建索引 Test void createHotelIndex() throws IOException {// 1.创建Request对象CreateIndexRequest request new CreateIndexRequest(hotel);// 2.准备请求的参数DSL语句request.source(MAPPING_TEMPLATE, XContentType.JSON);// 3.发送请求client.indices().create(request, RequestOptions.DEFAULT); } RestClient操作索引库-删除和判断索引库P94 1.删除索引库 删除索引库的DSL语句非常简单 DELETE /hotel 与创建索引库相比 请求方式从PUT变为DELTE 请求路径不变 无请求参数 所以代码的差异注意体现在Request对象上。依然是三步走 1创建Request对象。这次是DeleteIndexRequest对象 2准备参数。这里是无参 3发送请求。改用delete方法 在hotel-demo中的HotelIndexTest测试类中编写单元测试实现删除索引 Test void testDeleteHotelIndex() throws IOException {// 1.创建Request对象DeleteIndexRequest request new DeleteIndexRequest(hotel);// 2.发送请求client.indices().delete(request, RequestOptions.DEFAULT); } 2.判断索引库是否存在 判断索引库是否存在本质就是查询对应的DSL是 GET /hotel 因此与删除的Java代码流程是类似的。依然是三步走 1创建Request对象。这次是GetIndexRequest对象 2准备参数。这里是无参 3发送请求。改用exists方法 Test void testExistsHotelIndex() throws IOException {// 1.创建Request对象GetIndexRequest request new GetIndexRequest(hotel);// 2.发送请求boolean exists client.indices().exists(request, RequestOptions.DEFAULT);// 3.输出System.err.println(exists ? 索引库已经存在 : 索引库不存在); } 总结 JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。 索引库操作的基本步骤 初始化RestHighLevelClient 创建XxxIndexRequest。XXX是Create、Get、Delete 准备DSL Create时需要其它是无参 发送请求。调用RestHighLevelClient#indices().xxx()方法xxx是create、exists、delete RestClient操作文档-新增文档P95 RestClient操作文档 为了与索引库操作分离我们再次参加一个测试类做两件事情 初始化RestHighLevelClient 我们的酒店数据在数据库需要利用IHotelService去查询所以注入这个接口 package cn.itcast.hotel; ​ import cn.itcast.hotel.pojo.Hotel; import cn.itcast.hotel.service.IHotelService; 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 {Autowiredprivate IHotelService hotelService; ​private RestHighLevelClient client; ​BeforeEachvoid setUp() {this.client new RestHighLevelClient(RestClient.builder(HttpHost.create(http://192.168.150.101:9200)));} ​AfterEachvoid tearDown() throws IOException {this.client.close();} } 我们要将数据库的酒店数据查询出来写入elasticsearch中。上述图片的步骤二 1.索引库实体类 数据库查询后的结果是一个Hotel类型的对象。结构如下 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; } 与我们的索引库结构存在差异 longitude和latitude需要合并为location 因此我们需要定义一个新的类型与索引库结构吻合 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();} } ​ 2.语法说明 新增文档的DSL语句如下 POST /{索引库名}/_doc/1 {name: Jack,age: 21 } 对应的java代码如图 可以看到与创建索引库类似同样是三步走 1创建Request对象 2准备请求参数也就是DSL中的JSON文档 3发送请求 变化的地方在于这里直接使用client.xxx()的API不再需要client.indices()了。 3.完整代码 我们导入酒店数据基本流程一致但是需要考虑几点变化 酒店数据来自于数据库我们需要先查询出来得到hotel对象 hotel对象需要转为HotelDoc对象 HotelDoc需要序列化为json格式 因此代码整体步骤如下 1根据id查询酒店数据Hotel 2将Hotel封装为HotelDoc 3将HotelDoc序列化为JSON 4创建IndexRequest指定索引库名和id 5准备请求参数也就是JSON文档 6发送请求 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testAddDocument() throws IOException {// 1.根据id查询酒店数据Hotel hotel hotelService.getById(61083L);// 2.转换为文档类型HotelDoc hotelDoc new HotelDoc(hotel);// 3.将HotelDoc转jsonString json JSON.toJSONString(hotelDoc); ​// 1.准备Request对象IndexRequest request new IndexRequest(hotel).id(hotelDoc.getId().toString());// 2.准备Json文档request.source(json, XContentType.JSON);// 3.发送请求client.index(request, RequestOptions.DEFAULT); } RestClient操作文档-查询文档P96 1.语法说明 查询的DSL语句如下 GET /hotel/_doc/{id} 非常简单因此代码大概分两步 准备Request对象 发送请求 不过查询的目的是得到结果解析为HotelDoc因此难点是结果的解析。完整代码如下 可以看到结果是一个JSON其中文档放在一个_source属性中因此解析就是拿到_source反序列化为Java对象即可。 与之前类似也是三步走 1准备Request对象。这次是查询所以是GetRequest 2发送请求得到结果。因为是查询这里调用client.get()方法 3解析结果就是对JSON做反序列化 2.完整代码 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testGetDocumentById() throws IOException {// 1.准备RequestGetRequest request new GetRequest(hotel, 61082);// 2.发送请求得到响应GetResponse response client.get(request, RequestOptions.DEFAULT);// 3.解析响应结果String json response.getSourceAsString(); ​HotelDoc hotelDoc JSON.parseObject(json, HotelDoc.class);System.out.println(hotelDoc); } RestClient操作文档-更新文档P97 1.语法说明 修改我们讲过两种方式 全量修改本质是先根据id删除再新增 增量修改修改文档中的指定字段值 在RestClient的API中全量修改与新增的API完全一致判断依据是ID 如果新增时ID已经存在则修改 如果新增时ID不存在则新增 这里不再赘述我们主要关注增量修改。 代码示例如图 与之前类似也是三步走 1准备Request对象。这次是修改所以是UpdateRequest 2准备参数。也就是JSON文档里面包含要修改的字段 3更新文档。这里调用client.update()方法  2.完整代码 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testUpdateDocument() throws IOException {// 1.准备RequestUpdateRequest request new UpdateRequest(hotel, 61083);// 2.准备请求参数request.doc(price, 952,starName, 四钻);// 3.发送请求client.update(request, RequestOptions.DEFAULT); } RestClient操作文档-删除文档P98 删除的DSL为是这样的 DELETE /hotel/_doc/{id} 与查询相比仅仅是请求方式从DELETE变成GET可以想象Java代码应该依然是三步走 1准备Request对象因为是删除这次是DeleteRequest对象。要指定索引库名和id 2准备参数无参 3发送请求。因为是删除所以是client.delete()方法 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testDeleteDocument() throws IOException {// 1.准备RequestDeleteRequest request new DeleteRequest(hotel, 61083);// 2.发送请求client.delete(request, RequestOptions.DEFAULT); } 总结 RestClient操作文档-批量导入文档P99 案例需求利用BulkRequest批量将数据库数据导入到索引库中。 步骤如下 利用mybatis-plus查询酒店数据 将查询到的酒店数据Hotel转换为文档类型数据HotelDoc 利用JavaRestClient中的BulkRequest批处理实现批量新增文档 1.语法说明 批量处理BulkRequest其本质就是将多个普通的CRUD请求组合在一起发送。 其中提供了一个add方法用来添加其他请求 可以看到能添加的请求包括 IndexRequest也就是新增 UpdateRequest也就是修改 DeleteRequest也就是删除 因此Bulk中添加了多个IndexRequest就是批量新增功能了。示例 其实还是三步走 1创建Request对象。这里是BulkRequest 2准备参数。批处理的参数就是其它Request对象这里就是多个IndexRequest 3发起请求。这里是批处理调用的方法为client.bulk()方法 我们在导入酒店数据时将上述代码改造成for循环处理即可。 2.完整代码 在hotel-demo的HotelDocumentTest测试类中编写单元测试 Test void testBulkRequest() throws IOException {// 批量查询酒店数据ListHotel hotels hotelService.list(); ​// 1.创建RequestBulkRequest request new BulkRequest();// 2.准备参数添加多个新增的Requestfor (Hotel hotel : hotels) {// 2.1.转换为文档类型HotelDocHotelDoc hotelDoc new HotelDoc(hotel);// 2.2.创建新增文档的Request对象request.add(new IndexRequest(hotel).id(hotelDoc.getId().toString()).source(JSON.toJSONString(hotelDoc), XContentType.JSON));}// 3.发送请求client.bulk(request, RequestOptions.DEFAULT); } 小结 文档操作的基本步骤 初始化RestHighLevelClient 创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk 准备参数Index、Update、Bulk时需要 发送请求。调用RestHighLevelClient#.xxx()方法xxx是index、get、update、delete、bulk 解析结果Get时需要 DSL查询语法-DSL查询分类和基本语法P100P101 elasticsearch最擅长的还是搜索和数据分析。 今天分别使用DSL和RestClient实现搜索。 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 DSL查询分类 Elasticsearch提供了基于JSON的DSLDomain Specific Language来定义查询。常见的查询类型包括 查询所有查询出所有数据一般测试用。例如match_all 全文检索full text查询利用分词器对用户输入内容分词然后去倒排索引库中匹配。例如 match_query multi_match_query 精确查询根据精确词条值查找数据一般是查找keyword、数值、日期、boolean等类型字段。例如 ids range term 地理geo查询根据经纬度查询。例如 geo_distance geo_bounding_box 复合compound查询复合查询可以将上述各种查询条件组合起来合并查询条件。例如 bool function_score 查询的语法基本一致 GET /indexName/_search {query: {查询类型: {查询条件: 条件值}} } 我们以查询所有为例其中 查询类型为match_all 没有查询条件 // 查询所有 GET /indexName/_search {query: {match_all: {}} } 其它查询无非就是查询类型、查询条件的变化。 DSL查询语法-全文检索查询P102 1.使用场景 全文检索查询的基本流程如下 对用户搜索的内容做分词得到词条 根据词条去倒排索引库中匹配得到文档id 根据文档id找到文档返回给用户 比较常用的场景包括 商城的输入框搜索 百度输入框搜索 例如京东 因为是拿着词条去匹配因此参与搜索的字段也必须是可分词的text类型的字段。 2.基本语法 常见的全文检索查询包括 match查询单字段查询 multi_match查询多字段查询任意一个字段符合条件就算符合查询条件 match查询语法如下 GET /indexName/_search {query: {match: {FIELD: TEXT}} } mulit_match语法如下 GET /indexName/_search {query: {multi_match: {query: TEXT,fields: [FIELD1,  FIELD12]}} } 3.示例 match查询示例 multi_match查询示例可以多字段查询 可以看到两种查询结果是一样的为什么 因为我们将brand、name、business值都利用copy_to复制到了all字段中。因此你根据三个字段搜索和根据all字段搜索效果当然一样了。 但是搜索字段越多对查询性能影响越大因此建议采用copy_to然后单字段查询的方式。 4.总结 match和multi_match的区别是什么 match根据一个字段查询 multi_match根据多个字段查询参与查询字段越多查询性能越差 DSL查询语法-精确查询P103 精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有 term根据词条精确值查询 range根据值的范围查询 1.term查询 因为精确查询的字段搜是不分词的字段因此查询的条件也必须是不分词的词条。查询时用户输入的内容跟自动值完全匹配时才认为符合条件。如果用户输入的内容过多反而搜索不到数据。 语法说明 // term查询 GET /indexName/_search {query: {term: {FIELD: {value: VALUE}}} } 示例 当我搜索的是精确词条时能正确查询出结果 但是当我搜索的内容不是词条而是多个词语形成的短语时反而搜索不到 2.range查询 范围查询一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。 基本语法 // range查询 GET /indexName/_search {query: {range: {FIELD: {gte: 10, // 这里的gte代表大于等于gt则代表大于lte: 20 // lte代表小于等于lt则代表小于}}} } 示例 3.总结 精确查询常见的有哪些 term查询根据词条精确匹配一般搜索keyword类型、数值类型、布尔类型、日期类型字段 range查询根据数值范围查询可以是数值、日期的范围 DSL查询语法-地理查询P104 所谓的地理坐标查询其实就是根据经纬度查询官方文档Geo queries | Elasticsearch Guide [8.9] | Elastic 常见的使用场景包括 携程搜索我附近的酒店 滴滴搜索我附近的出租车 微信搜索我附近的人 附近的酒店 附近的车 1.矩形范围查询 矩形范围查询也就是geo_bounding_box查询查询坐标落在某个矩形范围的所有文档 查询时需要指定矩形的左上、右下两个点的坐标然后画出一个矩形落在该矩形内的都是符合条件的点。 语法如下 // geo_bounding_box查询 GET /indexName/_search {query: {geo_bounding_box: {FIELD: {top_left: { // 左上点lat: 31.1,lon: 121.5},bottom_right: { // 右下点lat: 30.9,lon: 121.7}}}} } 这种并不符合“附近的人”这样的需求所以我们就不做了。 2.附近查询 附近查询也叫做距离查询geo_distance查询到指定中心点小于某个距离值的所有文档。 换句话来说在地图上找一个点作为圆心以指定距离为半径画一个圆落在圆内的坐标都算符合条件 语法说明: // geo_distance 查询 GET /indexName/_search {query: {geo_distance: {distance: 15km, // 半径FIELD: 31.21,121.5 // 圆心}} } 示例 我们先搜索陆家嘴附近15km的酒店 发现共有47家酒店。 然后把半径缩短到3公里 可以发现搜索到的酒店数量减少到了5家。 DSL查询语法-相关性算分P105 复合compound查询复合查询可以将其它简单查询组合起来实现更复杂的搜索逻辑。常见的有两种 fuction score算分函数查询可以控制文档相关性算分控制文档排名 bool query布尔查询利用逻辑关系组合多个其它的查询实现复杂搜索 1.相关性算分 当我们利用match查询时文档结果会根据与搜索词条的关联度打分_score返回结果时按照分值降序排列。 例如我们搜索 虹桥如家结果如下 [{_score : 17.850193,_source : {name : 虹桥如家酒店真不错,}},{_score : 12.259849,_source : {name : 外滩如家酒店真不错,}},{_score : 11.91091,_source : {name : 迪士尼如家酒店真不错,}} ] 在elasticsearch中早期使用的打分算法是TF-IDF算法公式如下 在后来的5.1版本升级中elasticsearch将算法改进为BM25算法公式如下 TF-IDF算法有一各缺陷就是词条频率越高文档得分也会越高单个词条对文档影响较大。而BM25则会让单个词条的算分有一个上限曲线更加平滑 小结elasticsearch会根据词条和文档的相关度做打分算法由两种 TF-IDF算法 BM25算法elasticsearch5.1版本后采用的算法 DSL查询语法-FunctionScoreQueryP106 根据相关度打分是比较合理的需求但合理的不一定是产品经理需要的。 以百度为例你搜索的结果中并不是相关度越高排名越靠前而是谁掏的钱多排名就越靠前。如图 要想认为控制相关性算分就需要利用elasticsearch中的function score 查询了。 1语法说明 function score 查询中包含四部分内容 原始查询条件query部分基于这个条件搜索文档并且基于BM25算法给文档打分原始算分query score) 过滤条件filter部分符合该条件的文档才会重新算分 算分函数符合filter条件的文档要根据这个函数做运算得到的函数算分function score有四种函数 weight函数结果是常量 field_value_factor以文档中的某个字段值作为函数结果 random_score以随机数作为函数结果 script_score自定义算分函数算法 运算模式算分函数的结果、原始查询的相关性算分两者之间的运算方式包括 multiply相乘 replace用function score替换query score 其它例如sum、avg、max、min function score的运行流程如下 1根据原始条件查询搜索文档并且计算相关性算分称为原始算分query score 2根据过滤条件过滤文档 3符合过滤条件的文档基于算分函数运算得到函数算分function score 4将原始算分query score和函数算分function score基于运算模式做运算得到最终结果作为相关性算分。 因此其中的关键点是 过滤条件决定哪些文档的算分被修改 算分函数决定函数算分的算法 运算模式决定最终算分结果 2示例 需求给“如家”这个品牌的酒店排名靠前一些 翻译一下这个需求转换为之前说的四个要点 原始条件不确定可以任意变化 过滤条件brand 如家 算分函数可以简单粗暴直接给固定的算分结果weight 运算模式比如求和 因此最终的DSL语句如下 GET /hotel/_search {query: {function_score: {query: {  .... }, // 原始查询可以是任意条件functions: [ // 算分函数{filter: { // 满足的条件品牌必须是如家term: {brand: 如家}},weight: 2 // 算分权重为2}],boost_mode: sum // 加权模式求和}} } 测试在未添加算分函数时如家得分如下 添加了算分函数后如家得分就提升了 3小结 function score query定义的三要素是什么 过滤条件哪些文档要加分 算分函数如何计算function score 加权方式function score 与 query score如何运算 DSL查询语法-BooleanQueryP107 布尔查询是一个或多个查询子句的组合每一个子句就是一个子查询。子查询的组合方式有 must必须匹配每个子查询类似“与” should选择性匹配子查询类似“或” must_not必须不匹配不参与算分类似“非” filter必须匹配不参与算分 比如在搜索酒店时除了关键字搜索外我们还可能根据品牌、价格、城市等字段做过滤 每一个不同的字段其查询的条件、方式都不一样必须是多个不同的查询而要组合这些查询就必须用bool查询了。 需要注意的是搜索时参与打分的字段越多查询的性能也越差。因此这种多条件查询时建议这样做 搜索框的关键字搜索是全文检索查询使用must查询参与算分 其它过滤条件采用filter查询。不参与算分 1语法示例 GET /hotel/_search {query: {bool: {must: [{term: {city: 上海 }}],should: [{term: {brand: 皇冠假日 }},{term: {brand: 华美达 }}],must_not: [{ range: { price: { lte: 500 } }}],filter: [{ range: {score: { gte: 45 } }}]}} } 2示例 需求搜索名字包含“如家”价格不高于400在坐标31.21,121.5周围10km范围内的酒店。 分析 名称搜索属于全文检索查询应该参与算分。放到must中 价格不高于400用range查询属于过滤条件不参与算分。放到must_not中 周围10km范围内用geo_distance查询属于过滤条件不参与算分。放到filter中 3小结 bool查询有几种逻辑关系 must必须匹配的条件可以理解为“与” should选择性匹配的条件可以理解为“或” must_not必须不匹配的条件不参与打分 filter必须匹配的条件不参与打分 搜索结果处理-排序P108 搜索的结果可以按照用户指定的方式去处理或展示。 elasticsearch默认是根据相关度算分_score来排序但是也支持自定义方式对搜索结果排序。可以排序字段类型有keyword类型、数值类型、地理坐标类型、日期类型等。 1.普通字段排序 keyword、数值、日期类型排序的语法基本一致。 语法 GET /indexName/_search {query: {match_all: {}},sort: [{FIELD: desc  // 排序字段、排序方式ASC、DESC}] } 排序条件是一个数组也就是可以写多个排序条件。按照声明的顺序当第一个条件相等时再按照第二个条件排序以此类推 示例 需求描述酒店数据按照用户评价score)降序排序评价相同的按照价格(price)升序排序 2.地理坐标排序 地理坐标排序略有不同。 语法说明 GET /indexName/_search {query: {match_all: {}},sort: [{_geo_distance : {FIELD : 纬度经度, // 文档中geo_point类型的字段名、目标坐标点order : asc, // 排序方式unit : km // 排序的距离单位}}] } 这个查询的含义是 指定一个坐标作为目标点 计算每一个文档中指定字段必须是geo_point类型的坐标 到目标点的距离是多少 根据距离排序 示例 需求描述实现对酒店数据按照到你的位置坐标的距离升序排序 提示获取你的位置的经纬度的方式获取鼠标点击经纬度-地图属性-示例中心-JS API 2.0 示例 | 高德地图API 假设我的位置是31.034661121.612282寻找我周围距离最近的酒店。 搜索结果处理-分页P109 elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果 from从第几个文档开始 size总共查询几个文档 类似于mysql中的limit ?, ? 1.基本的分页 分页的基本语法如下 GET /hotel/_search {query: {match_all: {}},from: 0, // 分页开始的位置默认为0size: 10, // 期望获取的文档总数sort: [{price: asc}] } 2.深度分页问题 现在我要查询990~1000的数据查询逻辑要这么写 GET /hotel/_search {query: {match_all: {}},from: 990, // 分页开始的位置默认为0size: 10, // 期望获取的文档总数sort: [{price: asc}] } 这里是查询990开始的数据也就是 第990~第1000条 数据。 不过elasticsearch内部分页时必须先查询 0~1000条然后截取其中的990 ~ 1000的这10条 查询TOP1000如果es是单点模式这并无太大影响。 但是elasticsearch将来一定是集群例如我集群有5个节点我要查询TOP1000的数据并不是每个节点查询200条就可以了。 因为节点A的TOP200在另一个节点可能排到10000名以外了。 因此要想获取整个集群的TOP1000必须先查询出每个节点的TOP1000汇总结果后重新排名重新截取TOP1000。 那如果我要查询9900~10000的数据呢是不是要先查询TOP10000呢那每个节点都要查询10000条汇总到内存中 当查询分页深度较大时汇总数据过多对内存和CPU会产生非常大的压力因此elasticsearch会禁止from size 超过10000的请求。 针对深度分页ES提供了两种解决方案官方文档 search after分页时需要排序原理是从上一次的排序值开始查询下一页数据。官方推荐使用的方式。 scroll原理将排序后的文档id形成快照保存在内存。官方已经不推荐使用。 3.小结 分页查询的常见实现方案以及优缺点 from size 优点支持随机翻页 缺点深度分页问题默认查询上限from size是10000 场景百度、京东、谷歌、淘宝这样的随机翻页搜索 after search 优点没有查询上限单次查询的size不超过10000 缺点只能向后逐页查询不支持随机翻页 场景没有随机翻页需求的搜索例如手机向下滚动翻页 scroll 优点没有查询上限单次查询的size不超过10000 缺点会有额外内存消耗并且搜索结果是非实时的 场景海量数据的获取和迁移。从ES7.1开始不推荐建议用 after search方案。 搜索结果处理-高亮P110 1.高亮原理 什么是高亮显示呢 我们在百度京东搜索时关键字会变成红色比较醒目这叫高亮显示 高亮显示的实现分为两步 1给文档中的所有关键字都添加一个标签例如em标签 2页面给em标签编写CSS样式 2.实现高亮 高亮的语法 GET /hotel/_search {query: {match: {FIELD: TEXT // 查询条件高亮一定要使用全文检索查询}},highlight: {fields: { // 指定要高亮的字段FIELD: {pre_tags: em,  // 用来标记高亮字段的前置标签post_tags: /em // 用来标记高亮字段的后置标签}}} } 注意 高亮是对关键字高亮因此搜索条件必须带有关键字而不能是范围这样的查询。 默认情况下高亮的字段必须与搜索指定的字段一致否则无法高亮 如果要对非搜索字段高亮则需要添加一个属性required_field_matchfalse 示例 3.总结 查询的DSL是一个大的JSON对象包含下列属性 query查询条件 from和size分页条件 sort排序条件 highlight高亮条件 示例 RestClient查询文档-快速入门P111 文档的查询同样适用昨天学习的 RestHighLevelClient对象基本步骤包括 1准备Request对象 2准备请求参数 3发起请求 4解析响应 我们以match_all查询为例 1.发起查询请求 代码解读 第一步创建SearchRequest对象指定索引库名 第二步利用request.source()构建DSLDSL中可以包含查询、分页、排序、高亮等 query()代表查询条件利用QueryBuilders.matchAllQuery()构建一个match_all查询的DSL 第三步利用client.search()发送请求得到响应 这里关键的API有两个一个是request.source()其中包含了查询、排序、分页、高亮等所有功能 另一个是QueryBuilders其中包含match、term、function_score、bool等各种查询 2.解析响应 响应结果的解析 elasticsearch返回的结果是一个JSON字符串结构包含 hits命中的结果 total总条数其中的value是具体的总条数值 max_score所有结果中得分最高的文档的相关性算分 hits搜索结果的文档数组其中的每个文档都是一个json对象 _source文档中的原始数据也是json对象 因此我们解析响应结果就是逐层解析JSON字符串流程如下 SearchHits通过response.getHits()获取就是JSON中的最外层的hits代表命中的结果 SearchHits#getTotalHits().value获取总条数信息 SearchHits#getHits()获取SearchHit数组也就是文档数组 SearchHit#getSourceAsString()获取文档结果中的_source也就是原始的json文档数据 3.完整代码 完整代码如下 Test void testMatchAll() throws IOException {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSLrequest.source().query(QueryBuilders.matchAllQuery());// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT); ​// 4.解析响应handleResponse(response); } ​ private void handleResponse(SearchResponse response) {// 4.解析响应SearchHits searchHits response.getHits();// 4.1.获取总条数long total searchHits.getTotalHits().value;System.out.println(共搜索到 total 条数据);// 4.2.文档数组SearchHit[] hits searchHits.getHits();// 4.3.遍历for (SearchHit hit : hits) {// 获取文档sourceString json hit.getSourceAsString();// 反序列化HotelDoc hotelDoc JSON.parseObject(json, HotelDoc.class);System.out.println(hotelDoc hotelDoc);} } 4.小结 查询的基本步骤是 创建SearchRequest对象 准备Request.source()也就是DSL。 ① QueryBuilders来构建查询条件 ② 传入Request.source() 的 query() 方法 发送请求得到结果 解析结果参考JSON结果从外到内逐层解析 RestClient查询文档-match、term、range、bool查询P112 match查询 全文检索的match和multi_match查询与match_all的API基本一致。差别是查询条件也就是query的部分。 match_all查询全部的数据 match在“all”中的所有数据all是所有索引字段添加的 multi_match多个字段查询 因此Java代码上的差异主要是request.source().query()中的参数了。同样是利用QueryBuilders提供的方法 而结果解析代码则完全一致可以抽取并共享。 完整代码如下 Test void testMatch() throws IOException {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSLrequest.source().query(QueryBuilders.matchQuery(all, 如家));// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response); ​ }private void handleResponse(SearchResponse response) {// 4.解析响应SearchHits searchHits response.getHits();// 4.1.获取总条数long total searchHits.getTotalHits().value;System.out.println(共搜索到 total 条数据);// 4.2.文档数组SearchHit[] hits searchHits.getHits();// 4.3.遍历for (SearchHit hit : hits) {// 获取文档sourceString json hit.getSourceAsString();// 反序列化HotelDoc hotelDoc JSON.parseObject(json, HotelDoc.class);System.out.println(hotelDoc hotelDoc);} } 精确查询 精确查询主要是两者 term词条精确匹配 range范围查询 与之前的查询相比差异同样在查询条件其它都一样。 查询条件构造的API如下 布尔查询 布尔查询是用must、must_not、filter等方式组合其它查询代码示例如下 可以看到API与其它查询的差别同样是在查询条件的构建QueryBuilders结果解析等其他代码完全不变。 完整代码如下Test void testBool() throws IOException {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSL// 2.1.准备BooleanQueryBoolQueryBuilder boolQuery QueryBuilders.boolQuery();// 2.2.添加termboolQuery.must(QueryBuilders.termQuery(city, 杭州));// 2.3.添加rangeboolQuery.filter(QueryBuilders.rangeQuery(price).lte(250)); ​request.source().query(boolQuery);// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response); ​ } 总结 RestClient查询文档-排序和分页P113 搜索结果的排序和分页是与query同级的参数因此同样是使用request.source()来设置。 对应的API如下 完整代码示例 Test void testPageAndSort() throws IOException {// 页码每页大小int page 1, size 5; ​// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSL// 2.1.queryrequest.source().query(QueryBuilders.matchAllQuery());// 2.2.排序 sortrequest.source().sort(price, SortOrder.ASC);// 2.3.分页 from、sizerequest.source().from((page - 1) * size).size(5);// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response); ​ } RestClient查询文档-高亮显示P114 高亮的代码与之前代码差异较大有两点 查询的DSL其中除了查询条件还需要添加高亮条件同样是与query同级。 结果解析结果除了要解析_source文档数据还要解析高亮结果 1.高亮请求构建 高亮请求的构建API如下 上述代码省略了查询条件部分但是大家不要忘了高亮查询必须使用全文检索查询并且要有搜索关键字将来才可以对关键字高亮。 完整代码如下 Test void testHighlight() throws IOException {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSL// 2.1.queryrequest.source().query(QueryBuilders.matchQuery(all, 如家));// 2.2.高亮request.source().highlighter(new HighlightBuilder().field(name).requireFieldMatch(false));// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response); ​ } 2.高亮结果解析 高亮的结果与查询的文档结果默认是分离的并不在一起。 因此解析高亮的代码需要额外处理 代码解读 第一步从结果中获取source。hit.getSourceAsString()这部分是非高亮结果json字符串。还需要反序列为HotelDoc对象 第二步获取高亮结果。hit.getHighlightFields()返回值是一个Mapkey是高亮字段名称值是HighlightField对象代表高亮值 第三步从map中根据高亮字段名称获取高亮字段值对象HighlightField 第四步从HighlightField中获取Fragments并且转为字符串。这部分就是真正的高亮字符串了 第五步用高亮的结果替换HotelDoc中的非高亮结果 完整代码如下 private void handleResponse(SearchResponse response) {// 4.解析响应SearchHits searchHits response.getHits();// 4.1.获取总条数long total searchHits.getTotalHits().value;System.out.println(共搜索到 total 条数据);// 4.2.文档数组SearchHit[] hits searchHits.getHits();// 4.3.遍历for (SearchHit hit : hits) {// 获取文档sourceString json hit.getSourceAsString();// 反序列化HotelDoc hotelDoc JSON.parseObject(json, HotelDoc.class);// 获取高亮结果MapString, HighlightField highlightFields hit.getHighlightFields();if (!CollectionUtils.isEmpty(highlightFields)) {// 根据字段名获取高亮结果HighlightField highlightField highlightFields.get(name);if (highlightField ! null) {// 获取高亮值String name highlightField.getFragments()[0].string();// 覆盖非高亮结果hotelDoc.setName(name);}}System.out.println(hotelDoc hotelDoc);} } 旅游案例-搜索分页P115 案例需求实现黑马旅游的酒店搜索功能完成关键字搜索和分页 1.需求分析 在项目的首页有一个大大的搜索框还有分页按钮 点击搜索按钮可以看到浏览器控制台发出了请求 请求参数如下 由此可以知道我们这个请求的信息如下 请求方式POST 请求路径/hotel/list 请求参数JSON对象包含4个字段 key搜索关键字 page页码 size每页大小 sortBy排序目前暂不实现 返回值分页查询需要返回分页结果PageResult包含两个属性 total总条数 ListHotelDoc当前页的数据 因此我们实现业务的流程如下 步骤一定义实体类接收请求参数的JSON对象 步骤二编写controller接收页面的请求 步骤三编写业务实现利用RestHighLevelClient实现搜索、分页 2.定义实体类 实体类有两个一个是前端的请求参数实体一个是服务端应该返回的响应结果实体。 1请求参数 前端请求的json结构如下 {key: 搜索关键字,page: 1,size: 3,sortBy: default } 因此我们在cn.itcast.hotel.pojo包下定义一个实体类 package cn.itcast.hotel.pojo; ​ import lombok.Data; ​ Data public class RequestParams {private String key;private Integer page;private Integer size;private String sortBy; } 2返回值 分页查询需要返回分页结果PageResult包含两个属性 total总条数 ListHotelDoc当前页的数据 因此我们在cn.itcast.hotel.pojo中定义返回结果 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;} } 3.定义controller 定义一个HotelController声明查询接口满足下列要求 请求方式Post 请求路径/hotel/list 请求参数对象类型为RequestParam 返回值PageResult包含两个属性 Long total总条数 ListHotelDoc hotels酒店数据 因此我们在cn.itcast.hotel.web中定义HotelController RestController RequestMapping(/hotel) public class HotelController { ​Autowiredprivate IHotelService hotelService;// 搜索酒店数据PostMapping(/list)public PageResult search(RequestBody RequestParams params){return hotelService.search(params);} } 4.实现搜索业务 我们在controller调用了IHotelService并没有实现该方法因此下面我们就在IHotelService中定义方法并且去实现业务逻辑。 1在cn.itcast.hotel.service中的IHotelService接口中定义一个方法 /*** 根据关键字搜索酒店信息* param params 请求参数对象包含用户输入的关键字 * return 酒店文档列表*/ PageResult search(RequestParams params); 2实现搜索业务肯定离不开RestHighLevelClient我们需要把它注册到Spring中作为一个Bean。在cn.itcast.hotel中的HotelDemoApplication中声明这个Bean Bean public RestHighLevelClient client(){return  new RestHighLevelClient(RestClient.builder(HttpHost.create(http://192.168.150.101:9200))); } 通过Autowired注入bean 3在cn.itcast.hotel.service.impl中的HotelService中实现search方法 Override public PageResult search(RequestParams params) {try {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSL// 2.1.queryString key params.getKey();if (key null || .equals(key)) {boolQuery.must(QueryBuilders.matchAllQuery());} else {boolQuery.must(QueryBuilders.matchQuery(all, key));} ​// 2.2.分页int page params.getPage();int size params.getSize();request.source().from((page - 1) * size).size(size); ​// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应return handleResponse(response);} catch (IOException e) {throw new RuntimeException(e);} } ​ // 结果解析 private PageResult handleResponse(SearchResponse response) {// 4.解析响应SearchHits searchHits response.getHits();// 4.1.获取总条数long total searchHits.getTotalHits().value;// 4.2.文档数组SearchHit[] hits searchHits.getHits();// 4.3.遍历ListHotelDoc hotels new ArrayList();for (SearchHit hit : hits) {// 获取文档sourceString json hit.getSourceAsString();// 反序列化HotelDoc hotelDoc JSON.parseObject(json, HotelDoc.class);// 放入集合hotels.add(hotelDoc);}// 4.4.封装返回return new PageResult(total, hotels); } 旅游案例-条件过滤P116 需求添加品牌、城市、星级、价格等过滤功能 1.需求分析 在页面搜索框下面会有一些过滤项 传递的参数如图 包含的过滤条件有 brand品牌值city城市minPrice~maxPrice价格范围starName星级 我们需要做两件事情 修改请求参数的对象RequestParams接收上述参数修改业务逻辑在搜索条件之外添加一些过滤条件 2.修改实体类 修改在cn.itcast.hotel.pojo包下的实体类RequestParams 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; }3.修改搜索业务 在HotelService的search方法中只有一个地方需要修改requet.source().query( … )其中的查询条件。 在之前的业务中只有match查询根据关键字搜索现在要添加条件过滤包括 品牌过滤是keyword类型用term查询星级过滤是keyword类型用term查询价格过滤是数值类型用range查询城市过滤是keyword类型用term查询 多个查询条件组合肯定是boolean查询来组合 关键字搜索放到must中参与算分其它过滤条件放到filter中不参与算分 因为条件构建的逻辑比较复杂这里先封装为一个函数 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-erAcDDMS-1692105687313)(assets/image-20210722092935453.png)] buildBasicQuery的代码如下 private void buildBasicQuery(RequestParams params, SearchRequest request) {// 1.构建BooleanQueryBoolQueryBuilder boolQuery QueryBuilders.boolQuery();// 2.关键字搜索String key params.getKey();if (key null || .equals(key)) {boolQuery.must(QueryBuilders.matchAllQuery());} else {boolQuery.must(QueryBuilders.matchQuery(all, key));}// 3.城市条件if (params.getCity() ! null !params.getCity().equals()) {boolQuery.filter(QueryBuilders.termQuery(city, params.getCity()));}// 4.品牌条件if (params.getBrand() ! null !params.getBrand().equals()) {boolQuery.filter(QueryBuilders.termQuery(brand, params.getBrand()));}// 5.星级条件if (params.getStarName() ! null !params.getStarName().equals()) {boolQuery.filter(QueryBuilders.termQuery(starName, params.getStarName()));}// 6.价格if (params.getMinPrice() ! null params.getMaxPrice() ! null) {boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}// 7.放入sourcerequest.source().query(boolQuery); } 旅游案例-我附近的酒店P117 需求我附近的酒店 1.需求分析 在酒店列表页的右侧有一个小地图点击地图的定位按钮地图会找到你所在的位置 并且在前端会发起查询请求将你的坐标发送到服务端 我们要做的事情就是基于这个location坐标然后按照距离对周围酒店排序。实现思路如下 修改RequestParams参数接收location字段 修改search方法业务逻辑如果location有值添加根据geo_distance排序的功能 2.修改实体类 修改在cn.itcast.hotel.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;private String city;private String brand;private String starName;private Integer minPrice;private Integer maxPrice;// 我当前的地理坐标private String location; } ​ 3.距离排序API 我们以前学习过排序功能包括两种 普通字段排序 地理坐标排序 我们只讲了普通字段排序对应的java写法。地理坐标排序只学过DSL语法如下 GET /indexName/_search {query: {match_all: {}},sort: [{price: asc  },{_geo_distance : {FIELD : 纬度经度,order : asc,unit : km}}] } 对应的java代码示例 4.添加距离排序 在cn.itcast.hotel.service.impl的HotelService的search方法中添加一个排序功能 完整代码 Override public PageResult search(RequestParams params) {try {// 1.准备RequestSearchRequest request new SearchRequest(hotel);// 2.准备DSL// 2.1.querybuildBasicQuery(params, request); ​// 2.2.分页int page params.getPage();int size params.getSize();request.source().from((page - 1) * size).size(size); ​// 2.3.排序String location params.getLocation();if (location ! null !location.equals()) {request.source().sort(SortBuilders.geoDistanceSort(location, new GeoPoint(location)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));} ​// 3.发送请求SearchResponse response client.search(request, RequestOptions.DEFAULT);// 4.解析响应return handleResponse(response);} catch (IOException e) {throw new RuntimeException(e);} } 5.排序距离显示 重启服务后测试我的酒店功能 发现确实可以实现对我附近酒店的排序不过并没有看到酒店到底距离我多远这该怎么办 排序完成后页面还要获取我附近每个酒店的具体距离值这个值在响应结果中是独立的 因此我们在结果解析阶段除了解析source部分以外还要得到sort部分也就是排序的距离然后放到响应结果中。 我们要做两件事 修改HotelDoc添加排序距离字段用于页面显示 修改HotelService类中的handleResponse方法添加对sort值的获取 1修改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;// 排序时的 距离值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();} } ​ 2修改HotelService中的handleResponse方法 重启后测试发现页面能成功显示距离了 旅游案例-广告置顶P118 需求让指定的酒店在搜索结果中排名置顶 1.需求分析 要让指定酒店在搜索结果中排名置顶效果如图 页面会给指定的酒店添加广告标记。 那怎样才能让指定的酒店排名置顶呢 我们之前学习过的function_score查询可以影响算分算分高了自然排名也就高了。而function_score包含3个要素 过滤条件哪些文档要加分 算分函数如何计算function score 加权方式function score 与 query score如何运算 这里的需求是让指定酒店排名靠前。因此我们需要给这些酒店添加一个标记这样在过滤条件中就可以根据这个标记来判断是否要提高算分。 比如我们给酒店添加一个字段isADBoolean类型 true是广告 false不是广告 这样function_score包含3个要素就很好确定了 过滤条件判断isAD 是否为true 算分函数我们可以用最简单暴力的weight固定加权值 加权方式可以用默认的相乘大大提高算分 因此业务的实现步骤包括 给HotelDoc类添加isAD字段Boolean类型 挑选几个你喜欢的酒店给它的文档数据添加isAD字段值为true 修改search方法添加function score功能给isAD值为true的酒店增加权重 2.修改HotelDoc实体 给cn.itcast.hotel.pojo包下的HotelDoc类添加isAD字段 3.添加广告标记 接下来我们挑几个酒店添加isAD字段设置为true POST /hotel/_update/1902197537 {doc: {isAD: true} } POST /hotel/_update/2056126831 {doc: {isAD: true} } POST /hotel/_update/1989806195 {doc: {isAD: true} } POST /hotel/_update/2056105938 {doc: {isAD: true} } 4.添加算分函数查询 接下来我们就要修改查询条件了。之前是用的boolean 查询现在要改成function_socre查询。 function_score查询结构如下 对应的JavaAPI如下 我们可以将之前写的boolean查询作为原始查询条件放到query中接下来就是添加过滤条件、算分函数、加权模式了。所以原来的代码依然可以沿用。 修改cn.itcast.hotel.service.impl包下的HotelService类中的buildBasicQuery方法添加算分函数查询 private void buildBasicQuery(RequestParams params, SearchRequest request) {// 1.构建BooleanQueryBoolQueryBuilder boolQuery QueryBuilders.boolQuery();// 关键字搜索String key params.getKey();if (key null || .equals(key)) {boolQuery.must(QueryBuilders.matchAllQuery());} else {boolQuery.must(QueryBuilders.matchQuery(all, key));}// 城市条件if (params.getCity() ! null !params.getCity().equals()) {boolQuery.filter(QueryBuilders.termQuery(city, params.getCity()));}// 品牌条件if (params.getBrand() ! null !params.getBrand().equals()) {boolQuery.filter(QueryBuilders.termQuery(brand, params.getBrand()));}// 星级条件if (params.getStarName() ! null !params.getStarName().equals()) {boolQuery.filter(QueryBuilders.termQuery(starName, params.getStarName()));}// 价格if (params.getMinPrice() ! null params.getMaxPrice() ! null) {boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));} ​// 2.算分控制FunctionScoreQueryBuilder functionScoreQuery QueryBuilders.functionScoreQuery(// 原始查询相关性算分的查询boolQuery,// function score的数组new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{// 其中的一个function score 元素new FunctionScoreQueryBuilder.FilterFunctionBuilder(// 过滤条件QueryBuilders.termQuery(isAD, true),// 算分函数ScoreFunctionBuilders.weightFactorFunction(10))});request.source().query(functionScoreQuery); }
http://www.hkea.cn/news/14474168/

相关文章:

  • 东莞网站seo价格东营网站设计制作
  • 嘉兴手机端建站模板wordpress如何加菜单
  • 一个网站大概多少页面三网合一 营销型网站
  • 怎么用手机做网站平台从做网站可以用现在的名称吗
  • 免费动画模板素材网站做个商城网站怎么做便宜
  • 哈尔滨建站流程网站百度排名怎么做快
  • 哪有宝安网站推广杭州的设计网站
  • 手机网站建设林肖php网站建设到护卫神
  • 苏州 网站设计昆明网站建设SEO公司
  • 网站怎么谈设计四川省建设三类职称网站
  • 建设网站是要先建站在备案么北京网站建设 标准型 新翼
  • 如何让百度搜到网站工作总结2023最新完整版
  • 服饰网站建设规划书做一个网站的建设流程
  • 上海做响应式网站的公司大连百度代理
  • 创新的盐城网站建设手机网站展示
  • 网站建设与网页设计 难学吗上海市五金外贸公司
  • 嘉兴做微网站多少钱建设局是做什么的
  • app开发搭建淄博网站排名seo
  • 网站建设金手指稳定win10 电脑做网站服务器吗
  • 网站开发工作容易出现的失误三五互联做网站吗
  • 深圳高端集团网站建设公司WordPress 任务悬赏插件
  • 注册域名的官方网站广州微网站建设效果
  • 代码统计网站常德网站建设培训机构
  • 建立网站的顺序项目设计方案
  • 电子商务网站建设的意义是什么意思电脑上用手机app是什么软件
  • 中国公司查询网站wordpress不居中
  • 网站建设方投资成本网站排名软件多浏览器
  • 井冈山保育院网站建设网站设计的公司运营接单
  • 网站建设douyanet个人做网站要注意什么条件
  • 网站建设的基本要求织梦网站文章内容模板