1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Elasticsearch——分布式搜索引擎01(索引库 文档 RestAPI RestClient 拼音分词器 IK分词器)

Elasticsearch——分布式搜索引擎01(索引库 文档 RestAPI RestClient 拼音分词器 IK分词器)

时间:2022-09-22 07:41:33

相关推荐

Elasticsearch——分布式搜索引擎01(索引库 文档 RestAPI RestClient 拼音分词器 IK分词器)

Elasticsearch——分布式搜索引擎01(索引库、文档、RestAPI、RestClient、拼音分词器、IK分词器)

一、初识 elesticsearch1.1 简介1.2 倒排索引(重点)1.2.1 正向索引1.2.2 倒排索引1.2.3 正向和倒排(对比)1.3 es 的其它概念1.3.1 文档和字段1.3.2 索引和映射1.3.3 mysql 和 elasticsearch 比较(重点)1.4 安装 ES、Kibana1.5 使用 kibana1.5.1 Dev Tools1.5.2 默认分词器 standard1.5.3 IK分词器1.5.4 扩展词典1.5.5 停用词词典1.5.6 拼音分词器(自动补全查询基础技术)二、索引库操作2.1 mapping 映射属性2.2 索引库的 CRUD2.2.1 创建索引库和映射2.2.2 查询索引库2.2.3 修改索引库2.2.4 删除索引库三、文档操作3.1 新增文档3.2 查询文档3.2.1 根据Id查询文档3.2.2 查询所有文档3.3 删除文档3.4 修改文档3.4.1 全量修改3.4.2 增量修改四、RestAPI(案例演示)4.1 Demo工程4.1.1 导入数据4.1.2 导入项目4.1.3 mapping 映射分析4.1.4 初始化 RestClient4.2 创建索引库4.3 删除索引库4.4 判断索引库是否存在4.5 总结五、RestClient 操作文档5.1 新增文档5.1.1 索引库实体类5.1.2 语法说明5.1.3 完整代码5.2 查询文档5.2.1 语法说明5.2.2 完整代码5.3.删除文档5.4 修改文档5.4.1 语法说明5.4.2.完整代码5.5 批量导入文档5.5.1.语法说明5.5.2 完整代码5.6 自动补全查询的 JavaAPI5.7 小结

备注:有误还望指正,本博客仅供学习参考,一起加油!

一、初识 elesticsearch

1.1 简介

Elasticsearch是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

Elasticsearch 结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域;而 elasticsearch 是 elastic stack 的核心,负责存储、搜索、分析数据。

elasticsearch 底层是基于lucene来实现的。Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。Lucene 的优缺点如下:

Lucene 的优点

易扩展高性能(基于倒排索引)

Lucene 的缺点

只限于 Java 语言开发学习曲线陡峭(学习成本大)不支持水平扩展

ElasticSearch 相对于 Lucene 的优势

支持分布式,可水平扩展提供 Restful 接口,可被任何语言调用

相关面试题总结

1)什么是elasticsearch?

一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能。

2)什么是elastic stack(ELK)?

是以elasticsearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch。

3)什么是Lucene?

是Apache的开源搜索引擎类库,提供了搜索引擎的核心API。

1.2 倒排索引(重点)

倒排索引的概念是基于MySQL这样的正向索引而言的。

1.2.1 正向索引

那么什么是正向索引呢?例如给下表(tb_goods)中的id创建索引:

如果是根据id查询,那么直接走索引,查询速度非常快。但如果是基于title做模糊查询,只能是逐行扫描数据,流程如下:

1)用户搜索数据,条件是title符合"%手机%"

2)逐行获取数据,比如id为1的数据

3)判断数据中的title是否符合用户搜索条件

4)如果符合则放入结果集,不符合则丢弃。回到步骤1

逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,查询效率就会非常低。

1.2.2 倒排索引

倒排索引中有两个非常重要的概念:

文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息词条(Term):对文档数据用户搜索的数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条

创建倒排索引是对正向索引的一种特殊处理,流程如下:

将每一个文档的数据利用算法分词,得到一个个词条创建表,每行数据包括词条、词条所在文档id、位置等信息因为词条唯一性,可以给词条创建索引,例如hash表结构索引

如图:

倒排索引的搜索流程如下(以搜索"华为手机"为例):

1)用户输入条件"华为手机"进行搜索。

2)对用户输入内容分词,得到词条:华为手机

