RestAPI操作es(一)

官方文档地址:

Elasticsearch Clients | Elastichttps://www.elastic.co/guide/en/elasticsearch/client/index.html

RestClient的使用

1)引入esRestHighLevelClient依赖:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

2)因为SpringBoot默认的ES版本是7.17.10,所以我们需要覆盖默认的ES版本:

  <properties>
      <maven.compiler.source>11</maven.compiler.source>
      <maven.compiler.target>11</maven.compiler.target>
      <elasticsearch.version>7.12.1</elasticsearch.version>
  </properties>

3)初始化RestHighLevelClient:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
        HttpHost.create("http://192.168.226.103:9200")
));

为了单元测试方便,我们创建一个测试类IndexTest,然后将初始化的代码编写在@BeforeEach方法中:(写了连接方法,获取连接结果,用完之后关闭)

public class IndexTest {

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.226.103:9200")
        ));
    }

    @Test
    void testConnect() {
        System.out.println(client);
        System.out.println(client.getClass().desiredAssertionStatus());
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

此时说明我们注入成功

创建索引库

示例:

结合数据库表结构,假设字段对应的mapping映射属性如下:

|---|---|-----------|-------------|-----------------|-----------------|---------|
| 字段名 || 字段类型 | 类型说明 | 是否 参与搜索 | 是否 参与分词 | 分词器 |
| id || long | 长整数 | * | * | ------ |
| name || text | 字符串,参与分词搜索 | * | * | IK |
| price || integer | 以分为单位,所以是整数 | * | * | ------ |
| stock || integer | 字符串,但需要分词 | * | * | ------ |
| image || keyword | 字符串,但是不分词 | * | * | ------ |
| category || keyword | 字符串,但是不分词 | * | * | ------ |
| brand || keyword | 字符串,但是不分词 | * | * | ------ |
| sold || integer | 销量,整数 | * | * | ------ |
| commentCount || integer | 评价,整数 | * | * | ------ |
| isAD || boolean | 布尔类型 | * | * | ------ |
| updateTime || Date | 更新时间 | * | * | ------ |

最终我们的索引库文档结构应该是这样:

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)创建Request对象。

因为是创建索引库的操作,因此Request是CreateIndexRequest

2)添加请求参数

其实就是Json格式的Mapping映射参数。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。

3)发送请求

client.``indices``()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。例如创建索引、删除索引、判断索引是否存在等

创建单元测试类testCreateIndex()测试创建索引库方法(由于索引映射比较长我们采用静态字符串的方式引用,并指定他的格式为json)

@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" +
            "}";

可以看到我们创建成功了

我们可以去网页查看

GET /item

可以看到创建成功

删除索引库

编写单元测试testDeleteIndex()方法,由于是删除就不需要准备请求参数

 @Test
    void testDeleteIndex() throws IOException {
        //创建请求
        DeleteIndexRequest request = new DeleteIndexRequest("item");
        //发送请求
        client.indices().delete(request, RequestOptions.DEFAULT);
    }

可以看到删除成功了

我们去网页验证(可以看到404结果)

判断索引库是否存在

之前都是用的网页判断索引库是否创建成功,现在我们用java代码来判断索引库是否存在

创建单元测试testExistsIndex()方法

 @Test
    void testExistsIndex() throws IOException {
        //创建请求
        GetIndexRequest request = new GetIndexRequest("item");
        //发送请求,并接收结果
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        //控制台输出
        System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
    }

由于之前删除了索引库,显示不存在

现在我们执行上面创建索引库的单元测试方法,再次查看是否存在

总结

JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。

索引库操作的基本步骤:

  • 初始化RestHighLevelClient

  • 创建XxxIndexRequest。XXX是CreateGetDelete

  • 准备请求参数( Create时需要,其它是无参,可以省略)

  • 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是createexistsdelete

RestClient操作文档

新增文档

新增文档我们必须先创建实体接收要添加的文档数据

@Data
@ApiModel(description = "索引库实体")
public class ItemDoc{
    @ApiModelProperty("商品id")
    private String id;
    @ApiModelProperty("商品名称")
    private String name;
    @ApiModelProperty("价格(分)")
    private Integer price;
    @ApiModelProperty("库存数量")
    private Integer stock;
    @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;
}

索引库操作的API非常类似,同样是三步走:

  • 1)创建Request对象,这里是IndexRequest,因为添加文档就是创建倒排索引的过程

  • 2)准备请求参数,本例中就是Json文档

  • 3)发送请求

变化的地方在于,这里直接使用client.xxx()的API,不再需要client.indices()了。

创建单元测试方法testAddDocument()

@Test
    void testAddDocument() throws IOException {
        //从数据库中查询数据
        Item item = itemService.getById(317578);
        if (item == null) {
            return;
        }
        //封装成itemdoc对象
        ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
        //创建请求
        IndexRequest request = new IndexRequest("item").id(itemDoc.getId());
        //给请求添加参数
        request.source(JSONUtil.toJsonPrettyStr(itemDoc), XContentType.JSON);
        //发送请求
        client.index(request, RequestOptions.DEFAULT);
    }

