[微服务]elasticsearc客服端操作
客户端初始化
Elasticsearch目前最新版本是8.0,其java客户端有很大变化。不过大多数企业使用的还是8以下版本,所以我们选择使用早期的lavaRestClient客户端来学习。
官方文档地址: Elasticsearch Clients | Elastic
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient
的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
分为三步:
1)在item-service
模块中引入es
的RestHighLevelClient
依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2)因为SpringBoot默认的ES版本是7.17.10
,所以我们需要覆盖默认的ES版本:
<properties>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3)初始化RestHighLevelClient:
初始化的语法如下:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
为了单元测试方便,我们创建一个测试类IndexTest
,然后将初始化的代码编写在@BeforeEach
方法中:
public class ElasticTest {
private RestHighLevelClient client;
@Test
void test() {
System.out.println("client =" + client);
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://...:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
启动测试
商品表Mapping映射
我们要实现商品搜索,那么索引库的字段肯定要满足页面搜索的需求:
# 商品索引库
PUT /hmall
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": ":ik_smart" # 分词器类型
},
"price": {
"type": "integer"
},
"image": {
"type": "keyword",
"index": false # 不需要参与搜索
},
"category": {
"type": "keyword"
},
"brabd": {
"type": "keyword"
},
"sold": {
"type": "integer"
},
"commentCount": {
"type": "integer",
"index": false
}
"isAD": {
"type": "boolean"
},
"updateTime": {
"type": "date"
}
}
}
索引库操作
创建索引库的JavaAPI与Restful接口API对比:
删除索引库
查询索引库
代码示例
public class ElasticIndexTest {
private RestHighLevelClient client;
@Test
void test() {
System.out.println("client =" + client);
}
@Test
void testCreateIndex() throws IOException {
// 1. 准备Request对象
CreateIndexRequest request = new CreateIndexRequest("items");
// 2. 准备请求参数
// request.source(请求体数据, 请求体类型);
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3. 发送请求
// client.indices() 得到索引库操作对象
// client.indices().create(请求参数, 请求可选项)
// RequestOptions.DEFAULT 默认的请求选项
client.indices().create(request, RequestOptions.DEFAULT);
}
@Test
void testGetIndex() throws IOException {
// 1. 准备Request对象
GetIndexRequest request = new GetIndexRequest("items");
// 2. 发送请求
// 查询索引库, 返回索引库信息
// GetIndexResponse res = client.indices().get(request, RequestOptions.DEFAULT);
// 查询索引库是否存在, 返回布尔值
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println("exists = " + exists);
}
@Test
void testDelIndex() throws IOException {
// 1. 准备Request对象
DeleteIndexRequest request = new DeleteIndexRequest("items");
// 2. 发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
private 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" +
" \"brabd\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"sold\": {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"commentCount\": {\n" +
" \"type\": \"integer\",\n" +
" \"index\": false\n" +
" },\n" +
" \"isAD\": {\n" +
" \"type\": \"boolean\"\n" +
" },\n" +
" \"updateTime\": {\n" +
" \"type\": \"date\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}
文档操作
1. 新增文档
- 索引库准备好以后,就可以操作文档了。为了与索引库操作分离,我们创建一个测试类,做两件事情:
- 初始化RestHighLevelClient
- 我们的商品数据在数据库,需要利用IHotelService去查询,所以注入这个接口
@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {
private RestHighLevelClient client;
// 这个接口查询数据要访问数据库
// 所以要通过properties配置环境变量,激活配置文件 此处是local
// 配置文件激活后才能拿到访问数据库的相关数据
@Autowired
private IItemService itemService;
@Test
void test() {
System.out.println("client =" + client);
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
- 商品的索引库结构和数据库结构存在查询, 要定义一个与索引库结构对应的实体
@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;
}
- 新增文档的JavaAPI如下:
@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {
private RestHighLevelClient client;
// 这个接口查询数据要访问数据库
// 所以要通过properties配置环境变量,激活配置文件 此处是local
// 配置文件激活后才能拿到访问数据库的相关数据
@Autowired
private IItemService itemService;
@Test
void test() {
System.out.println("client =" + client);
}
@Test
void testAddDoc() throws IOException {
//0 准备文档数据
//根据id查询数据库数据
Item item = itemService.getById(317578);
//数据库的商品数据转为搜索库的文档数据
ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
//1 准备Request
IndexRequest request = new IndexRequest("items").id(itemDoc.getId());
//2 准备请求参数
request.source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON);
//3 发送请求
client.index(request, RequestOptions.DEFAULT);
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
- 使用jdk17, 需要增加一个测试启动项的vm配置, 不然会因为高版本造成 seata 报错
- 验证文档是否添加成功
2. 查询文档
查询文档包含查询和解析响应结果两部分。对应的IavaAPI如下
public class ElasticDocumentTest {
private RestHighLevelClient client;
@Test
void test() {
System.out.println("client =" + client);
}
@Test
void testGetDoc() throws IOException {
//1 准备Request
GetRequest request = new GetRequest("items", "317578");
//2 发送请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//3 解析结果
String json = response.getSourceAsString();
ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
System.out.println("doc =" + doc);
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
3. 删除文档
删除文档的JavaAPI如下:
public class ElasticDocumentTest {
private RestHighLevelClient client;
@Test
void test() {
System.out.println("client =" + client);
}
@Test
void testDelDoc() throws IOException {
//1 准备Request
DeleteRequest request = new DeleteRequest("items", "317578");
//2 发送请求
client.delete(request, RequestOptions.DEFAULT);
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
4. 修改文档
修改文档数据有两种方式:
方式一: 全量更新。再次写入id一样的文档,就会删除旧文档,添加新文档。与新增的JavaAPI一致。
方式二: 局部更新。只更新指定部分字段,
public class ElasticDocumentTest {
private RestHighLevelClient client;
@Test
void test() {
System.out.println("client =" + client);
}
@Test
void testUpdateDoc() throws IOException {
//1 准备Request
UpdateRequest request = new UpdateRequest("items", "317578");
//2 准备请求参数
request.doc(
"price", 100
);
//3 发送请求
client.update(request, RequestOptions.DEFAULT);
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
5. 批量操作
批处理代码流程与之前类似,只不过构建请求会用到一个名为BulkRequest来封装普通的CRUD请求:
批处理的API示例:
public class ElasticDocumentTest {
private RestHighLevelClient client;
@Test
void test() {
System.out.println("client =" + client);
}
@Test
void testBulkDoc() throws IOException {
//1 准备Request
BulkRequest request = new BulkRequest();
//2 准备请求参数
request.add(new IndexRequest("items").id("1").source("json", XContentType.JSON));
request.add(new DeleteRequest("items").id("1"));
//3 发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}
把数据库的数据批量添加到索引库
@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {
private RestHighLevelClient client;
// 这个接口查询数据要访问数据库
// 所以要通过properties配置环境变量,激活配置文件 此处是local
// 配置文件激活后才能拿到访问数据库的相关数据
@Autowired
private IItemService itemService;
@Test
void test() {
System.out.println("client =" + client);
}
@Test
void testBulkDoc() throws IOException {
int pageNo = 1, pageSize = 500;
while (true) {
//1 准备文档数据
Page<Item> page = itemService.lambdaQuery().eq(Item::getStatus, 1).page(new Page(pageNo, pageSize));
List<Item> records = page.getRecords();
if (records.isEmpty() || records == null) {
return;
}
//2 准备Request
BulkRequest request = new BulkRequest();
//3 准备请求参数
for (Item item : records) {
request.add(new IndexRequest("items")
.id(item.getId().toString())
.source(JSONUtil.toJsonStr(BeanUtil.copyProperties(item, ItemDoc.class)), XContentType.JSON));
}
//4 发送请求
client.bulk(request, RequestOptions.DEFAULT);
//5 翻页
pageNo++;
}
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}