3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。

4)拿着文档id到正向索引中查找具体文档。

如图:

备注:虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。

1.2.3 正向和倒排(对比)

正向索引和倒排索引的对比:

正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档(每行的数据),然后判断文档中是否包含所需要的词条(关键字),是根据文档找词条的过程

倒排索引则相反,是先找到用户要搜索的词条,根据词条得到包含词条的文档的id,然后根据id获取文档。是根据词条找文档的过程

两种索引的优缺点如下:

正向索引 优点

可以给多个字段创建索引。根据索引字段搜索、排序速度非常快。

正向索引 缺点

根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描(索引会失效),查询速度当数据量大时变慢。

倒排索引 优点

根据词条搜索、模糊搜索时,速度非常快。

倒排索引 缺点

只能给词条创建索引,而不是字段。无法根据字段做排序。

1.3 es 的其它概念

1.3.1 文档和字段

elasticsearch 是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为 json格式 后存储在 elasticsearch 中;而Json文档中往往包含很多的字段(Field),类似于数据库中的列。

1.3.2 索引和映射

索引(Index),就是相同类型的文档的集合。

例如:

所有用户文档,就可以组织在一起,称为用户的索引;所有商品的文档,可以组织在一起,称为商品的索引;所有订单的文档,可以组织在一起,称为订单的索引;

因此,我们可以把索引当做是数据库中的表(es中索引也叫索引库)。可以将索引比作数据库的

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。映射对应数据库中表的约束信息

1.3.3 mysql 和 elasticsearch 比较(重点)

我们统一的把mysql与elasticsearch的概念做一下对比:

是不是说,我们学习了elasticsearch就不再需要mysql了呢?

并不是如此,两者各自有自己的擅长之处:

Mysql:擅长事务类型操作,可以确保数据的安全和一致性。Elasticsearch:擅长海量数据的搜索、分析、计算。

因此在企业中,往往是两者结合使用:

对安全性要求较高的写操作,使用mysql实现。对查询性能要求较高的搜索需求,使用elasticsearch实现。两者再基于某种方式,实现数据的同步,保证一致性。(比如 RabbitMQ)

1.4 安装 ES、Kibana

参考链接:Docker 安装 ES、Kibana

1.5 使用 kibana

1.5.1 Dev Tools

kibana中提供了一个DevTools界面:往下拉,可以看到Dev Tools,这个界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。

1.5.2 默认分词器 standard

以下DSL就是用来测试分词效果的:

语法提示:

GET:请求方式。/_analyze:请求路径,这里省略了http://192.168.150.101:9200,由kibana帮我们补充请求参数,json风格。analyzer:分词器类型,这里是默认的 standard 分词器。text:要分词的内容。

1.5.3 IK分词器

安装参考:Docker 安装 IK分词器

IK分词器包含两种模式

ik_smart:最少切分(粗粒度)ik_max_word:最细切分(细粒度)

分词器常见面试问题

1)分词器的作用是什么?

创建倒排索引时对文档分词用户搜索时,对输入的内容分词

2)IK分词器有几种模式?

ik_smart:智能切分,粗粒度ik_max_word:最细切分,细粒度

3)IK分词器如何扩展词条?如何停用词条?

利用config目录的IkAnalyzer.cfg.xml文件添加扩展词典和停用词典在词典中添加拓展词条或者停用词条

1.5.4 扩展词典

随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表(词典)中并不存在。比如:“奥力给”,“刷脸” 等。大家可以测试text为 “Java学科,奥力给!” 的分词效果。

所以分词器的词库也需要不断的更新,IK分词器提供了扩展词库的功能。

1)打开IK分词器config目录:(这里使用的是数据卷的挂载)

[root@VM-16-16-centos ~]# cd ../var/lib/docker/volumes/es-config/_data/analysis-ik[root@VM-16-16-centos analysis-ik]# lltotal 8260-rw-rw---- 1 lighthouse root 5225922 Feb 15 09:21 extra_main.dic-rw-rw---- 1 lighthouse root 63188 Feb 15 09:21 extra_single_word.dic-rw-rw---- 1 lighthouse root 63188 Feb 15 09:21 extra_single_word_full.dic-rw-rw---- 1 lighthouse root 10855 Feb 15 09:21 extra_single_word_low_freq.dic-rw-rw---- 1 lighthouse root156 Feb 15 09:21 extra_stopword.dic-rw-rw---- 1 lighthouse root625 Feb 15 09:21 IKAnalyzer.cfg.xml-rw-rw---- 1 lighthouse root 3058510 Feb 15 09:21 main.dic-rw-rw---- 1 lighthouse root123 Feb 15 09:21 preposition.dic-rw-rw---- 1 lighthouse root 1824 Feb 15 09:21 quantifier.dic-rw-rw---- 1 lighthouse root164 Feb 15 09:21 stopword.dic-rw-rw---- 1 lighthouse root192 Feb 15 09:21 suffix.dic-rw-rw---- 1 lighthouse root752 Feb 15 09:21 surname.dic

