Elasticsearch 8.x 分布式搜索引擎 -索引库文档CRUD、文档增删改
一 介绍 Elasticsearch
1、Elasticsearch的作用
elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容
2、ELK技术栈
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。
而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
3、倒排索引
倒排索引的概念是基于MySQL这样的正向索引而言的。
- 正向索引
那么什么是正向索引呢?例如给下表(tb_goods)中的id创建索引:
如果是根据id查询,那么直接走索引,查询速度非常快。
但如果是基于title做模糊查询,只能是逐行扫描数据,流程如下:
1)用户搜索数据,条件是title符合"%手机%"
2)逐行获取数据,比如id为1的数据
3)判断数据中的title是否符合用户搜索条件
4)如果符合则放入结果集,不符合则丢弃。回到步骤1
逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,就是一场灾难。
- 倒排索引
倒排索引中有两个非常重要的概念:
- 文档(
Document
):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息 - 词条(
Term
):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
创建倒排索引是对正向索引的一种特殊处理,流程如下:
- 将每一个文档的数据利用算法分词,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息
- 因为词条唯一性,可以给词条创建索引,例如hash表结构索引
倒排索引的搜索流程如下(以搜索"华为手机"为例):
1)用户输入条件"华为手机"
进行搜索。
2)对用户输入内容分词,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。
4)拿着文档id到正向索引中查找具体文档。
虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
4、正向和倒排
那么为什么一个叫做正向索引,一个叫做倒排索引呢?
-
正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
-
而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
是不是恰好反过来了?
那么两者方式的优缺点是什么呢?
正向索引:
- 优点:
- 可以给多个字段创建索引
- 根据索引字段搜索、排序速度非常快
- 缺点:
- 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
倒排索引:
- 优点:
- 根据词条搜索、模糊搜索时,速度非常快
- 缺点:
- 只能给词条创建索引,而不是字段
- 无法根据字段做排序
5、ES的一些概念
elasticsearch中有很多独有的概念,与mysql中略有差别,但也有相似之处。
1) 、文档和字段
elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:
而Json文档中往往包含很多的字段(Field),类似于数据库中的列。
2)、索引和映射
索引(Index),就是相同类型的文档的集合。
例如:
- 所有用户文档,就可以组织在一起,称为用户的索引;
- 所有商品的文档,可以组织在一起,称为商品的索引;
- 所有订单的文档,可以组织在一起,称为订单的索引;
因此,我们可以把索引当做是数据库中的表。
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。
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实现
- 两者再基于某种方式,实现数据的同步,保证一致性
6、安装 ES、Kibana、分词器
安装就不一一详细说了 百度中有很多
注意 : 三者的版本一定要一样
我这里安装的 8.2.3 下面的例子也使用的8.2.3
7、分词器
分词器到作用
- 创建倒排索引时对文档分词
- 用户搜索时,对输入的内容分词
IK分词器有几种模式?
- ik_smart:智能切分,粗粒度
- ik_max_word:最细切分,细粒度
IK分词器如何拓展词条?如何停用词条?
- 利用config目录的IkAnalyzer.cfg.xml文件添加拓展词典和停用词典
- 在词典中添加拓展词条或者停用词条
二 索引库操作
索引库就类似数据库表,mapping映射就类似表的结构。
我们要向es中存储数据,必须先创建“库”和“表”。
1、mapping映射属性
mapping是对索引库中文档的约束,常见的mapping属性包括:
- type:字段数据类型,常见的简单类型有:
- 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object
- index:是否创建索引,默认为true
- analyzer:使用哪种分词器
- properties:该字段的子字段
例如下面的json文档
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "Java讲师",
"email": "ts@threesides.cn",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "sides",
"lastName": "three"
}
}
对应的每个字段映射(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;无需分词器
2、索引库的CRUD
这里使用Kibana编写DSL的方式来演示。
1)、创建索引库和映射
基本语法:
- 请求方式:PUT
- 请求路径:/索引库名,可以自定义
- 请求参数:mapping映射
格式:
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
示例:
PUT /ts
{
"mappings": {
"properties": {
"info":{
"type": "text",
"analyzer": "ik_smart"
},
"email":{
"type": "keyword",
"index": "false"
},
"name":{
"properties": {
"firstName": {
"type": "keyword"
}
}
}
}
}
}
2)、查询索引库
基本语法:
- 请求方式:GET
- 请求路径:/索引库名
- 请求参数:无
格式:
GET /索引库名
3)、修改索引库
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
语法说明:
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "类型"
}
}
}
示例:
4)、删除索引库
语法:
- 请求方式:DELETE
- 请求路径:/索引库名
- 请求参数:无
格式:
DELETE /索引库名
三 文档操作
1、新增文档
语法:
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
2)、查询文档
根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。
语法:
GET /{索引库名称}/_doc/{id}
3)、删除文档
删除使用DELETE请求,同样,需要根据id进行删除:
语法:
DELETE /{索引库名}/_doc/id值
4)、修改文档
修改有两种方式:
- 全量修改:直接覆盖原来的文档
- 增量修改:修改文档中的部分字段
A、全量修改
全量修改是覆盖原来的文档,其本质是:
- 根据指定的id删除文档
- 新增一个相同id的文档
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
B、增量修改
增量修改是只修改指定id匹配的文档中的部分字段。
语法:
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
四 Elasticsearch Java 客户端
1、创建ES项目
1)、在pom.xml 中引入版本控制依赖
<!-- lombok 工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
2)、编写启动类
@SpringBootApplication
public class ESApplication {
public static void main(String[] args) {
SpringApplication.run(ESApplication.class, args);
}
}
3)、添加配置文件 application.yaml
server:
port: 8082
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring_cloud_study
username: root
password: root1234
application:
name: es-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8868
config:
server-addr: 127.0.0.1:8868
file-extension: yaml
# 支持多个共享dataId的配置,优先级小于extension-configs,shared-configs是一个集合
shared-configs[0]:
# 网关 通用配置可以定义在这个里面
dataId: demo-gateway.yaml # 配置文件名dataId
group: DEFAULT_GROUP # 默认为DEFAULT_GROUP
refresh: true # 是否动态刷新,默认为false
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #mybatis日志输出
map-underscore-to-camel-case: true #Mybatis 驼峰配置
4)、导入数据
项目doc中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;
2、mapping映射分析
1)、创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:
- 字段名
- 字段数据类型
- 是否参与搜索
- 是否需要分词
- 如果分词,分词器是什么?
其中: - 字段名、字段数据类型,可以参考数据表结构的名称和类型
- 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
- 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
- 分词器,我们可以统一使用ik_max_word
2)、酒店数据的索引库结构:
PUT /hotel
{
"mappings": {
"properties": {
"id": {
"type": "long"
},
"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"
}
}
}
}
3)几个特殊字段说明:
- location:地理坐标,里面包含精度、纬度
- all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索
地理坐标说明:
copy_to说明:
3、初始化ES Client
必须先完成这个对象的初始化,建立与elasticsearch的连接。
1)引入ES的RClient依赖:
<!--elasticsearch-->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</dependency>
2)因为SpringBoot默认的ES版本是7.6.2 、默认的jackson版本是2.11.4,所以我们需要覆盖默认的ES版本和jackson版本,并且需要引入org.glassfish.jakarta.json
<elasticsearch-version>8.2.3</elasticsearch-version>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.3</version>
</dependency>
3)初始化ElasticsearchClient:
配置文件 application.yaml中添加ES host、port
elasticsearch:
host: 127.0.0.1
port: 9200
启动类中添加初始化代码
@Value("${elasticsearch.host}")
private String host;
@Value("${elasticsearch.port}")
private Integer port;
@Bean
public ElasticsearchClient getClient() {
RestClient restClient = RestClient.builder(new HttpHost(host,port)).build();
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
4、操作索引库
A、创建索引库
1)、编写 EsClient
类 添加createIndex
方法
@Component
public class EsClient {
@Resource
private ElasticsearchClient elasticsearchClient;
/**
* 创建索引:若索引存在,先删除再创建
*
* @param indexName 索引名称
* @param mappings 映射
*/
public void createIndex(String indexName, Map<String, Property> mappings) {
try {
//创建索引
CreateIndexRequest createIndexRequest = CreateIndexRequest.of(e -> e
.index(indexName)
.mappings(m -> m
.properties(mappings)
)
);
elasticsearchClient.indices().create(createIndexRequest);
} catch (IOException e) {
System.err.println("创建索引失败!");
}
}
}
2)、ESIndexLibraryTest
测试类中,实现创建索引:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ESApplication.class)
public class ESIndexLibraryTest {
@Autowired
private EsClient esClient;
@Test
public void test() {
// keyword类型
Property keywordProperty = Property.of(o -> o.keyword(kBuilder -> kBuilder));
// keyword类型CopyTo
Property keywordPropertyCopyTo = Property.of(o -> o.keyword(kBuilder -> kBuilder.copyTo("all")));
// keyword类型不创建索引
Property keywordPropertyFalseIndex = Property.of(o -> o.keyword(kBuilder -> kBuilder.index(false)));
// text类型分词 搜索分词
Property textProperty = Property.of(o -> o.text(tBuilder -> tBuilder.analyzer("ik_max_word").searchAnalyzer("ik_max_word")));
// text类型分词CopyTo 搜索分词
Property textPropertyCopyTo = Property.of(o -> o.text(tBuilder -> tBuilder.copyTo("all").analyzer("ik_max_word").searchAnalyzer("ik_max_word")));
// integer类型
Property integerProperty = Property.of(o -> o.integer(iBuilder -> iBuilder));
// long类型
Property longProperty = Property.of(o -> o.long_(lBuilder -> lBuilder));
// geoPoint类型
Property geoPointProperty = Property.of(o -> o.geoPoint(lBuilder -> lBuilder));
// date类型
Property dateProperty = Property.of(o -> o.date(dBuilder -> dBuilder.format("yyyy-MM-dd HH:mm:ss")));
Map<String, Property> esDTO = new HashMap<>();
esDTO.put("id", longProperty);
esDTO.put("name", textPropertyCopyTo);
esDTO.put("address", keywordPropertyFalseIndex);
esDTO.put("price", integerProperty);
esDTO.put("rating", integerProperty);
esDTO.put("brand", keywordPropertyCopyTo);
esDTO.put("city", keywordPropertyCopyTo);
esDTO.put("starName", keywordPropertyCopyTo);
esDTO.put("business", keywordProperty);
esDTO.put("location", geoPointProperty);
esDTO.put("pic", keywordPropertyFalseIndex);
esDTO.put("all", textProperty);
esClient.createIndex("hotel", esDTO);
}
}
B、删除索引库
1)、在EsClient
添加deleteIndex
方法
public void deleteIndex(String indexName) {
try {
DeleteIndexRequest deleteIndexRequest = DeleteIndexRequest
.of(e -> e
.index(indexName));
elasticsearchClient.indices().delete(deleteIndexRequest);
} catch (IOException e) {
System.err.println("删除索引失败!");
}
}
2)、在ESIndexLibraryTest
测试类中,实现删除索引:
@Test
public void deleteIndexTest() {
try {
esClient.deleteIndex("hotel");
} catch (Exception e) {
e.printStackTrace();
}
}
5、操作文档
A、新增文档
1)、索引库实体类
数据库查询后的结果是一个Hotel类型的对象。结构如下:
@Data
@TableName("tb_hotel")
public class Hotel implements Serializable {
/**
* 酒店id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 酒店名称
*/
@TableField("name")
private String name;
/**
* 酒店地址
*/
@TableField("address")
private String address;
/**
* 酒店价格
*/
@TableField("price")
private Integer price;
/**
* 酒店评分
*/
@TableField("score")
private Integer score;
/**
* 酒店品牌
*/
@TableField("brand")
private String brand;
/**
* 所在城市
*/
@TableField("city")
private String city;
/**
* 酒店星级,1星到5星,1钻到5钻
*/
@TableField("star_name")
private String starName;
/**
* 商圈
*/
@TableField("business")
private String business;
/**
* 纬度
*/
@TableField("latitude")
private String latitude;
/**
* 经度
*/
@TableField("longitude")
private String longitude;
/**
* 酒店图片
*/
@TableField("pic")
private String pic;
}
与我们的索引库结构存在差异:
- longitude和latitude需要合并为location
因此,我们需要定义一个新的类型,与索引库结构吻合:
@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer rating;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
// 排序时的 距离值
private Object distance;
// 广告标记
private Boolean isAD;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.rating = 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)、在EsClient
添加importDocument
方法
/**
* 导入文档
*
* @param indexName 索引名称
* @param object 数据
* @param <T> T
*/
public <T> void importDocument(String indexName, T object) {
try {
//通过反射获取Id属性
Field[] field = object.getClass().getDeclaredFields();
//设置对象的访问权限,保证对private的属性的访问
field[0].setAccessible(true);
String id = field[0].get(object).toString();
elasticsearchClient.index(i -> i
.index(indexName)
.id(id)
.document(object)
);
} catch (Exception e) {
e.printStackTrace();
}
}
3)、在ESImportDocumentTest
测试类中,实现新增文档:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ESApplication.class)
public class ESImportDocumentTest {
@Autowired
private EsClient esClient;
@Autowired
HotelMapper hotelMapper;
@Test
public void importDocumentTest() {
Hotel hotel = hotelMapper.selectOne(new QueryWrapper<Hotel>().eq("id", 36934));
HotelDoc hotelDoc = new HotelDoc(hotel);
esClient.importDocument("hotel",hotelDoc);
}
}
B、查询文档
1)、在EsClient
添加getDocumentById
方法
public <T> T getDocumentById(String indexName,String id,Class<T> targetClass){
GetResponse<T> response = null;
try {
response = elasticsearchClient.get(g -> g
.index(indexName)
.id(id), targetClass
);
} catch (IOException e) {
e.printStackTrace();
}
return handleResponse(response);
}
private <T> T handleResponse(GetResponse<T> response) {
if (response == null) {
return null;
}
return response.source();
}
2)、在ESQueryDocumentTest
测试类中,实现id查询文档:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ESApplication.class)
public class ESQueryDocumentTest {
@Autowired
private EsClient esClient;
@Test
public void QueryDocumentByIdTest() {
HotelDoc hotel = esClient.getDocumentById("hotel", "36934", HotelDoc.class);
System.out.println("hotel = " + hotel);
}
}
C、删除文档
1)、在EsClient
添加deleteDocument
方法
/**
* 删除文档
*
* @param indexName 索引名
* @param id id
*/
public void deleteDocument(String indexName, String id) {
try {
elasticsearchClient.delete(DeleteRequest
.of(s -> s
.index(indexName)
.id(id)
)
);
} catch (IOException e) {
e.printStackTrace();
}
}
2)、在ESDeleteDocumentTest
测试类中,实现id删除文档:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ESApplication.class)
public class ESDeleteDocumentTest {
@Autowired
private EsClient esClient;
@Test
public void deleteDocument() {
esClient.deleteDocument("hotel","36934");
}
}
D、修改文档
1)、在EsClient
添加updateDocumentById
方法
public <T> void updateDocumentById(String indexName,String id ,Object value,Class<T> targetClass){
try {
elasticsearchClient.update(g -> g
.index(indexName)
.id(id)
.doc(value),targetClass
);
} catch (IOException e) {
e.printStackTrace();
}
}
2)、在ESUpdateDocumentTest
测试类中,实现修改文档:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ESApplication.class)
public class ESUpdateDocumentTest {
@Autowired
private EsClient esClient;
@Test
public void updateDocumentTest() {
Map<String, Object> objectMap = new HashMap<>();
objectMap.put("price","952");
objectMap.put("starName","四钻");
esClient.updateDocumentById("hotel", "36934",objectMap, HotelDoc.class);
}
}
E、批量导入文档
1)、在EsClient
添加batchImportDocumentBuild
方法
/**
* 批量导入文档数据
*
* @param list 数据对象集合
* @param indexName 索引名
* @param <T> 数据对象类型
*/
public <T> void batchImportDocumentBuild(String indexName, List<T> list) {
BulkRequest.Builder br = new BulkRequest.Builder();
try {
for (T object : list) {
//通过反射获取Id属性
Field[] field = object.getClass().getDeclaredFields();
//设置对象的访问权限,保证对private的属性的访问
field[0].setAccessible(true);
String id = field[0].get(object).toString();
br.operations(op -> op
.index(idx -> idx
.index(indexName)
.id(id)
.document(object)
)
);
}
elasticsearchClient.bulk(br.build());
} catch (Exception e) {
e.printStackTrace();
}
}
2)、在ESImportDocumentTest
测试类中,实现批量添加文档:
@Test
public void batchImportDocumentBuildTest() {
List<Hotel> hotelList = hotelMapper.selectList(new QueryWrapper<Hotel>());
List<HotelDoc> hotelDocList = hotelList.stream().map(HotelDoc::new).collect(Collectors.toList());
esClient.batchImportDocumentBuild("hotel",hotelDocList);
}