1.Elasticsearch
字数: 0 字 时长: 0 分钟
1.1.了解ES
1.1.1.elasticsearch的作用
elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容
例如:
- 在GitHub搜索代码
- 在电商网站搜索商品
- 在Google搜索答案
- 在打车软件搜索附近的车
1.1.2.ELK技术栈
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:
而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
1.1.3.elasticsearch和lucene
elasticsearch底层是基于lucene来实现的。
Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。官网地址:https://lucene.apache.org/ 。
1.1.4.为什么不是其他搜索技术?
目前比较知名的搜索引擎技术排名:
虽然在早期,Apache Solr是最主要的搜索引擎技术,但随着发展elasticsearch已经渐渐超越了Solr,独占鳌头:
1.1.5.总结
什么是elasticsearch?
- 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能
什么是elastic stack(ELK)?
- 是以elasticsearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch
什么是Lucene?
- 是Apache的开源搜索引擎类库,提供了搜索引擎的核心API
1.2.倒排索引
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的一些概念
elasticsearch中有很多独有的概念,与mysql中略有差别,但也有相似之处。
1.3.1.文档和字段
elasticsearch是面向**文档(Document)**存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:
而Json文档中往往包含很多的字段(Field),类似于数据库中的列。
1.3.2.索引和映射
索引(Index),就是相同类型的文档的集合。
例如:
- 所有用户文档,就可以组织在一起,称为用户的索引;
- 所有商品的文档,可以组织在一起,称为商品的索引;
- 所有订单的文档,可以组织在一起,称为订单的索引;
相同类型的文档的集合
以上是不是就有三个索引 有点类似数据库的表
因此,我们可以把索引当做是数据库中的表。
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。
1.3.3.mysql与elasticsearch
我们统一的把mysql与elasticsearch的概念做一下对比:
MySQL | Elasticsearch | 说明 |
---|---|---|
Table | Index | 索引(index),就是文档的集合,类似数据库的表(table) |
Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
是不是说,我们学习了elasticsearch就不再需要mysql了呢?
并不是如此,两者各自有自己的擅长支出:
Mysql:擅长事务类型操作,可以确保数据的安全和一致性
Elasticsearch:擅长海量数据的搜索、分析、计算
因此在企业中,往往是两者结合使用:
- 对安全性要求较高的写操作,使用mysql实现
- 对查询性能要求较高的搜索需求,使用elasticsearch实现
- 两者再基于某种方式,实现数据的同步,保证一致性
1.4.安装elasticsearch
1.4.1部署单点es
因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:
docker network create es-net
1.4.2加载镜像
docker pull elasticsearch
文件很大、下载时间很长、耐心等待
查看镜像
ok 都在、如果你 pull 不指定版本的话 就是 latest
1.4.3运行
运行docker命令,部署单点es:
docker run -d \
--name es \ #名字
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ #JVM 堆内存大小 注意、我的虚拟机内存小、一定要大于 512
-e "discovery.type=single-node" \ # 模式 非集群
-v es-data:/usr/share/elasticsearch/data \ # 数据卷挂载 数据保存目录
-v es-plugins:/usr/share/elasticsearch/plugins \ #数据卷挂载 插件目录目录
--privileged \ # 参数是Docker容器的一个安全设置。它允许容器内的进程访问主机系统上的所有设备
--network es-net \ # 假如我们刚才的网络
-p 9200:9200 \ # https 用户访问端口
-p 9300:9300 \ # 集群容器各个节点互联的端口
elasticsearch:7.12.1 # 镜像名字:tag
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
-e "cluster.name=es-docker-cluster"
:设置集群名称-e "http.host=0.0.0.0"
:监听的地址,可以外网访问-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:JVM 堆内存大小 注意、我的虚拟机内存小、一定要大于 512-e "discovery.type=single-node"
:非集群模式-v es-data:/usr/share/elasticsearch/data
:挂载逻辑卷,绑定es的数据目录-v es-logs:/usr/share/elasticsearch/logs
:挂载逻辑卷,绑定es的日志目录-v es-plugins:/usr/share/elasticsearch/plugins
:挂载逻辑卷,绑定es的插件目录--privileged
:授予逻辑卷访问权--network es-net
:加入一个名为es-net的网络中-p 9200:9200
:端口映射配置
去网页浏览
启动成功!
1.5.安装kibana
1.5.1.安装
docker pull kibana
文件很大、下载时间很长、耐心等待
kibana可以给我们提供一个elasticsearch的可视化界面,便于我们学习。
- 注意 要跟 es 安装在一个网络中
docker run
-d \ #后台运行
--name kibana \ # 容器名字
-e ELASTICSEARCH_HOSTS=http://es:9200 \ # 环境变量 是不是配置了我们刚才那个玩意es 的端口、es 是刚才那个容器的名称啊
--network=es-net \ # es 的假如的那个网络啦
-p 5601:5601 \ # 端口映射
kibana:7.12.1 # 镜像名字:tag
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
--network es-net
:加入一个名为es-net的网络中,与elasticsearch在同一个网络中-e ELASTICSEARCH_HOSTS=http://es:9200"
:设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch-p 5601:5601
:端口映射配置
kibana启动一般比较慢,需要多等待一会,可以通过命令:
docker logs -f kibana
你刚输入 docker run 然后马上docker logs 卡一会是正常的后悔会输出下面的日志
瞪大眼睛
看到这个就启动成功了
浏览器
ok 大功告成!
1.5.2.DevTools
按照下图找到这个
这个就是 DSL的控制台
这个界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。
从上文我们得知、我们的数据是通过 http 请求 JSON格式语句、非常方便的发送请求
GET _search #GET 就是 get 请求、 _search 代表搜索
{
"query": {
"match_all": {}
}
}
这一段就是DSL 语句了、这个语句就是查询所有的数据
获得结果
其实它的本质就是发送一个请求到 es 的当中
我们自己随便写一个 注意得到的结果
跟这里一样的喔
1.6 安装 IK 分词器
1.6.1 分词测试
es 是需要分词的、比如一开始的 小米 华为 手机等等
这种文字的拆分是很复杂的、需要很特殊的算法、有科研经历或者比较会写算法的人可以去自行百度研究
在kibana的DevTools中测试: es在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好
下面做个测试
语法说明: POST:请求方式
/ analyze:请求路径,这里省略了htp://192.168.150.101:9200,有kibana帮我们补充
请求参数,json风格:
standard分词器analyzer:分词器类型,这里是默认
text:要分词的内容
{
"tokens" : [
{
"token" : "玉",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "林",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "师",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "范",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "学",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<IDEOGRAPHIC>",
"position" : 4
},
{
"token" : "院",
"start_offset" : 5,
"end_offset" : 6,
"type" : "<IDEOGRAPHIC>",
"position" : 5
},
{
"token" : "跟",
"start_offset" : 6,
"end_offset" : 7,
"type" : "<IDEOGRAPHIC>",
"position" : 6
},
{
"token" : "pyc",
"start_offset" : 8,
"end_offset" : 11,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "学",
"start_offset" : 12,
"end_offset" : 13,
"type" : "<IDEOGRAPHIC>",
"position" : 8
},
{
"token" : "习",
"start_offset" : 13,
"end_offset" : 14,
"type" : "<IDEOGRAPHIC>",
"position" : 9
},
{
"token" : "java",
"start_offset" : 15,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 10
},
{
"token" : "太",
"start_offset" : 20,
"end_offset" : 21,
"type" : "<IDEOGRAPHIC>",
"position" : 11
},
{
"token" : "棒",
"start_offset" : 21,
"end_offset" : 22,
"type" : "<IDEOGRAPHIC>",
"position" : 12
},
{
"token" : "辣",
"start_offset" : 22,
"end_offset" : 23,
"type" : "<IDEOGRAPHIC>",
"position" : 13
}
]
}
可以看到英语分的还是可以的、但是中文每个字分个词、这咋办?
换中文呗 好 来
POST /_analyze
{
"analyzer": "chinese",
"text": "玉林师范学院跟 pyc 学习 Java 太棒辣!"
}
{
"tokens" : [
{
"token" : "玉",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
},
{
"token" : "林",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
},
{
"token" : "师",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
},
{
"token" : "范",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
},
{
"token" : "学",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<IDEOGRAPHIC>",
"position" : 4
},
{
"token" : "院",
"start_offset" : 5,
"end_offset" : 6,
"type" : "<IDEOGRAPHIC>",
"position" : 5
},
{
"token" : "跟",
"start_offset" : 6,
"end_offset" : 7,
"type" : "<IDEOGRAPHIC>",
"position" : 6
},
{
"token" : "pyc",
"start_offset" : 8,
"end_offset" : 11,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "学",
"start_offset" : 12,
"end_offset" : 13,
"type" : "<IDEOGRAPHIC>",
"position" : 8
},
{
"token" : "习",
"start_offset" : 13,
"end_offset" : 14,
"type" : "<IDEOGRAPHIC>",
"position" : 9
},
{
"token" : "java",
"start_offset" : 15,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 10
},
{
"token" : "太",
"start_offset" : 20,
"end_offset" : 21,
"type" : "<IDEOGRAPHIC>",
"position" : 11
},
{
"token" : "棒",
"start_offset" : 21,
"end_offset" : 22,
"type" : "<IDEOGRAPHIC>",
"position" : 12
},
{
"token" : "辣",
"start_offset" : 22,
"end_offset" : 23,
"type" : "<IDEOGRAPHIC>",
"position" : 13
}
]
}
你发现还是一个叼样
它没办法理解中文的意思
标准分词器也是 不信你自己实验吧
POST /_analyze
{
"analyzer": "standard",
"text": "玉林师范学院跟 pyc 学习 Java 太棒辣!"
}
要是中文用、就没办法用这种方式了、比如里面有个手机、就分成了手和机两个词、这很不友好
处理中文分词,一般会使用IK分词器。官网:https://github.com/medcl/elasticsearch-analysis-ik
1.6.2 安装 IK分词器
在线安装ik插件(较慢)
# 进入容器内部
docker exec -it elasticsearch /bin/bash
# 在线下载并安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
#退出
exit
#重启容器
docker restart elasticsearch
离线安装ik插件(推荐)
1)查看数据卷目录
安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:
docker volume inspect es-plugins
显示结果:
[
{
"CreatedAt": "2022-05-06T10:06:34+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
"Name": "es-plugins",
"Options": null,
"Scope": "local"
}
]
说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data
这个目录中。
2)解压缩分词器安装包
3)上传到es容器的插件数据卷中
4)重启容器
# 4、重启容器
docker restart es
# 查看es日志
docker logs -f es
看到这个就成功了
1.6.3 测试
IK分词器包含两种模式:
ik_smart
:最少切分ik_max_word
:最细切分先测试ik_smart
POST /_analyze
{
"analyzer": "ik_smart",
"text": "玉林师范学院跟 pyc 学习 Java 太棒辣!"
}
{
"tokens" : [
{
"token" : "玉林",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "师范学院",
"start_offset" : 2,
"end_offset" : 6,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "跟",
"start_offset" : 6,
"end_offset" : 7,
"type" : "CN_CHAR",
"position" : 2
},
{
"token" : "pyc",
"start_offset" : 8,
"end_offset" : 11,
"type" : "ENGLISH",
"position" : 3
},
{
"token" : "学习",
"start_offset" : 12,
"end_offset" : 14,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "java",
"start_offset" : 15,
"end_offset" : 19,
"type" : "ENGLISH",
"position" : 5
},
{
"token" : "太棒",
"start_offset" : 20,
"end_offset" : 22,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "辣",
"start_offset" : 22,
"end_offset" : 23,
"type" : "CN_CHAR",
"position" : 7
}
]
}
这个分词就舒服很多了
再来测试 ik_max_word
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "玉林师范学院跟 pyc 学习 Java 太棒辣!"
}
{
"tokens" : [
{
"token" : "玉林",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "师范学院",
"start_offset" : 2,
"end_offset" : 6,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "师范",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "学院",
"start_offset" : 4,
"end_offset" : 6,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "跟",
"start_offset" : 6,
"end_offset" : 7,
"type" : "CN_CHAR",
"position" : 4
},
{
"token" : "pyc",
"start_offset" : 8,
"end_offset" : 11,
"type" : "ENGLISH",
"position" : 5
},
{
"token" : "学习",
"start_offset" : 12,
"end_offset" : 14,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "java",
"start_offset" : 15,
"end_offset" : 19,
"type" : "ENGLISH",
"position" : 7
},
{
"token" : "太棒",
"start_offset" : 20,
"end_offset" : 22,
"type" : "CN_WORD",
"position" : 8
},
{
"token" : "辣",
"start_offset" : 22,
"end_offset" : 23,
"type" : "CN_CHAR",
"position" : 9
}
]
}
1.7 扩展、停止词词典
1.7.1 为什么需要扩展停用词典?
随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“奥力给”,“鸡你太美”, "白嫖" 等。
测试:
POST /_analyze
{
"analyzer": "ik_smart",
"text": "今天在 B 站看了鸡你太美鬼畜作品,还白嫖了 UP 主,奥利给!"
}
{
"tokens" : [
{
"token" : "今天在",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "b",
"start_offset" : 4,
"end_offset" : 5,
"type" : "ENGLISH",
"position" : 1
},
{
"token" : "站",
"start_offset" : 6,
"end_offset" : 7,
"type" : "CN_CHAR",
"position" : 2
},
{
"token" : "看了",
"start_offset" : 7,
"end_offset" : 9,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "鸡",
"start_offset" : 9,
"end_offset" : 10,
"type" : "CN_CHAR",
"position" : 4
},
{
"token" : "你",
"start_offset" : 10,
"end_offset" : 11,
"type" : "CN_CHAR",
"position" : 5
},
{
"token" : "太美",
"start_offset" : 11,
"end_offset" : 13,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "鬼畜",
"start_offset" : 13,
"end_offset" : 15,
"type" : "CN_WORD",
"position" : 7
},
{
"token" : "作品",
"start_offset" : 15,
"end_offset" : 17,
"type" : "CN_WORD",
"position" : 8
},
{
"token" : "还",
"start_offset" : 18,
"end_offset" : 19,
"type" : "CN_CHAR",
"position" : 9
},
{
"token" : "白",
"start_offset" : 19,
"end_offset" : 20,
"type" : "CN_CHAR",
"position" : 10
},
{
"token" : "嫖",
"start_offset" : 20,
"end_offset" : 21,
"type" : "CN_CHAR",
"position" : 11
},
{
"token" : "了",
"start_offset" : 21,
"end_offset" : 22,
"type" : "CN_CHAR",
"position" : 12
},
{
"token" : "up",
"start_offset" : 23,
"end_offset" : 25,
"type" : "ENGLISH",
"position" : 13
},
{
"token" : "主",
"start_offset" : 26,
"end_offset" : 27,
"type" : "CN_CHAR",
"position" : 14
},
{
"token" : "奥",
"start_offset" : 28,
"end_offset" : 29,
"type" : "CN_CHAR",
"position" : 15
},
{
"token" : "利",
"start_offset" : 29,
"end_offset" : 30,
"type" : "CN_CHAR",
"position" : 16
},
{
"token" : "给",
"start_offset" : 30,
"end_offset" : 31,
"type" : "CN_CHAR",
"position" : 17
}
]
}
看这个分词结果、也就是它的字典里没有根本没有白嫖、鸡你太美、奥利给、b 站等等词语
注意、实际上线还有很多忌讳的、比如国家领导人的名字、毒品、祖安用语等等、能不能屏蔽呢? 还有注意大家觉得分词中的 ‘的’也被分成一个词、的、啊、喔、嗷等等语气助词能不能不让他分词?
所以我们的词汇也需要不断的更新,IK分词器提供了扩展词汇的功能。
1.7.2 配置拓展词库、停用词库
ik分词器-拓展词库
要拓展ik分词器的词库,只需要修改一个ik分词器目录中的config目录中的lkAnalyzer.cfg.xml文件:
1)打开IK分词器config目录:
/var/lib/docker/volumes/es-plugins/_data/ik/config/
写上下面配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>
注意:ext.dic 和 stopword.dic 是文件名
这俩文件放在那里那呢? 实在当前文件所在目录
新建一个文件 ext.dic
打开它
我这里用 vscode 打开并且加入我们需要的词语
保存然后 调整停用词库
ik分词器-停用词库
要禁用某些敏感词条,只需要修改一个ik分词器目录中的config目录中的IkAnalyzer.cfg.xml文件:
还是老样子、但是 这里有个现成的 停用的词典 打开看看
会发现这里有很多英语的没意义的词 我们可以把中文没意义的词和敏感词放进去
我这里祖安是敏感词 然后保存重启
重启需要一定的时间、感觉挺慢的,耐心等待,不过也因人而异,可能是我的虚拟机性能不好吧。
docker restart es
1.7.3 测试
POST /_analyze
{
"analyzer": "ik_smart",
"text": "今天在b站看了鸡你太美的鬼畜作品,还白嫖了 UP 主,奥利给,但是评论有很多祖安人!"
}
{
"tokens" : [
{
"token" : "今天在",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "b站",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "看了",
"start_offset" : 5,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "鸡你太美",
"start_offset" : 7,
"end_offset" : 11,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "鬼畜",
"start_offset" : 11,
"end_offset" : 13,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "作品",
"start_offset" : 13,
"end_offset" : 15,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "还",
"start_offset" : 16,
"end_offset" : 17,
"type" : "CN_CHAR",
"position" : 6
},
{
"token" : "白嫖",
"start_offset" : 17,
"end_offset" : 19,
"type" : "CN_WORD",
"position" : 7
},
{
"token" : "了",
"start_offset" : 19,
"end_offset" : 20,
"type" : "CN_CHAR",
"position" : 8
},
{
"token" : "up",
"start_offset" : 21,
"end_offset" : 23,
"type" : "ENGLISH",
"position" : 9
},
{
"token" : "主",
"start_offset" : 24,
"end_offset" : 25,
"type" : "CN_CHAR",
"position" : 10
},
{
"token" : "奥利给",
"start_offset" : 26,
"end_offset" : 29,
"type" : "CN_WORD",
"position" : 11
},
{
"token" : "但是",
"start_offset" : 30,
"end_offset" : 32,
"type" : "CN_WORD",
"position" : 12
},
{
"token" : "评论",
"start_offset" : 32,
"end_offset" : 34,
"type" : "CN_WORD",
"position" : 13
},
{
"token" : "有",
"start_offset" : 34,
"end_offset" : 35,
"type" : "CN_CHAR",
"position" : 14
},
{
"token" : "很多",
"start_offset" : 35,
"end_offset" : 37,
"type" : "CN_WORD",
"position" : 15
},
{
"token" : "祖",
"start_offset" : 37,
"end_offset" : 38,
"type" : "CN_CHAR",
"position" : 16
},
{
"token" : "安人",
"start_offset" : 38,
"end_offset" : 40,
"type" : "CN_WORD",
"position" : 17
}
]
}
捏 OK
1.7.4 小结
分词器的作用是什么?
创建倒排索引时对文档分词
用户搜索时,对输入的内容分词
IK分词器有几种模式?
ik smart:智能切分,粗粒度
ik max word:最细切分,细粒度
IK分词器如何拓展词条?如何停用词条?
利用config目录的lkAnalyzer.cfg.xml文件添加拓展词典和停用词典
在词典中添加拓展词条或者停用词条
2.索引库操作
字数: 0 字 时长: 0 分钟
索引库就类似数据库表,mapping映射就类似表的结构。
我们要向es中存储数据,必须先创建“库”和“表”。
2.1.mapping映射属性
数据库建表需要建表语句 建表语句是不是需要字段 id,name,age,tag等等 也就是对字段的约束
索引库创建的时候需要 mapping 映射、这是对文档的约束
mapping是对索引库中文档的约束,常见的mapping属性包括:
Es官网手册:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html
东西很多啊 将来对那个有兴趣、或者看到那个大佬的代码用了什么属性你看不懂、来这里查看、不懂英语可以翻译啥的对吧
- type:字段数据类型,常见的简单类型有:
- 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object
- index:是否创建索引,默认为true、比如图片 url 地址、比如邮箱、没有搜索意义
- analyzer:使用哪种分词器、其实就是 text 需要分词器而已、其他的基本不需要 中文就是 ik 分词器嘛
- properties:该字段的子字段、就是下面的 firstName 和 lastName 你需要那个就搞那个
例如下面的json文档:
{
"age": 24,
"weight": 62.7,
"height":176,
"isMarried": false,
"info": "Java后端开发菜鸡",
"email": "popkerwim@yeah.net",
"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_smart
- email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
- score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
- name:类型为object,需要定义多个子属性
- name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
- name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
mapping常见属性有哪些?
type:数据类型
index:是否索引
analyzer:分词器
properties:子字段
type常见的有哪些?
字符串:text、keyword
数字:long、integer、short、byte、double、float
布尔:boolean
日期:date
对象:object
2.2.索引库的CRUD
这里我们统一使用Kibana编写DSL的方式来演示。
2.2.1.创建索引库和映射
基本语法:
- 请求方式:PUT
- 请求路径:/索引库名,可以自定义
- 请求参数:mapping映射
格式:
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart" #需要分词、分词器是ik_smart
},
"字段名2":{
"type": "keyword", # 不需要分词
"index": "false" # 将来不创建倒排索引 不写默认是 true
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
示例:
PUT /kerwim
{
"mappings": {
"properties": {
"info":{
"type":"text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": false
},
"name":{
"type": "object",
"properties": {
"firstName":{
"type": "keyword"
},
"lastName":{
"type": "keyword"
}
}
}
}
}
}
创建成功
2.2.2.查询索引库
基本语法:
请求方式:GET
请求路径:/索引库名
请求参数:无
格式:
GET /索引库名
GET /kerwim
示例:
2.2.3.修改索引库
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
语法说明:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
#修改
PUT /kerwim/_mapping
{
"properties":{
"age":{
"type":"integer"
}
}
}
示例:
2.2.4.删除索引库
语法:
请求方式:DELETE
请求路径:/索引库名
请求参数:无
格式:
DELETE /索引库名
在kibana中测试:
2.2.5.总结
索引库操作有哪些?
- 创建索引库:PUT /索引库名
- 查询索引库:GET /索引库名
- 删除索引库:DELETE /索引库名
- 添加字段:PUT /索引库名/_mapping
3.文档操作
字数: 0 字 时长: 0 分钟
3.1.新增文档
语法:
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
示例:
POST /heima/_doc/1
{
"info": "玉林师范学院",
"email": "zy@163.cn",
"name": {
"firstName": "云",
"lastName": "赵"
}
}
响应:
3.2.查询文档
根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。
语法:
GET /{索引库名称}/_doc/{id}
通过kibana查看数据:
GET /kerwim/_doc/1
3.3.删除文档
删除使用DELETE请求,同样,需要根据id进行删除:
语法:
DELETE /{索引库名}/_doc/id值
示例:
DELETE /kerwim/_doc/1
在查询一次
3.4.修改文档
修改有两种方式:
- 全量修改:直接覆盖原来的文档
- 增量修改:修改文档中的部分字段
3.4.1.全量修改
全量修改是覆盖原来的文档,其本质是:
- 根据指定的id删除文档
- 新增一个相同id的文档
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
示例:
PUT /kerwim/_doc/1
{
"info":"玉林师范学院计算机科学与工程学院",
"email":"zy@163.com.cn",
"name":{
"firstName":"云",
"lastName":"赵"
}
}
3.4.2.增量修改
增量修改是只修改指定id匹配的文档中的部分字段。
语法:
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
示例:
# 增量修改
POST /kerwim/_update/1
{
"doc": {
"email": "ZhaoYun@yeah.net"
}
}
3.5.总结
文档操作有哪些?
- 创建文档:POST /{索引库名}/_doc/文档id
- 查询文档:GET /{索引库名}/_doc/文档id
- 删除文档:DELETE /{索引库名}/_doc/文档id
- 修改文档:
- 全量修改:PUT /{索引库名}/_doc/文档id
- 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}
4.RestAPI
字数: 0 字 时长: 0 分钟
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
其中的Java Rest Client又包括两种:
- Java Low Level Rest Client
- Java High Level Rest Client
这里使用的是的是Java High Level Rest Client客户端API
4.1准备工作
创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:
- 字段名
- 字段数据类型
- 是否参与搜索
- 是否需要分词
- 如果分词,分词器是什么?
其中:
- 字段名、字段数据类型,可以参考数据表结构的名称和类型
- 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
- 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
- 分词器,我们可以统一使用ik_max_word
DROP TABLE IF EXISTS `tb_hotel`;
CREATE TABLE `tb_hotel` (
`id` bigint(20) NOT NULL COMMENT '酒店id',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店名称',
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店地址',
`price` int(10) NOT NULL COMMENT '酒店价格',
`score` int(2) NOT NULL COMMENT '酒店评分',
`brand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店品牌',
`city` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所在城市',
`star_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店星级,1星到5星,1钻到5钻',
`business` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商圈',
`latitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '纬度',
`longitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '经度',
`pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店图片',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
根据 sql 来设计 es 的 mapping
几个特殊字段说明:
- location:地理坐标,里面包含精度、纬度
- all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索
地理坐标说明:
copy_to说明:
PUT /hotel
{
"mappings": {
"properties": {
"id":{ #字段名字
"type": "keyword" #字段数据类型 text=分词 keyword=不分词
},
"name":{
"type": "text",
"analyzer": "ik_max_word", #指定分词器
"copy_to": "all" # 如果你想根据多个字段搜、通过 copy_to
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point" #地理坐标表达方式
},
"pic":{
"type": "keyword",
"index": false #不参与搜索 就 index:false 默认是 true 参与搜索
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
理想化是这样的
4.2 初始化RestClient
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
分为三步:
1)引入es的RestHighLevelClient依赖:
<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.150.101:9200")
));
这里为了单元测试方便,我们创建一个测试类HotelIndexTest,然后将初始化的代码编写在@BeforeEach方法中:
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;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
4.3.创建索引库
4.3.1.代码解读
创建索引库的API如下:
代码分为三步:
- 1)创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。
- 2)添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
- 3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。
4.3.2.完整示例
在hotel-demo的cn.kerwim.hotel.constants包下,创建一个类,定义mapping映射的JSON字符串常量:
ublic 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" +
" },\n" +
" \"starName\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"business\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\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" +
"}";
}
其实上面的字符串是就是我们上面写的那一段去掉PUT /hotel
在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);
}
成功 然后去网页版查询这个 mapping
4.4.删除索引库
删除索引库的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);
}
确实被删掉了
4.5.判断索引库是否存在
判断索引库是否存在,本质就是查询,对应的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.out.println(exists ? "索引库已经存在":"索引库不存在");
}
肯定是不存在的、因为我上面刚删掉
4.6.总结
JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。
索引库操作的基本步骤:
- 初始化RestHighLevelClient
- 创建XxxIndexRequest。XXX是Create、Get、Delete
- 准备DSL( Create时需要,其它是无参)
- 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete
5.RestClient操作文档
字数: 0 字 时长: 0 分钟
为了与索引库操作分离,我们再次参加一个测试类,做两件事情:
- 初始化RestHighLevelClient
- 我们的酒店数据在数据库,需要利用IHotelService去查询,所以注入这个接口
package cn.kerwim.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 {
@Autowired
private IHotelService hotelService;
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@AfterEach
void 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
因此,我们需要定义一个新的类型,与索引库结构吻合:
package cn.kerwim.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();
}
}
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查询酒店数据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转json
String 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);
}
5.2.查询文档
5.2.1.语法说明
查询的DSL语句如下:
GET /hotel/_doc/{id}
非常简单,因此代码大概分两步:
- 准备Request对象
- 发送请求
不过查询的目的是得到结果,解析为HotelDoc,因此难点是结果的解析。完整代码如下:
可以看到,结果是一个JSON,其中文档放在一个_source
属性中,因此解析就是拿到_source
,反序列化为Java对象即可。
与之前类似,也是三步走:
- 1)准备Request对象。这次是查询,所以是GetRequest
- 2)发送请求,得到结果。因为是查询,这里调用client.get()方法
- 3)解析结果,就是对JSON做反序列化
5.2.2.完整代码
在hotel-demo的HotelDocumentTest测试类中,编写单元测试:
@Test
void testGetDocumentById() throws IOException {
// 1.准备Request
GetRequest 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);
}
5.3.修改文档
5.3.1.语法说明
修改我们讲过两种方式:
- 全量修改:本质是先根据id删除,再新增
- 增量修改:修改文档中的指定字段值
在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:
- 如果新增时,ID已经存在,则修改
- 如果新增时,ID不存在,则新增
这里不再赘述,我们主要关注增量修改。
代码示例如图:
与之前类似,也是三步走:
- 1)准备Request对象。这次是修改,所以是UpdateRequest
- 2)准备参数。也就是JSON文档,里面包含要修改的字段
- 3)更新文档。这里调用client.update()方法
5.3.2.完整代码
在hotel-demo的HotelDocumentTest测试类中,编写单元测试:
@Test
void testUpdateDocument() throws IOException {
// 1. 准备 request 对象
UpdateRequest request = new UpdateRequest("hotel", "61083");
// 2. 准备请求参数
request.doc(
"price","998",
"starName","四钻"
);
// 3. 发送请求
client.update(request,RequestOptions.DEFAULT);
}
5.4.删除文档
删除的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.准备Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
P97