2)在IKAnalyzer.cfg.xml配置文件内容添加:(保存退出)

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE properties SYSTEM "/dtd/properties.dtd"><properties><comment>IK Analyzer 扩展配置</comment><!--用户可以在这里配置自己的扩展字典 --><entry key="ext_dict">ext.dic</entry><!--用户可以在这里配置自己的扩展停止词字典--><entry key="ext_stopwords"></entry><!--用户可以在这里配置远程扩展字典 --><!-- <entry key="remote_ext_dict">words_location</entry> --><!--用户可以在这里配置远程扩展停止词字典--><!-- <entry key="remote_ext_stopwords">words_location</entry> --></properties>

3)新建一个ext.dic,可以参考config目录下复制一个配置文件进行修改

奥利给刷脸

4)重启 es

#重启es容器docker restart es# 查看 日志(可选)docker logs -f es

日志中已经成功加载IKAnalyzer.cfg.xml配置文件,也就加载了ext.dic文件。

5)测试效果

注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑

1.5.5 停用词词典

在互联网项目中,在网络间传输的速度很快,所以很多语言是不允许在网络上传递的,如:关于宗教、政治等敏感词语,那么我们在搜索时也应该忽略当前词汇。

IK分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。

1)IKAnalyzer.cfg.xml配置文件内容添加

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE properties SYSTEM "/dtd/properties.dtd"><properties><comment>IK Analyzer 扩展配置</comment><!--用户可以在这里配置自己的扩展字典 --><entry key="ext_dict">ext.dic</entry><!--用户可以在这里配置自己的扩展停止词字典--><entry key="ext_stopwords">stopword.dic</entry><!--用户可以在这里配置远程扩展字典 --><!-- <entry key="remote_ext_dict">words_location</entry> --><!--用户可以在这里配置远程扩展停止词字典--><!-- <entry key="remote_ext_stopwords">words_location</entry> --></properties>

3)在stopword.dic添加停用词(没有文件就创建文件)

暴力

4)重启elasticsearch

# 重启服务docker restart esdocker restart kibana# 查看 日志docker logs -f es

日志中已经成功加载stopword.dic配置文件。

5)测试效果:

注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑

1.5.6 拼音分词器(自动补全查询基础技术)

要实现根据字母做补全,就必须对文档按照拼音分词。

elasticsearch-analysis-pinyin-7.12.1.zip 安装包、拼音分词器安装

1)测试用法如下

2)自定义分词器:(重点)

默认的拼音分词器会将每个汉字单独分为拼音,而我们希望的是每个词条形成一组拼音(rujia),需要对拼音分词器做个性化定制,形成自定义分词器。

elasticsearch中分词器(analyzer)的组成包含三部分:

character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符tokenizer:将文本按照一定的规则切割成词条(term)。例如ik_smarttokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等

文档分词时会依次由这三部分来处理文档:

声明自定义分词器的语法如下

