Elasticsearch基础篇
基础篇
数据库的模糊搜索功能单一,匹配条件非常苛刻,必须恰好包含用户搜索的关键字。而在搜索引擎中,用户输入出现个别错字,或者用拼音搜索、同义词搜索都能正确匹配到数据。
综上,在面临海量数据的搜索,或者有一些复杂搜索需求的时候,推荐使用专门的搜索引擎来实现搜索功能。
学习目标:
- 理解倒排索引原理
- 会使用IK分词器
- 理解索引库Mapping映射的属性含义
- 能创建索引库及映射
- 能实现文档的CRUD
1.初识elasticsearch
Elasticsearch的官方网站如下:
https://www.elastic.co/cn/elasticsearch
本章我们一起来初步了解一下Elasticsearch的基本原理和一些基础概念。
1.1.认识和安装
Elasticsearch是由elastic公司开发的一套搜索引擎技术,它是elastic技术栈中的一部分。完整的技术栈包括:
- Elasticsearch:用于数据存储、计算和搜索
- Logstash/Beats:用于数据收集
- Kibana:用于数据可视化
整套技术栈被称为ELK,经常用来做日志收集、系统监控和状态分析等等:

整套技术栈的核心就是用来存储、搜索、计算的Elasticsearch,因此我们接下来学习的核心也是Elasticsearch。
我们要安装的内容包含2部分:
- elasticsearch:存储、搜索和运算
- kibana:图形化展示
Elasticsearch,是提供核心的数据存储、搜索、分析功能的。
关于Kibana,Elasticsearch对外提供的是Restful风格的API,任何操作都可以通过发送http请求来完成。不过http请求的方式、路径、还有请求参数的格式都有严格的规范。这些规范我们肯定记不住,因此我们要借助于Kibana这个服务。
Kibana是elastic公司提供的用于操作Elasticsearch的可视化控制台。它的功能非常强大,包括:
- 对Elasticsearch数据的搜索、展示
- 对Elasticsearch数据的统计、聚合,并形成图形化报表、图形
- 对Elasticsearch的集群状态监控
- 它还提供了一个开发控制台(DevTools),在其中对Elasticsearch的Restful的API接口提供了语法提示(我们安装kibana的原因)
1.1.1.安装elasticsearch
通过下面的Docker命令即可安装单机版本的elasticsearch:
1 | docker run -d \ |
提前关防火墙!

- -e “ES_JAVA_OPTS=-Xms512m -Xmx512m”:设置 Elasticsearch 的 Java 堆内存大小为 512MB(初始值和最大值)。
- -e “discovery.type=single-node”:指定 Elasticsearch 为单节点模式,防止集群发现功能启动。
这里我们采用的是elasticsearch的7.12.1版本,由于8以上版本的JavaAPI变化很大,在企业中应用并不广泛
安装完成后,访问9200端口,即可看到响应的Elasticsearch服务的基本信息:

1.1.2.安装Kibana
通过下面的Docker命令,即可部署Kibana:
1 | docker run -d \ |
安装完成后,直接访问5601端口,即可看到控制台页面:

选择Explore on my own之后,进入主页面
然后选中Dev tools,进入开发工具页面:

1.2倒排索引
elasticsearch之所以有如此高性能的搜索表现,正是得益于底层的倒排索引技术。那么什么是倒排索引呢?
倒排索引的概念是基于MySQL这样的正向索引而言的。
1.2.1.正向索引

其中的id字段已经创建了索引,由于索引底层采用了B+树结构,因此我们根据id搜索的速度会非常快。但是其他字段例如title,只在叶子节点上存在。

因此要根据title搜索的时候只能遍历树中的每一个叶子节点,判断title数据是否符合要求。
比如用户的SQL语句为:
1 | select * from tb_goods where title like '%手机%'; |
那搜索的大概流程如图:

说明:
- 1)检查到搜索条件为
like '%手机%',需要找到title中包含手机的数据 - 2)逐条遍历每行数据(每个叶子节点),比如第1次拿到
id为1的数据 - 3)判断数据中的
title字段值是否符合条件 - 4)如果符合则放入结果集,不符合则丢弃
- 5)回到步骤1
综上,根据id精确匹配时,可以走索引,查询效率较高。而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。
因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配。
而倒排索引恰好解决的就是根据部分词条模糊匹配的问题。
1.2.2.倒排索引

