目录
在前面我们已经学习了ES当中各种各样的增删改查的接口,但是这些接口我们之前都是通过Kibana中的DevTools去调用的,在真正业务开发的时候,肯定需要用代码的方式调用这些api的接口,所以我们接下来就要学习一下ES给我们提供的Java客户端。这个Java客户名字叫做JavaRestClient。
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。
由于ES目前最新版本是8.8,提供了全新版本的客户端,不过大多数企业使用的还是8以下版本,而我们采用的是7.12版本,所以我们选择使用早期的JavaRestClient客户端来学习。
一、SpringBoot整合ES
1.1 创建SpringBoot项目
1)New Project
2) 选择Spring项目,填写好项目信息点击下一步
3)选择SpringBoot的web依赖,点击创建
1.2 引入依赖
引入ES的RestHighLevelClient
依赖(依赖的版本号需要和安装的es版本保持一致)
<!-- https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.12.0</version>
</dependency>
1.3 客户端的初始化
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient
的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
初始化的代码如下:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
在config文件夹下创建ES配置类
代码如下,es地址记得换成自己的ip
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ESConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://127.0.0.1:9200")
));
return client;
}
}
测试ES配置是否成功,运行测试方法,打印如下,说明前面的配置没有问题,连接建立成功。
二、索引库操作
Index就类似数据库表,Mapping映射就类似表的结构。我们要向es中存储数据,必须先创建Index和Mapping
对Mapping映射属性不熟悉的可以看前面的章节:
2.1 创建索引库Mapping映射
这里我们创建一个商品索引库,供我们后面学习使用。
我们先把mapping的映射写好,然后以代码的方式通过Java客户端的Api创建索引库
# 名为goods的商品索引库映射
PUT /goods
{
"mappings":{
"properties":{
"id":{
"type":"keyword"
},
"name":{
"type":"text",
"analyzer":"ik_smart",
"search_analyzer":"ik_smart"
},
"price":{
"type":"integer"
},
"image":{
"type":"keyword",
"index":false
},
"category":{
"type":"keyword"
},
"brand":{
"type":"keyword"
},
"sold":{
"type":"integer"
},
"commentCount":{
"type":"integer",
"index":false
},
"isPromotion":{
"type":"boolean"
},
"updateTime":{
"type":"date"
}
}
}
}
2.2 索引库的CRUD
2.2.1 创建索引库
代码分为三步:
1)创建Request对象。
因为是创建索引库的操作,因此Request是CreateIndexRequest
。
2)添加请求参数
其实就是Json格式的Mapping映射参数。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE
,让代码看起来更加优雅。
3)发送请求
client.indices
()
方法的返回值是IndicesClient
类型,封装了所有与索引库操作有关的方法。例如创建索引、删除索引、判断索引是否存在等
在IndexTest类中,具体代码如下:
@SpringBootTest
public class IndexTest {
public static final String MAPPING_TEMPLATE = "{\n" +
" \"mappings\":{\n" +
" \"properties\":{\n" +
" \"id\":{\n" +
" \"type\":\"keyword\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\":\"text\",\n" +
" \"analyzer\":\"ik_smart\"\n" +
" },\n" +
" \"price\":{\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" +
" \"index\":false\n" +
" },\n" +
" \"isPromotion\":{\n" +
" \"type\":\"boolean\"\n" +
" },\n" +
" \"updateTime\":{\n" +
" \"type\":\"date\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
@Resource
private RestHighLevelClient restHighLevelClient;
@Test
public void testCreateIndex() throws IOException {
// 1.准备Request对象,参数为要创建索引库的名称
CreateIndexRequest request = new CreateIndexRequest("goods");
// 2.准备请求参数
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求
restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
}
}
这里需要注意:CreateIndexRequest对象是org.elasticsearch.client.indices.CreateIndexRequest这个包下的,导错包可能会出响create方法过时
接下来我们就来验证一下,可以看到控制台没有异常,说明索引库创建成功了
2.2.2 判断索引库是否存在
流程如下:
1)创建Request对象。这次是GetIndexRequest对象
2)准备参数。这里是无参,直接省略
3)发送请求。改用exists方法
/**
* 查询索引库是否存在
*/
@Test
public void testGetIndex() throws IOException {
// 1.准备Request对象,参数为要查询的索引库名称
GetIndexRequest request = new GetIndexRequest("goods");
// 2.发送请求
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println("exists = " + exists);
}
2.2.3 删除索引库
流程如下:
1)创建Request对象。这次是DeleteIndexRequest对象
2)准备参数。这里是无参,因此省略
3)发送请求。改用delete方法
/**
* 删除索引库
*/
@Test
public void testDeleteIndex() throws IOException {
// 1.准备Request对象,参数为要删除的索引库名称
DeleteIndexRequest request = new DeleteIndexRequest("goods");
// 2.发送请求
restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
}
删除索引库之后,再次查询,索引库已近不存在了
2.2.4 获取和修改索引库
通常我们不需要拿到索引库的完整信息和修改索引库Mapping结构,我这边就不演示了,感兴趣的同学可以自己操作一下。
2.3 总结
JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()
方法来获取索引库的操作对象。
索引库操作的基本步骤:
初始化
RestHighLevelClient
创建XxxIndexRequest。XXX是
Create
、Get
、Delete
准备请求参数(
Create
时需要,其它是无参,可以省略)发送请求。调用
RestHighLevelClient#indices().xxx()
方法,xxx是create
、exists
、delete
三、文档操作
3.1 创建实体类
我们要定义一个索引库结构对应的实体,方便后顺序的操作。
/**
* goods索引库实体类
*/
@Data
public class GoodsDoc{
private String id;
private String name;
private Integer price;
private String image;
private String category;
private String brand;
private Integer sold;
private Integer commentCount;
private Boolean isAD;
private LocalDateTime updateTime;
}
引入Lombok依赖和Hutool工具包,方便后续操作
<!--引入Lombok依赖,自动生成Getter和Setter方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入Hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
3.2 新增文档
步骤如下:
1)创建Request对象,这里是IndexRequest
,因为添加文档就是创建倒排索引的过程
2)准备请求参数
3)发送请求
import cn.hutool.json.JSONUtil;
import com.zhijia.domain.dto.GoodsDoc;
import jakarta.annotation.Resource;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.Date;
@SpringBootTest
public class EsDocumentTest {
@Resource
private RestHighLevelClient restHighLevelClient;
@Test
public void testIndexDocument() throws IOException {
// 1.准备文档数据,这里的数据我就写固定的了,实际开发中要从其他数据库获取
GoodsDoc goodsDoc = new GoodsDoc();
goodsDoc.setId("001");
goodsDoc.setName("华为手机");
goodsDoc.setCategory("手机");
goodsDoc.setBrand("华为");
goodsDoc.setImage("https://www.abc.com");
goodsDoc.setSold(123);
goodsDoc.setPrice(2000);
goodsDoc.setCommentCount(100);
goodsDoc.setIsPromotion(false);
goodsDoc.setUpdateTime(new Date());
// 2.创建Request对象,goods为要新增到的索引库名称,id()指定这条文档的id
IndexRequest request = new IndexRequest("goods").id(goodsDoc.getId());
// 3.准备JSON文档参数
request.source(JSONUtil.toJsonStr(goodsDoc), XContentType.JSON);
// 4.发送新增请求,这里的index()方法就是新增文档的方法
restHighLevelClient.index(request, RequestOptions.DEFAULT);
}
}
运行测试方法,没有任何异常就是创建成功了
3.3 查询文档
与之前的流程类似:
-
创建Request对象
-
准备请求参数,这里是无参,直接省略
-
发送请求
/**
* 查询文档
*/
@Test
public void testGetDocument() throws IOException {
// 1.创建Request对象,第一个参数为索引库名称,第二个参数为文档的id
GetRequest request = new GetRequest("goods","001");
// 2.发送查询请求
GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
// 3.获取响应结果,source里面才是我们要的信息
String sourceAsString = response.getSourceAsString();
// 4.将JSON字符串转成Java实体类
GoodsDoc goodsDoc = JSONUtil.toBean(sourceAsString, GoodsDoc.class);
System.out.println(goodsDoc);
}
运行结果:
3.4 删除文档
与查询相比,仅仅是请求方式从DELETE
变成GET
:
1)准备Request对象,因为是删除,这次是DeleteRequest
对象。要指定索引库名和id
2)准备参数,无参,直接省略
3)发送请求。因为是删除,所以是client.delete()
方法
/**
* 删除文档
*/
@Test
public void testDeleteDocument() throws IOException {
// 1.创建Request对象,第一个参数是索引库名,第二个参数是要删除的文档id
DeleteRequest request = new DeleteRequest("goods","001");
// 2.发送请求
restHighLevelClient.delete(request, RequestOptions.DEFAULT);
}
执行方法之后,再次获取id为001文档,可以发现是空的
3.5 修改文档
修改文档数据有两种方式:
方式一:全量修改。写入id如果存在,就会删除旧文档,然后添加新文档。与新增的JavaAPI一致
方式二:局部修改。只更新指定部分字段。
3.5.1 全量修改
此时还没有id为001的文档,执行index方法,可以看到是新增操作(created)
在id存在的情况下,我将price改为666,再执行一次index方法,可以发现就是updated操作了
再次查询,可以看到id为001的文档,价格被改成666了
3.5.2 局部修改
与之前类似,也是三步走:
1)准备Request
对象。这次是修改,所以是UpdateRequest
2)准备参数。也就是JSON文档,里面包含要修改的字段
3)更新文档。这里调用client.update()
方法
/**
* 修改文档
*/
@Test
public void testUpdateDocument() throws IOException {
// 1.创建Request对象,第一个参数是索引库名,第二个参数是要修改的文档id
UpdateRequest request = new UpdateRequest("goods", "001");
// 2.准备请求参数,每两个参数为一对 key,value
request.doc(
"price",999
);
// 3.更新文档
restHighLevelClient.update(request, RequestOptions.DEFAULT);
}
执行修改之后,可以看到价格被修改为999了
3.6 批处理文档
3.6.1 语法说明
BulkRequest
本身其实并没有请求参数,其本质就是将多个普通的CRUD请求组合在一起发送。例如:
批量新增文档:就是给每个文档创建一个
IndexRequest
请求,然后封装到BulkRequest
中,一起发出。批量删除文档:就是创建N个
DeleteRequest
请求,然后封装到BulkRequest
,一起发出
因此BulkRequest
中提供了add
方法,用以添加其它CRUD的请求:
可以看到,能添加的请求有:
-
IndexRequest
,也就是新增 -
UpdateRequest
,也就是修改 -
DeleteRequest
,也就是删除
3.6.2 批量新增文档
批处理与前面讲的文档的CRUD步骤基本一致:
-
创建Request,但这次用的是
BulkRequest
-
准备请求参数
-
发送请求,这次要用到
client.bulk()
方法
/**
* 批量新增文档
*/
@Test
public void testBulkDocument() throws IOException {
// 1.准备文档数据,这里的数据我就写固定的了,实际开发中要从其他数据库获取
List<GoodsDoc> goodsDocList = new ArrayList<>();
for (int i = 1; i < 3; i++) {
GoodsDoc goodsDoc = new GoodsDoc();
goodsDoc.setId("00"+i);
goodsDoc.setName("华为手机");
goodsDoc.setCategory("手机");
goodsDoc.setBrand("华为");
goodsDoc.setImage("https://www.abc.com");
goodsDoc.setSold(i * 123);
goodsDoc.setPrice(i * 666);
goodsDoc.setCommentCount(i * 100);
goodsDoc.setIsPromotion(false);
goodsDoc.setUpdateTime(new Date());
goodsDocList.add(goodsDoc);
}
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.add(new IndexRequest("goods").id(goodsDocList.get(0).getId()).source(JSONUtil.toJsonStr(goodsDocList.get(0)), XContentType.JSON));
bulkRequest.add(new IndexRequest("goods").id(goodsDocList.get(1).getId()).source(JSONUtil.toJsonStr(goodsDocList.get(1)), XContentType.JSON));
restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
}
3.6.3 批量删除文档
/**
* 批量删除文档
*/
@Test
public void testBulkDeleteDocument() throws IOException {
// 1.准备文档数据,这里的数据我就写固定的了,实际开发中要从其他数据库获取
List<GoodsDoc> goodsDocList = new ArrayList<>();
for (int i = 1; i < 3; i++) {
GoodsDoc goodsDoc = new GoodsDoc();
goodsDoc.setId("00"+i);
goodsDoc.setName("华为手机");
goodsDoc.setCategory("手机");
goodsDoc.setBrand("华为");
goodsDoc.setImage("https://www.abc.com");
goodsDoc.setSold(i * 123);
goodsDoc.setPrice(i * 666);
goodsDoc.setCommentCount(i * 100);
goodsDoc.setIsPromotion(false);
goodsDoc.setUpdateTime(new Date());
goodsDocList.add(goodsDoc);
}
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.add(new DeleteRequest("goods", goodsDocList.get(0).getId()));
bulkRequest.add(new DeleteRequest("goods", goodsDocList.get(1).getId()));
restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
}
3.6.4 批量修改文档
/**
* 批量删除文档
*/
@Test
public void testBulkDeleteDocument() throws IOException {
// 1.准备文档数据,这里的数据我就写固定的了,实际开发中要从其他数据库获取
List<GoodsDoc> goodsDocList = new ArrayList<>();
for (int i = 1; i < 3; i++) {
GoodsDoc goodsDoc = new GoodsDoc();
goodsDoc.setId("00"+i);
goodsDoc.setName("华为手机");
goodsDoc.setCategory("手机");
goodsDoc.setBrand("华为");
goodsDoc.setImage("https://www.abc.com");
goodsDoc.setSold(i * 123);
goodsDoc.setPrice(i * 666);
goodsDoc.setCommentCount(i * 100);
goodsDoc.setIsPromotion(false);
goodsDoc.setUpdateTime(new Date());
goodsDocList.add(goodsDoc);
}
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.add(new UpdateRequest("goods", goodsDocList.get(0).getId()).doc("price",1000));
bulkRequest.add(new UpdateRequest("goods", goodsDocList.get(1).getId()).doc("price",2000));
restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
}
3.7 DSL查询
在之前我们查询文档都是根据id查询,这种查询方式并不能满足业务的场景需求,一般我们的搜索会更复杂,这个时候就需要一种新的查询方式,ElasticSearch就给我们提供了DSL查询来实现这种复杂查询。由于DSL查询内容比较多,将在后续作为一个章节。
3.8 小结
文档操作的基本步骤:
初始化
RestHighLevelClient
创建XxxRequest。
XXX是
Index
、Get
、Update
、Delete
、Bulk
准备参数(
Index
、Update
、Bulk
时需要)发送请求。
调用
RestHighLevelClient#.xxx()
方法,xxx是index
、get
、update
、delete
、bulk
解析结果(
Get
时需要)