PUT /test{"settings": {"analysis": {"analyzer": {// 自定义分词器"my_analyzer": {// 分词器名称"tokenizer": "ik_max_word","filter": "py"}},"filter": {// 自定义tokenizer filter"py": {// 过滤器名称"type": "pinyin", // 过滤器类型,这里是pinyin"keep_full_pinyin": false,"keep_joined_full_pinyin": true,"keep_original": true,"limit_first_letter_length": 16,"remove_duplicated_term": true,"none_chinese_pinyin_tokenize": false}}}},"mappings": {"properties": {"name": {"type": "text","analyzer": "my_analyzer","search_analyzer": "ik_smart" //搜索时使用中文分词器}}}}

测试:

注意:拼音分词器适合在创建倒排索引时使用,不要在搜索的时候使用。

总结

如何使用拼音分词器?

①下载pinyin分词器②解压并放到elasticsearch的plugin目录③重启es容器即可

如何自定义分词器?

①创建索引库时,在settings中配置,可以包含三部分:character filtertokenizerfilter

拼音分词器注意事项有哪些?

为了避免搜索到同音字(按照拼音搜索到同音字),搜索时不要使用拼音分词器

二、索引库操作

索引库(相当于表)中有多个json文档(相当于表中的记录)】

2.1 mapping 映射属性

mapping 是对索引库中文档的约束,常见的mapping属性包括:

例如下面的 json 文档:

{"age": 21,"weight": 52.1,"isMarried": false,"info": "Java讲师,奥里给!","email": "cb@","score": [99.1, 99.5, 98.9],"name": {"firstName": "云","lastName": "赵"}}

对应的每个字段映射(mapping):(重点理解

age:类型为 integer;参与搜索,因此需要index为true;无需分词器weight:类型为float;参与搜索,因此需要index为true;无需分词器isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smartemail:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器name:类型为object,需要定义多个子属性 name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器

2.2 索引库的 CRUD

这里我们统一使用Kibana编写DSL的方式来演示。

索引库操作有哪些?

创建索引库:PUT /索引库名查询索引库:GET /索引库名删除索引库:DELETE /索引库名添加字段:PUT /索引库名/_mapping

2.2.1 创建索引库和映射

基本语法

请求方式:PUT请求路径:/索引库名,可以自定义请求参数:mapping映射

格式

// 创建索引库和映射PUT /索引库名称{"mappings": {"properties": {"字段名":{"type": "text","analyzer": "ik_smart"},"字段名2":{"type": "keyword","index": false},"字段名3":{"properties": {"子字段": {"type": "keyword"}}},// ...略}}}

示例

PUT /ccbx{"mappings": {"properties": {"info":{"type": "text","analyzer": "ik_smart"},"email":{"type": "keyword","index": false},"name":{"type": "object","properties": {"firstName":{"type":"keyword","index":true},"lastName":{"type":"keyword","index":true}}}}}}

结果如下

2.2.2 查询索引库

基本语法

请求方式:GET请求路径:/索引库名请求参数:无

格式

//查询索引库GET /索引库名

示例

2.2.3 修改索引库

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping

虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。

语法说明

PUT /索引库名/_mapping{"properties": {"新字段名":{"type": "指定的类型"}}}

示例

2.2.4 删除索引库

语法:

请求方式:DELETE

请求路径:/索引库名

请求参数:无

格式:

//删除索引库DELETE /索引库名

在kibana中测试:

#创建一个索引库和映射PUT /test-01{"mappings": {"properties": {"name":{"type": "text","analyzer": "ik_smart"}}}}#查询索引库GET test-01#删除索引库DELETE /test-01

三、文档操作

类似于向 MySQL 数据库的表中添加数据记录。

文档操作有哪些?

创建文档:POST /{索引库名}/_doc/文档id { json文档 }查询文档:GET /{索引库名}/_doc/文档id删除文档:DELETE /{索引库名}/_doc/文档id修改文档: 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}

3.1 新增文档

语法:

//新增文档POST /索引库名/_doc/文档id{"字段1": "值1","字段2": "值2","字段3": {"子属性1": "值3","子属性2": "值4"},// ...}

示例:

// 新增文档POST /ccbx/_doc/1{"email":"123@","info":"Java讲师,有钱途","isMarried":true,"name":{"firstName":"张","lastName":"良"}}

响应:

3.2 查询文档

根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。

3.2.1 根据Id查询文档

语法:

GET /{索引库名称}/_doc/{id}

通过 kibana 查看数据:

GET /ccbx/_doc/1

查看结果:

3.2.2 查询所有文档

语法1:GET /{索引库名称}/_search

语法2:

GET /{索引库名称}/_search{"query": {"match_all": {}}}

演示

3.3 删除文档

删除使用 DELETE 请求,同样,需要根据id进行删除:

语法:DELETE /{索引库名}/_doc/id值

示例:

3.4 修改文档

修改有两种方式:

全量修改:直接覆盖原来的文档增量修改:修改文档中的部分字段

3.4.1 全量修改

全量修改是覆盖原来的文档,其本质是:

根据指定的id删除文档新增一个相同id的文档

注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。

语法:

PUT /{索引库名}/_doc/文档id{"字段1": "值1","字段2": "值2",// ... 略}

示例:

PUT /ccbx/_doc/1{"info": "高级Java讲师","email": "chu@","name": {"firstName": "张","lastName": "良良"}}

3.4.2 增量修改

增量修改是只修改指定id匹配的文档中的部分字段。

语法:

POST /{索引库名}/_update/文档id{"doc": {"字段名": "新的值",}}

示例:

#增量修改POST /ccbx/_update/1{"doc":{"email":"zhangliang@"}}

四、RestAPI(案例演示)

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:Rest Client

其中的Java Rest Client又包括两种:

Java Low Level Rest ClientJava High Level Rest Client(本次学习使用的版本)

4.1 Demo工程

Rest API小案例下载链接

4.1.1 导入数据

1)首先创建数据库,字符集选择 utf8mb4。

2)导入下载资料提供的数据库数据:tb_hotel.sql 文件,其数据结构如下:

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

4.1.2 导入项目

3)导入下载的项目:hotel-demo,项目结构如下:

注意:maven 配置文件的位置要替换成自己本地的!!!

4.1.3 mapping 映射分析

创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:

字段名字段数据类型是否参与搜索是否需要分词如果分词,分词器是什么?

其中:

字段名、字段数据类型,可以参考数据表结构的名称和类型是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词分词器,我们可以统一使用 ik_max_word

酒店数据的索引库结构:

PUT /hotel{"mappings": {"properties": {"id": {"type": "keyword"},"name":{"type": "text","analyzer": "ik_max_word","copy_to": "all"},"address":{"type": "keyword","index": false},"price":{"type": "integer"},"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":{"type": "text","analyzer": "ik_max_word"}}}}

几个特殊字段说明:

location:地理坐标,里面包含精度、纬度。all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索。copy_to:字段拷贝;将当前字段的值拷贝到指定字段。

地理坐标说明:

4.1.4 初始化 RestClient

在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient.java的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。

分为三步:

1)引入es的 RestHighLevelClient 依赖:

<!-- 引入 elasticsearch 依赖 --><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId></dependency>

2)因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:

<properties><java.version>1.8</java.version><!-- 覆盖默认的版本号 --><elasticsearch.version>7.12.1</elasticsearch.version></properties>

3)初始化RestHighLevelClient,初始化的代码如下:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.92.66:9200")));

这里为了单元测试方便,我们创建一个测试类HotelIndexTest.java,然后将初始化的代码编写在@BeforeEach方法中:

public class HotelIndexTest {private RestHighLevelClient client;//只能用junit5的依赖,所有crud测试方法之前执行的@BeforeEachvoid setUp() {//IP地址替换成自己服务器的IP和端口号this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.92.66:9200")));}//tearDown方法用来释放资源,在所有crud测试方法之后执行的@AfterEachvoid tearDown() throws IOException {this.client.close();}}

4.2 创建索引库

创建索引库的API如下:

代码分为三步:

1、创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。2、添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。3、发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。

在hotel-demo的com.softeem.hotel.constants包下,创建一个类,定义mapping映射的JSON字符串常量:

package com.softeem.hotel.constants;/*** @Description 定义Mapping映射的 JSON 字符串常量。* @Author cb* @Date -02-15 22:11**/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测试类中,编写单元测试,实现创建索引:

@Testvoid createHotelIndex() throws IOException {// 1、创建 Request 对象 (hotel 是索引库名称)CreateIndexRequest request = new CreateIndexRequest("hotel");// 2、准备请求的参数:DSL 语句(下列静态导入该常量 MAPPING_TEMPLATE)//注意:// 正常的import声明从包中导入类,因此可以在没有包引用的情况下使用它们。// 类似地,静态导入声明从类中导入静态成员,并允许它们在没有类引用的情况下使用。request.source(MAPPING_TEMPLATE, XContentType.JSON);// 3、发送请求client.indices().create(request, RequestOptions.DEFAULT);}

静态导入常量:import static com.softeem.hotel.constants.HotelConstants.MAPPING_TEMPLATE;

4.3 删除索引库

删除索引库的DSL语句非常简单:

//删除索引库 hotelDELETE /hotel

与创建索引库相比:

请求方式从PUT变为DELTE请求路径不变无请求参数

所以代码的差异,注意体现在Request对象上。依然是三步走:

1)创建Request对象。这次是DeleteIndexRequest对象2)准备参数。这里是无参3)发送请求。改用delete方法