倒排索引中有两个非常重要的概念:
- 文档(
Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息

- 词条(
Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:
- 将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息

- 因为词条唯一性,可以给词条创建正向索引
倒排索引的搜索流程如下(以搜索"华为手机"为例),如图:


流程描述:
- 用户搜索关键词:华为手机
- 分词:华为、手机
- 倒排索引查词条 → 得到文档 id 集合
- 根据文档 id 查找具体文档内容(正向索引)
⚠️ 倒排索引能高效工作是因为“词条”和“文档 id”都建立了高效索引。
虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
正向索引 VS 倒排索引
| 比较项目 | 正向索引(传统数据库) | 倒排索引(Elasticsearch) |
|---|---|---|
| 查询方式 | 根据 id 查询某条记录,再看字段中是否包含关键词 | 先对关键词分词,再查每个词出现在哪些文档中 |
| 核心逻辑 | 文档 -> 词条 | 词条 -> 文档 |
| 适用场景 | 精确匹配:如根据 id、主键查询 | 模糊搜索、全文检索:如搜索 “小米手机” |
| 性能 | 非索引字段模糊搜索性能差(会全表扫描) | 性能极高,分词+索引查找词条 |
| 缺点 | 无法有效支持模糊匹配 | 无法用于字段排序或非分词字段的复杂查询 |

1.2.3.正向和倒排
那么为什么一个叫做正向索引,一个叫做倒排索引呢?
- 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
- 而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。

是不是恰好反过来了?
| 名称 | 过程 |
|---|---|
| 正向索引 | 文档 → 词条 |
| 倒排索引 | 词条 → 文档 |
那么两者方式的优缺点是什么呢?
| 类型 | 优势 | 劣势 |
|---|---|---|
| 正向索引 | 精确查询速度快,支持排序、范围查询 | 模糊搜索时性能差,全表扫描 |
| 倒排索引 | 模糊搜索性能优越,支持全文检索,适合搜索引擎 | 不能用于排序,仅支持词条维度的索引 |
1.3.基础概念
elasticsearch中有很多独有的概念,与mysql中略有差别,但也有相似之处。
1.3.1.文档和字段
elasticsearch是面向**文档(Document)**存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:

因此,原本数据库中的一行数据就是ES中的一个JSON文档;而数据库中每行数据都包含很多列,这些列就转换为JSON文档中的字段(Field)。

1.3.2.索引和映射
索引:某个字段词义逻辑雷同的一系列文档的“文档集合”。不一定要json格式完全一致。
随着业务发展,需要在es中存储的文档也会越来越多,比如有商品的文档、用户的文档、订单文档等等:
所有文档都散乱存放显然非常混乱,也不方便管理。
因此,我们要将类型相同的文档集中在一起管理,称为索引(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 |

- MySQL 的库 → ES 的索引库
- MySQL 的表结构 → ES 的 Mapping
- MySQL 的行 → ES 的文档
- MySQL 的列 → ES 的字段
那是不是说,我们学习了elasticsearch就不再需要mysql了呢?
并不是如此,两者各自有自己的擅长之处:
- Mysql:擅长事务类型操作,可以确保数据的安全和一致性
- Elasticsearch:擅长海量数据的搜索、分析、计算

因此在企业中,往往是两者结合使用:
- 对安全性要求较高的写操作,使用mysql实现
- 对查询性能要求较高的搜索需求,使用elasticsearch实现
- 两者再基于某种方式(Logstash 通过 JDBC 插件从 MySQL、PostgreSQL 等数据库读取数据,同步到 Elasticsearch 用于搜索。例如:将用户信息表同步到 ES,支持实时搜索),实现数据的同步,保证一致性

1.4.IK分词器
Elasticsearch的关键就是倒排索引,而倒排索引依赖于对文档内容的分词,而分词则需要高效、精准的分词算法,IK分词器就是这样一个中文分词算法。

1.4.1.安装IK分词器
方案一:在线安装
运行一个命令即可:
1 | docker exec -it es ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip |
然后重启es容器:
1 | docker restart es |
方案二:离线安装
如果网速较差,也可以选择离线安装。
首先,查看之前安装的Elasticsearch容器的plugins数据卷目录:
1 | docker volume inspect es-plugins |
结果如下:
1 | [ |
可以看到elasticsearch的插件挂载到了/var/lib/docker/volumes/es-plugins/_data这个目录。我们需要把IK分词器上传至这个目录。
1.4.2.使用IK分词器
IK分词器包含两种模式:
| 模式名 | 描述 |
|---|---|
ik_smart |
智能分词,粗粒度,只保留最核心词义(如搜索标题、精确匹配) |
ik_max_word |
最大词粒度,细粒度,列出所有可能组合(如文章内容、模糊搜索) |
ik_smart:智能语义切分(最小划分,只保留最核心语义的词。在不影响分词后词元的含义下“粗”粒度划分,减少过多划分后冗余词干扰。需要用户输入明确的关键词来严格匹配。适合主搜索字段(如标题、名称),和需要精准匹配的场景(如订单号、用户名)。)ik_max_word:最细粒度切分(穷尽所有可能组合,覆盖所有可能子词。保证用户输入可能不完整或包含子词。例如:“中华人民共和国”,搜索:“人民共和国”,也可以搜到整个句子。适合长文本内容(如文章正文、评论),和需要模糊搜索或高召回的场景(如日志关键词检索)。)
我们在Kibana的DevTools上来测试分词器,首先测试Elasticsearch官方提供的标准分词器:

1 | POST /_analyze |

可以看到,标准分词器只能1字1词条,无法正确对中文做分词。
我们再测试IK分词器:
1 | POST /_analyze |

1.4.3.拓展词典
随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“泰裤辣”,“小黑子漏出鸡脚了吧” 等。
IK分词器无法对这些词汇分词,测试一下:

所以要想正确分词,IK分词器的词库也需要不断的更新,IK分词器提供了扩展词汇的功能。
1)打开IK分词器config目录:

2)在IKAnalyzer.cfg.xml配置文件内容添加:
1 |
|
3)在IK分词器的config目录新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改
1 | 王源 |
4)重启elasticsearch
1 | docker restart es |
再次测试,可以发现传智播客和泰裤辣都正确分词了:

1.4.4.总结
分词器作用:
- 创建索引时对文档分词
- 查询时对用户输入分词
IK 分词模式:
ik_smart:粗粒度ik_max_word:细粒度
拓展词库方法:
- 配置
IKAnalyzer.cfg.xml - 新建
ext.dic添加自定义词条
2.索引库操作


2.1.Mapping映射属性
Mapping是对索引库中文档的约束,常见的Mapping属性包括:
-
type: 是否创建倒排索引(默认 true) - 设置为 false 则该字段无法被搜索1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
:字段数据类型,常见的简单类型有:
- 字符串:`text`(可分词的文本)、`keyword`(精确值,例如:品牌、国家、ip地址)
- 数值:`long`、`integer`、`short`、`byte`、`double`、`float`、
- 布尔:`boolean`
- 日期:`date`
- 对象:`object`
- `index`:是否创建索引,默认为`true`
- `analyzer`:使用哪种分词器
- `properties`:该字段的子字段
| 类型 | 说明 |
| --------- | ------------------------------------ |
| `text` | 可分词,适用于长文本,如文章、标题等 |
| `keyword` | 精确匹配,如分类、邮箱、IP等 |
| `integer` | 整型,适用于年龄、数量等 |
| `boolean` | 布尔值 true / false |
| `date` | 日期时间格式 |
| `object` | 嵌套对象(JSON子结构) |
其他属性:
- ```
index -
analyzer: 使用的分词器(ik_smart、standard等) -
properties: 用于定义嵌套对象的字段结构(类似 JSON 的子字段)

例如下面的json文档:
1 | { |


2.2.索引库的CRUD
由于Elasticsearch采用的是Restful风格的API,因此其请求方式和路径相对都比较规范,而且请求参数也都采用JSON风格。
我们直接基于Kibana的DevTools来编写请求做测试,由于有语法提示,会非常方便。
2.2.1.创建索引库和映射
基本语法:
- 请求方式:
PUT - 请求路径:
/索引库名,可以自定义 - 请求参数:
mapping映射
格式:
1 | PUT /索引库名称 |
示例:
1 | PUT /heima |
2.2.2.查询索引库
基本语法:
- 请求方式:GET
- 请求路径:/索引库名
- 请求参数:无
格式:
1 | GET /索引库名 |
示例:
1 | GET /heima |
2.2.3.修改索引库
❗ 注意:Elasticsearch 不允许修改已有字段类型或分词器,只能添加新字段!
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
语法说明:
1 | PUT /索引库名/_mapping |
示例:
1 | PUT /heima/_mapping |
2.2.4.删除索引库
语法:
- 请求方式:DELETE
- 请求路径:/索引库名
- 请求参数:无
格式:
1 | DELETE /索引库名 |
示例:
1 | DELETE /heima |
2.2.5.总结
索引库操作有哪些?
- 创建索引库:PUT /索引库名
- 查询索引库:GET /索引库名
- 删除索引库:DELETE /索引库名
- 修改索引库,添加字段:PUT /索引库名/_mapping
可以看到,对索引库的操作基本遵循的Restful的风格,因此API接口非常统一,方便记忆。
| 操作 | 请求方式 | 请求路径 | 请求体 |
|---|---|---|---|
| 创建索引 | PUT | /索引名 |
有 |
| 查询索引 | GET | /索引名 |
无 |
| 删除索引 | DELETE | /索引名 |
无 |
| 添加字段 | PUT | /索引名/_mapping |
有 |
注:POST /索引库名 是用于向索引库中新增文档(数据),而不是创建索引结构


| 操作类型 | 请求方法 | 路径 | 用途 |
|---|---|---|---|
| 创建索引库 | PUT |
/index_name |
创建索引和结构(Mapping) |
| 添加文档(自动ID) | POST |
/index_name |
插入数据文档 |
| 添加文档(指定ID) | POST |
/index_name/_doc/{id} |
插入/更新数据文档 |
3.文档操作
有了索引库,接下来就可以向索引库中添加数据了。
Elasticsearch中的数据其实就是JSON风格的文档。操作文档自然保护增、删、改、查等几种常见操作,我们分别来学习。
3.1.新增文档
语法:
1 | POST /索引库名/_doc/文档id |
示例:
1 | POST /heima/_doc/1 |
说明:
- 路径中的
_doc是文档类型,ES7+ 虽然只支持一个类型,但这个字段保留。 - 文档ID可指定(如
/1),也可不写让系统自动生成。
响应:


3.2.查询文档
根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。
语法:
1 | GET /{索引库名称}/_doc/{id} |
示例:
1 | GET /heima/_doc/1 |
说明:
- 查询的是整个文档内容,包括字段值和元信息(如
_id,_index,_version等)。
查看结果:

3.3.删除文档
删除使用DELETE请求,同样,需要根据id进行删除:
语法:
1 | DELETE /{索引库名}/_doc/id值 |
示例:
1 | DELETE /heima/_doc/1 |
说明:
- 删除后,该文档将不再存在,查询也查不到。
结果:

3.4.修改文档
修改有两种方式:
- 全量修改:直接覆盖原来的文档
- 局部修改:修改文档中的部分字段
3.4.1.全量修改
全量修改是覆盖原来的文档,其本质是两步操作:
- 根据指定的id删除文档
- 新增一个相同id的文档
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
1 | PUT /{索引库名}/_doc/文档id |
示例:
1 | PUT /heima/_doc/1 |
说明:
- 会完全覆盖原文档(未提供的字段将被删除)。
- 如果 ID 不存在,会创建新文档(created);存在则覆盖(updated)。
由于id为1的文档已经被删除,所以第一次执行时,得到的反馈是created:

所以如果执行第2次时,得到的反馈则是updated:

3.4.2.局部修改

局部修改是只修改指定id匹配的文档中的部分字段。
语法:
1 | POST /{索引库名}/_update/文档id |
示例:
1 | POST /heima/_update/1 |
说明:
- 只修改指定字段,不影响其他字段。
执行结果:

3.5.批处理

批处理采用POST请求,基本语法如下:
1 | POST _bulk |




说明:
- 每个操作是一对 JSON 对象,一行一个,不能少。
- 用于提高写入或删除的性能,非常适合大批量数据导入。
其中:
-
index代表删除操作 - `_index`:指定索引库名 - `_id`指定要操作的文档id1
2
3
4
5
6
7
8
9
代表新增操作
- `_index`:指定索引库名
- `_id`指定要操作的文档id
- `{ "field1" : "value1" }`:则是要新增的文档内容
- ```
delete -
update1
2
3
4
5
6
7
8
9
代表更新操作
- `_index`:指定索引库名
- `_id`指定要操作的文档id
- `{ "doc" : {"field2" : "value2"} }`:要更新的文档字段
示例,批量新增:
POST /_bulk
{“index”: {“_index”:“heima”, “_id”: “3”}}
{“info”: “黑马程序员C++讲师”, “email”: “ww@itcast.cn”, “name”:{“firstName”: “五”, “lastName”:“王”}}
{“index”: {“_index”:“heima”, “_id”: “4”}}
{“info”: “黑马程序员前端讲师”, “email”: “zhangsan@itcast.cn”, “name”:{“firstName”: “三”, “lastName”:“张”}}
1 |
|
POST /_bulk
{“delete”:{“_index”:“heima”, “_id”: “3”}}
{“delete”:{“_index”:“heima”, “_id”: “4”}}
1 |
|
1 |
|
1 |
|
1 |
|
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class IndexTest {
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@Test
void testConnect() {
System.out.println(client);
}
@AfterEach//结束方法
void tearDown() throws IOException {
client.close();
}
}
1 |
|
PUT /items
{
“mappings”: {
“properties”: {
“id”: {
“type”: “keyword”
},
“name”:{
“type”: “text”,
“analyzer”: “ik_max_word”
},
“price”:{
“type”: “integer”
},
“stock”:{
“type”: “integer”
},
“image”:{
“type”: “keyword”,
“index”: false
},
“category”:{
“type”: “keyword”
},
“brand”:{
“type”: “keyword”
},
“sold”:{
“type”: “integer”
},
“commentCount”:{
“type”: “integer”,
“index”: false
},
“isAD”:{
“type”: “boolean”
},
“updateTime”:{
“type”: “date”
}
}
}
}
1 |
|
@Test
void testCreateIndex() throws IOException {
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest(“items”);
// 2.准备请求参数
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
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" +
" },\n" +
" “price”:{\n" +
" “type”: “integer”\n" +
" },\n" +
" “stock”:{\n" +
" “type”: “integer”\n" +
" },\n" +
" “image”:{\n" +
" “type”: “keyword”,\n" +
" “index”: false\n" +
" },\n" +
" “category”:{\n" +
" “type”: “keyword”\n" +
" },\n" +
" “brand”:{\n" +
" “type”: “keyword”\n" +
" },\n" +
" “sold”:{\n" +
" “type”: “integer”\n" +
" },\n" +
" “commentCount”:{\n" +
" “type”: “integer”\n" +
" },\n" +
" “isAD”:{\n" +
" “type”: “boolean”\n" +
" },\n" +
" “updateTime”:{\n" +
" “type”: “date”\n" +
" }\n" +
" }\n" +
" }\n" +
“}”;
1 |
|
DELETE /hotel
1 |
|
@Test
void testDeleteIndex() throws IOException {
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest(“items”);
// 2.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
1 |
|
GET /hotel
1 |
|
@Test
void testExistsIndex() throws IOException {
// 1.创建Request对象
GetIndexRequest request = new GetIndexRequest(“items”);
// 2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.输出
System.err.println(exists ? “索引库已经存在!” : “索引库不存在!”);
}
1 |
|
package com.hmall.item.es;
import com.hmall.item.service.IItemService;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest(properties = “spring.profiles.active=local”)
public class DocumentTest {
private RestHighLevelClient client;
@Autowired
private IItemService itemService;
@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();
}
}
1 |
|
package com.hmall.item.domain.po;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel(description = “索引库实体”)
public class ItemDoc{
@ApiModelProperty("商品id")
private String id;
@ApiModelProperty("商品名称")
private String name;
@ApiModelProperty("价格(分)")
private Integer price;
@ApiModelProperty("商品图片")
private String image;
@ApiModelProperty("类目名称")
private String category;
@ApiModelProperty("品牌名称")
private String brand;
@ApiModelProperty("销量")
private Integer sold;
@ApiModelProperty("评论数")
private Integer commentCount;
@ApiModelProperty("是否是推广广告,true/false")
private Boolean isAD;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
}
1 |
|
POST /{索引库名}/_doc/1
{
“name”: “Jack”,
“age”: 21
}
1 |
|
@Test
void testAddDocument() throws IOException {
// 1.根据id查询商品数据
Item item = itemService.getById(100002644680L);
// 2.转换为文档类型
ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
// 3.将ItemDTO转json
String doc = JSONUtil.toJsonStr(itemDoc);
// 1.准备Request对象
IndexRequest request = new IndexRequest("items").id(itemDoc.getId());
// 2.准备Json文档
request.source(doc, XContentType.JSON);
// 3.发送请求
client.index(request, RequestOptions.DEFAULT);
}
1 |
|
GET /{索引库名}/_doc/{id}
1 |
|
@Test
void testGetDocumentById() throws IOException {
// 1.准备Request对象
GetRequest request = new GetRequest(“items”).id(“100002644680”);
// 2.发送请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.获取响应结果中的source
String json = response.getSourceAsString();
ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);
System.out.println("itemDoc= " + ItemDoc);
}
1 |
|
DELETE /{索引库名称}/_doc/{id}
1 |
|
@Test
void testDeleteDocument() throws IOException {
// 1.准备Request,两个参数,第一个是索引库名,第二个是文档id
DeleteRequest request = new DeleteRequest(“item”, “100002644680”);
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
1 |
|
POST /{索引库名}/_update/{id}
{
“doc”: {
“字段名”: “字段值”,
“字段名”: “字段值”
}
}
1 |
|
@Test
void testUpdateDocument() throws IOException {
// 1.准备Request
UpdateRequest request = new UpdateRequest(“items”, “100002644680”);
// 2.准备请求参数
request.doc(
“price”, 58800,
“commentCount”, 1
);
// 3.发送请求
client.update(request, RequestOptions.DEFAULT);
}
1 |
|
@Test
void testBulk() throws IOException {
// 1.创建Request
BulkRequest request = new BulkRequest();
// 2.准备请求参数
request.add(new IndexRequest(“items”).id(“1”).source(“json doc1”, XContentType.JSON));
request.add(new IndexRequest(“items”).id(“2”).source(“json doc2”, XContentType.JSON));
// 3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
1 |
|
@Test
void testLoadItemDocs() throws IOException {
// 分页查询商品数据
int pageNo = 1;
int size = 1000;
while (true) {
Page
// 非空校验
List
if (CollUtils.isEmpty(items)) {
return;
}
log.info(“加载第{}页数据,共{}条”, pageNo, items.size());
// 1.创建Request
BulkRequest request = new BulkRequest(“items”);
// 2.准备参数,添加多个新增的Request
for (Item item : items) {
// 2.1.转换为文档类型ItemDTO
ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
// 2.2.创建新增文档的Request对象
request.add(new IndexRequest()
.id(itemDoc.getId())
.source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON));
}
// 3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
// 翻页
pageNo++;
}
}







GET /items/_count

索引库里有这么多数据
### 5.6.小结
文档操作的基本步骤:
- 初始化`RestHighLevelClient`
- 创建XxxRequest。
- XXX是`Index`、`Get`、`Update`、`Delete`、`Bulk`
- 准备参数(`Index`、`Update`、`Bulk`时需要)
- 发送请求。
- 调用`RestHighLevelClient#.xxx()`方法,xxx是`index`、`get`、`update`、`delete`、`bulk`
- 解析结果(`Get`时需要)
| 操作类型 | Java 类 | 方法调用 | 参数形式 |
| -------- | --------------- | ----------------- | -------------------- |
| 新增 | `IndexRequest` | `client.index()` | JSON 文档 |
| 查询 | `GetRequest` | `client.get()` | 文档 ID |
| 删除 | `DeleteRequest` | `client.delete()` | 文档 ID |
| 修改 | `UpdateRequest` | `client.update()` | `"doc": {字段}` |
| 批量操作 | `BulkRequest` | `client.bulk()` | 多个 Index/Update 等 |
## 基础篇完