查询文档

其它代码与之前类似,流程如下:

1)准备Request对象。这次是查询,所以是GetRequest

2)发送请求,得到结果。因为是查询,这里调用client.get()方法

3)解析结果,就是对JSON做反序列化

编写单元测试testGetDocumentById()方法

    @Test
    void testGetDocumentById() throws IOException {
        //创建请求
        GetRequest request = new GetRequest("item", "317578");
        //发送请求
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        //解析请求
        String json = response.getSourceAsString();
        //转化成itemdoc对象
        ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);
        //输出
        System.out.println(itemDoc);
    }

可以看到查询成功了

删除文档

2步走:

  • 1)准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id

  • 2)准备参数,无参,直接省略

  • 3)发送请求。因为是删除,所以是client.delete()方法

创建单元测试testDeleteDocument()

   @Test
    void testDeleteDocument() throws IOException {
        //创建请求
        DeleteRequest request = new DeleteRequest("item", "317578");
        //发送请求
        client.delete(request, RequestOptions.DEFAULT);
    }

我们再次通过网页查看

GET /item/_doc/317578

可以看到删除成功

修改文档

修改我们讲过两种方式:

  • 全量修改:本质是先根据id删除,再新增

  • 局部修改:修改文档中的指定字段值

在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:

  • 如果新增时,ID已经存在,则修改

  • 如果新增时,ID不存在,则新增

三步走:

  • 1)准备Request对象。这次是修改,所以是UpdateRequest

  • 2)准备参数。也就是JSON文档,里面包含要修改的字段

  • 3)更新文档。这里调用client.update()方法

编写单元测试方法testUpdateDocument()

 @Test
    void testUpdateDocument() throws IOException {
        //创建请求
        UpdateRequest request = new UpdateRequest("item", "317578");
        //添加参数
        request.doc(
                "price", 58800,
                "commentCount", 1
        );
        //发送请求
        client.update(request, RequestOptions.DEFAULT);
    }

通过数据库查看该价格为28900

此时我们执修改操作,修改完后再次通过网页查看(可以看到价格与评论数量都有了更改)

批量导入文档

常见的方案有:

  • 利用Logstash批量导入

    • 需要安装Logstash

    • 对数据的再加工能力较弱

    • 无需编码,但要学习编写Logstash导入配置

  • 利用JavaAPI批量导入

    • 需要编码,但基于JavaAPI,学习成本低

    • 更加灵活,可以任意对数据做再加工处理后写入索引库

批处理与前面讲的文档的CRUD步骤基本一致:

  • 创建Request,但这次用的是BulkRequest

  • 准备请求参数

  • 发送请求,这次要用到client.bulk()方法

编写单元测试方法testLoadItemDocs()

当我们要导入商品数据时,由于商品数量达到数十万,因此不可能一次性全部导入。建议采用循环遍历方式,每次导入1000条左右的数据。

@Test
void testLoadItemDocs() throws IOException {
    // 分页查询商品数据
    int pageNo = 1;
    int size = 1000;
    while (true) {
        Page<Item> page = itemService.lambdaQuery().eq(Item::getStatus, 1).page(new Page<Item>(pageNo, size));
        // 非空校验
        List<Item> items = page.getRecords();
        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++;
    }
}

再次通过网页查看另一个商品id(可以发现添加成功)

小结

文档操作的基本步骤:

  • 初始化RestHighLevelClient

  • 创建XxxRequest。

    • XXX是IndexGetUpdateDeleteBulk
  • 准备参数(IndexUpdateBulk时需要)

  • 发送请求。

    • 调用RestHighLevelClient#.xxx()方法,xxx是indexgetupdatedeletebulk
  • 解析结果(Get时需要)

相关推荐
大数据编程之光27 分钟前
Flink普通API之Source使用全解析
大数据·windows·flink
二进制_博客27 分钟前
Flink学习连载文档第一篇--Flink集群的安装
大数据
青云交2 小时前
大数据新视界 -- Hive 查询性能优化:基于成本模型的奥秘(上)(5/ 30)
大数据·优化器·执行计划·统计信息·hive 查询性能·成本模型·hive 优化
gma9992 小时前
【BUG】ES使用过程中问题解决汇总
大数据·elasticsearch·搜索引擎
黎跃春2 小时前
智能体来了:构建用于具有结构化输出的内容审核的智能 AI Agent 智能体
人工智能·搜索引擎
Mephisto.java2 小时前
【大数据学习 | Spark-Core】RDD的缓存(cache and checkpoint)
大数据·学习·spark
zmd-zk3 小时前
flink学习(3)——方法的使用—对流的处理(map,flatMap,filter)
java·大数据·开发语言·学习·flink·tensorflow
NiNg_1_2343 小时前
Hadoop的MapReduce详解
大数据·hadoop·mapreduce
在下不上天3 小时前
flume-将日志采集到hdfs
大数据·linux·运维·hadoop·hdfs·flume
zmd-zk3 小时前
flink学习(1)——standalone模式的安装
大数据·hadoop·flink·实时