在hotel-demo中的HotelIndexTest测试类中,编写单元测试,实现删除索引:

@Testvoid testDeleteHotelIndex() throws IOException {// 1.创建Request对象DeleteIndexRequest request = new DeleteIndexRequest("hotel");// 2.发送请求client.indices().delete(request, RequestOptions.DEFAULT);}

4.4 判断索引库是否存在

判断索引库是否存在,本质就是查询,对应的DSL是:

// 查询索引库 hotelGET /hotel

因此与删除的Java代码流程是类似的。依然是三步走:

1)创建Request对象。这次是GetIndexRequest对象2)准备参数。这里是无参3)发送请求。改用exists方法

@Testvoid testExistsHotelIndex() throws IOException {// 1.创建Request对象GetIndexRequest request = new GetIndexRequest("hotel");// 2.发送请求boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);// 3.输出System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");}

4.5 总结

JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。

索引库操作的基本步骤

初始化RestHighLevelClient创建XxxIndexRequest。XXX是Create、Get、Delete准备DSL( Create时需要,其它时候是无参)发送请求。调用RestHighLevelClient.indices().xxx()方法,xxx是create、exists、delete。

五、RestClient 操作文档

为了与索引库操作分离,我们再次参加一个测试类,做两件事情:

初始化RestHighLevelClient我们的酒店数据在数据库,需要利用IHotelService去查询,所以注入这个接口

@SpringBootTestpublic class HotelDocumentTest {@Autowiredprivate IHotelService hotelService;private RestHighLevelClient client;//只能用junit5的依赖,所有crud测试方法之前执行的@BeforeEachvoid setUp() {this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://http://192.168.92.66:9200")));}//tearDown方法用来释放资源,在所有crud测试方法之后执行的@AfterEachvoid tearDown() throws IOException {this.client.close();}}

5.1 新增文档

我们要将数据库的酒店数据查询出来,写入elasticsearch中。

5.1.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

因此,我们需要定义一个新的类型,与索引库结构吻合:

@Data@NoArgsConstructorpublic 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;//通过传入Hotel对象从二构建HotelDoc对象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();}}

5.1.2 语法说明

新增文档的DSL语句如下:

POST /{索引库名}/_doc/1{"name": "Jack","age": 21}

对应的java代码如图:

可以看到与创建索引库类似,同样是三步走:

1)创建Request对象2)准备请求参数,也就是DSL中的JSON文档3)发送请求

变化的地方在于,这里直接使用 client.xxx() 的API,不再需要 client.indices()了。

5.1.3 完整代码

我们导入酒店数据,基本流程一致,但是需要考虑几点变化:

酒店数据来自于数据库,我们需要先查询出来,得到hotel对象hotel对象 需要转为 HotelDoc对象HotelDoc 需要序列化为json格式

因此,代码整体步骤如下:

1)根据id查询酒店数据Hotel2)将Hotel封装为HotelDoc3)将HotelDoc序列化为JSON4)创建IndexRequest,指定索引库名和id5)准备请求参数,也就是JSON文档6)发送请求

在hotel-demo的HotelDocumentTest测试类中,编写单元测试:

@Testvoid testAddDocument() throws IOException {// 1.根据id查询节点数据Hotel hotel = hotelService.getById(415659L);// 2.转换为文档类型HotelDoc hotelDoc = new HotelDoc(hotel);// 3.将HotelDoc转为json格式String jsonData = JSON.toJSONString(hotelDoc);// 1.准备Request对象IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());// 2.准备Json 文档request.source(jsonData, XContentType.JSON);// 3.发送请求client.index(request, RequestOptions.DEFAULT);}

5.2 查询文档

5.2.1 语法说明

查询的DSL语句如下:

GET /hotel/_doc/{id}

非常简单,因此代码大概分两步:

准备Request对象发送请求

不过查询的目的是得到结果,解析为HotelDoc,因此难点是结果的解析。完整代码如下:

可以看到,结果是一个JSON,其中文档放在一个_source属性中,因此解析就是拿到_source,反序列化为Java对象即可。

与之前类似,也是三步走:

1)准备Request对象。这次是查询,所以是GetRequest2)发送请求,得到结果。因为是查询,这里调用client.get()方法3)解析结果,就是对JSON做反序列化

5.2.2 完整代码

在hotel-demo的HotelDocumentTest测试类中,编写单元测试:

@Testvoid testGetDocumentById() throws IOException {// 1.准备RequestGetRequest request = new GetRequest("hotel", "415659");// 2.发送请求,得到响应GetResponse response = client.get(request, RequestOptions.DEFAULT);// 3.解析响应结果String json = response.getSourceAsString();System.out.println(json);HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);System.out.println(hotelDoc);}

5.3.删除文档

删除的DSL为是这样的:

DELETE /hotel/_doc/{id}

与查询相比,仅仅是请求方式从DELETE变成GET,可以想象Java代码应该依然是三步走:

1)准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id2)准备参数,无参3)发送请求。因为是删除,所以是client.delete()方法

在hotel-demo的HotelDocumentTest测试类中,编写单元测试:

@Testvoid testDeleteDocument() throws IOException {// 1.准备RequestDeleteRequest request = new DeleteRequest("hotel", "415659");// 2.发送请求client.delete(request, RequestOptions.DEFAULT);}

5.4 修改文档

5.4.1 语法说明

修改我们讲过两种方式:

全量修改:本质是先根据id删除,再新增。增量修改:修改文档中的指定字段值。

在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:

如果新增时,ID已经存在,则修改如果新增时,ID不存在,则新增

这里不再赘述,我们主要关注增量修改。

增量修改代码示例如图

与之前类似,也是三步走:

1)准备Request对象。这次是修改,所以是UpdateRequest2)准备参数。也就是JSON文档,里面包含要修改的字段3)更新文档。这里调用client.update()方法

5.4.2.完整代码

在hotel-demo的HotelDocumentTest测试类中,编写单元测试:

@Testvoid testUpdateDocument() throws IOException {// 1.准备RequestUpdateRequest request = new UpdateRequest("hotel", "415659");// 2.准备请求参数request.doc("price", "999","starName", "皇冠");// 3.发送请求client.update(request, RequestOptions.DEFAULT);}

5.5 批量导入文档

案例需求:利用BulkRequest批量将数据库数据导入到索引库中。

步骤如下:

利用mybatis-plus查询酒店数据将查询到的酒店数据(Hotel)转换为文档类型数据(HotelDoc)利用 JavaRestClient 中的BulkRequest批处理,实现批量新增文档

5.5.1.语法说明

批量处理BulkRequest,其本质就是将多个普通的CRUD请求组合在一起发送。

其中提供了一个add方法,用来添加其他请求:

可以看到,能添加的请求包括:

IndexRequest,也就是新增UpdateRequest,也就是修改DeleteRequest,也就是删除

因此Bulk中添加了多个IndexRequest,就是批量新增功能了。其实还是三步走:

1)创建Request对象。这里是BulkRequest2)准备参数。批处理的参数,就是其它Request对象,这里就是多个IndexRequest3)发起请求。这里是批处理,调用的方法为 client.bulk() 方法

示例:

我们在导入酒店数据时,将上述代码改造成for循环处理即可。

5.5.2 完整代码

在hotel-demo的HotelDocumentTest测试类中,编写单元测试:

@Testvoid testBulkRequest() throws IOException{//批量查询酒店数据List<Hotel> hotelList = hotelService.list();// 1.创建Bulk请求BulkRequest request = new BulkRequest();// 2.添加要批量提交的请求:这里添加了两个新增文档的请求;for (Hotel hotel : hotelList) {//2.1 转换为文档类型 HotelDocHotelDoc hotelDoc = new HotelDoc(hotel);//2.2 创建新增文档的Request对象(方法连调的时候注意source重载方法别调用错了)IndexRequest indexRequest = new IndexRequest("hotel").id(hotelDoc.getId().toString());indexRequest.source(JSON.toJSONString(hotelDoc),XContentType.JSON);request.add(indexRequest);}// 3.发起bulk请求client.bulk(request,RequestOptions.DEFAULT);}

es服务端查看:

5.6 自动补全查询的 JavaAPI

案例:

而自动补全的结果也比较特殊,解析的代码如下:

5.7 小结

文档操作的基本步骤:

初始化 RestHighLevelClient创建 XxxRequest。XXX是Index、Get、Update、Delete、Bulk准备参数(Index、Update、Bulk时需要)发送请求。调用 RestHighLevelClient.xxx()方法,xxx是index、get、update、delete、bulk解析结果(Get时需要)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。