RestClient操作索引库和文档

一、什么是RestClient?

前面我们学习了通过DSL语句操作ES索引库,文档数据,要想通过java代码操作,必须使用ES官方提供的RestClient实现了,ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。

官方文档地址:https://www.elastic.co/guide/en/elastic search/client/index.html,如下:

客户端说明查看:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.15/index.html

Java REST 客户端有两种风格:

  • Java Low Level REST Client:官方 Elasticsearch 的低级客户端。它允许与 Elasticsearch cluster通过http。留下请求编组和响应 取消编组给用户。它与所有Elasticsearch版本兼容。
  • Java High Level REST Client:Elasticsearch 的官方高级客户端。 基于低级客户端,公开 API 特定方法并注意 的请求编组和响应取消编组。

本次我们使用Java High Level REST Client进行操作索引库和文档

二、环境准备

2.1.创建酒店信息表

准备创建数据酒店信息表:tb_hotel

DROP TABLE IF EXISTS `tb_hotel`;
CREATE TABLE `tb_hotel`  (
  `id` bigint(20) NOT NULL COMMENT '酒店id',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店名称',
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店地址',
  `price` int(10) NOT NULL COMMENT '酒店价格',
  `score` int(2) NOT NULL COMMENT '酒店评分',
  `brand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '酒店品牌',
  `city` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所在城市',
  `star_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店星级,1星到5星,1钻到5钻',
  `business` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商圈',
  `latitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '纬度',
  `longitude` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '经度',
  `pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '酒店图片',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;

导入数据如下:

2.2.创建springboot工程

2.2.1.创建项目

设置项目名称为hotel_demo,

选择spring

2.2.2.导入依赖

创建springboot项目,导入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.12</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.augus</groupId>
    <artifactId>hotel-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hotel-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <elasticsearch.version>7.17.5</elasticsearch.version>
    </properties>
    <dependencies>

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

        <!--FastJson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.71</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!--添加druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!--mybatis-plus在springboot中启动器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

        <!--mysql数据库连接相关依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>

        <!--spring-boot启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--springboot测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.2.3.编写配置文件

在application.yml中进行配置如下:

server:
  port: 8081
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5
      min-idle: 5
      maxActive: 20
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      maxPoolPreparedStatementPerConnectionSize: 20
      filters: stat,wall,slf4j
      connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      web-stat-filter:
        enabled: true
        url-pattern: "/*"
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      stat-view-servlet:
        url-pattern: "/druid/*"
        allow: 127.0.0.1,192.168.8.109
        deny: 192.168.1.188
        reset-enable: false
        login-username: admin
        login-password: 123456

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  type-aliases-package: com.augus.pojo

2.2.3.创建mapper层

在创建com.augus.pojo包下创建tb_hotel表对应的实体类,创建:Hotel.java,代码如下:

package com.augus.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_hotel")
public class Hotel {
    @TableId(type = IdType.INPUT)
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String longitude;
    private String latitude;
    private String pic;
}

由于最后位置是由经纬度实现,但是表中经纬度是两个字段,所以创建一个HotelDoc.java实体类,其余字段不变,新创建一个location字段,将经纬度组合成字符串赋值给location,如下:

package com.augus.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = 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.2.4.创建mapper层

在创建com.augus.mapper包,创建:HotelMapper.java,代码如下:

@Mapper
public interface HotelMapper extends BaseMapper<Hotel> {
}

2.2.5.创建service层

在创建com.augus.service包,创建:IHotelService 接口代码如下:

package service;

import com.augus.pojo.Hotel;
import com.baomidou.mybatisplus.extension.service.IService;

public interface IHotelService extends IService<Hotel> {
}

然后在service创建子包impl,在里面创建实现类HotelServiceImpl,代码如下:

package service.impl;

import com.augus.mapper.HotelMapper;
import com.augus.pojo.Hotel;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import service.IHotelService;

@Service
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {}

三、删除索引库、利用Java Rest Client实现创建、判断索引库是否存在

需求实现:根据之前的酒店表中数据创建索引库,索引库名为hotel,mapping属性根据数据库结构定义。

基本步骤如下:

  1. 在数据库中创建表,导入数据,创建springboot项目
  2. 分析数据结构,定义mapping属性
  3. 初始化Java Rest Client
  4. 利用Java Rest Client创建索引库
  5. 利用Java Rest Client删除索引库
  6. 利用Java Rest Client判断索引库是否存在

3.1.步骤二:分析数据结构

mapping要考虑的问题:字段名、数据类型、是否参与搜索、字段是否分词、如果分词,分词器使用什么?,表结构如下:

注意,latitude和longitude分别是经纬度字段,在elasticsearch,地理位置ES中支持两种地理坐标数据类型:

  • geo_point:由纬度(latitude)和经度(longitude)确定的一个点。例如:"30.91161,121.45661"
  • geo_shape:有多个geo_point组成的复杂几何图形。例如一条直线,"LINE STRING(-77.0365338.897676,-77.00905138.889939)

在dev_tools编写索引库如下:

# 酒店的mapping
PUT /hotel
{
  "mappings": {
    "properties": {
      "id":{
        "type":"keyword"
      },
      "name":{
        "type":"text",
        "analyzer": "ik_max_word"
      },
      "addres":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type":"integer"
      },
      "score":{
        "type":"integer"
      },
      "brand":{
        "type":"keyword"
      },
      "city":{
        "type":"keyword"
      },
      "starName":{
        "type":"keyword"
      },
      "business":{
        "type":"keyword"
      },
      "location":{
        "type":"geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      }
    }
  }
}

注意:

  • id字段在数据库表中类型是bigint,但是在elasticsearch中需要设置为keyword类型,通常,id字段用于唯一标识文档。使用keyword类型可以确保id的值被视为一个完整的、不可分割的字符串,而不是一个可被解析为数字的整数。这样可以避免在处理id时发生意外的类型转换或截断。
  • 上面index属性默认为true,表示会被索引,设置为false时,表示该字段不会被索引。这意味着该字段的内容不会被存储在倒排索引中,因此无法通过该字段进行搜索、聚合或过滤操作。

单个字段搜索效率高还是要比多个字段搜索效率高,显然是一个字段效率高,但是实际需求是用户输入名称、品牌、商圈能搜到,而且多个字段搜索还要性能好怎么解决?

可以通过字段拷贝可以使用copy_to属性将当前字段拷贝到指定字段。示例:

"all":{
"type":"text","analyzer":"ik_max_word"
}
"brand":{
"type":"keyword",
"copy_to":"all"
}

并不是把文档拷贝进去,而只是基于它创建倒排索引,将来查的时候看不到这个字段,好像不存在一样,但是搜索却能根据它进行搜索,

3.2.步骤三:RestClient操作索引库-初始化RestClient

3.2.1.引入es的RestHighLevelClient依赖:

导入依赖的版本需要个elasticsearch的版本相同:

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

3.2.2.因为Spring Boot2.6.12默认的ES版本是7.15.2,所以我们需要覆盖默认的ES版本:

在上面导入依赖后查询maven中信息版本:

在pom.xml中指定依赖如下:

    <properties>
        <java.version>8</java.version>
        <elasticsearch.version>7.17.5</elasticsearch.version>
    </properties>

再次查看,版本已经切换过来了,如下:

3.2.3.初始化RestHighLevelClient:

语法如下:

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

在测试类HotelDemoApplicationTests.java中创建

  • @BeforeEach:单元测试里面的注解,提前完成对象的初始化
  • @AfterEach:销毁注解

案例代码如下:

package com.augus;

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 org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

@SpringBootTest
class HotelDemoApplicationTests {
    //创建成员变量,保存初始化的对象
    private RestHighLevelClient client;


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

    @AfterEach
    void testDown() throws IOException {
        this.client.close();
    }
    @Test
    public void testClient() {
        //测试创建连接是否成功
        System.out.println(client);
    }
}

以后写的所有的单元测试,都会先运行BeforeEach 最后运行@AfterEach,执行上面的testClien测试方法执行如下:

3.3.步骤四:RestClient操作索引库-创建索引库

先创建包constants然后创建HotelConstants类,在里面创建MAPPING_TEMPLATE静态属性,保存创建索引库DSL语言如下:

package com.augus.constants;


public class HotelConstants {
    public 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" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"addres\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\":\"integer\"\n" +
            "      },\n" +
            "      \"score\":{\n" +
            "        \"type\":\"integer\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\":\"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\":{\n" +
            "        \"type\":\"keyword\"\n" +
            "      },\n" +
            "      \"starName\":{\n" +
            "        \"type\":\"keyword\"\n" +
            "      },\n" +
            "      \"business\":{\n" +
            "        \"type\":\"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"location\":{\n" +
            "        \"type\":\"geo_point\"\n" +
            "      },\n" +
            "      \"pic\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"all\":{\n" +
            "        \"type\": \"text\"\n" +
            "        , \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

然后再之前的测试类中新建测试方法,用于创建索引库,代码如下:

    @Test
    void testCreateHotelIndex() throws IOException {
        //1.创建request对象
        CreateIndexRequest request = new CreateIndexRequest("hotel");

        //2.请求参数
        request.source(MAPPING_TEMPLATE, XContentType.JSON);

        //3.发起请求
        //client.indices() 返回的对象中包含索引库操作的所有方法
        client.indices().create(request, RequestOptions.DEFAULT);
    }

执行后,然后进行查询操作如下:

3.4.步骤五:删除索引库、判断索引库是否存在

  • 删除索引库代码如下:
    @Test
    void testDeleteHotel() throws IOException {
        //1.创建request对象
        DeleteIndexRequest request = new DeleteIndexRequest("hotel");
        
        //2.发送请求
        client.indices().delete(request, RequestOptions.DEFAULT);
    }
  • 判断索引库是否存在
    @Test
    void testExistsHotel() throws IOException {
        //1.创建对象
        GetIndexRequest request = new GetIndexRequest("hotel");

        //2.发起请求
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);

        //3.输出
        System.out.println(exists);
    }
}

先执行删除索引库操作,然后查询,执行结果如下:

四、Rest Client操作文档

利用Java RestClient实现文档的CRUD,去数据库查询酒店数据,导入到hotel索引库,实现酒店数据的CRUD。基本步骤如下:

  • 初始化Java RestClient(和之前一致)
  • 利用Java RestClient新增酒店数据
  • 利用Java RestClient根据id查询酒店数据
  • 利用Java RestClient删除酒店数据
  • 利用Java RestClient修改酒店数据

4.1.RestClien新建文档

创建测试类:ElasticsearchDocumentTest.java,完成初始化代码如下:

@SpringBootTest
public class ElasticsearchDocumentTest {
    //创建成员变量,保存初始化的对象
    private RestHighLevelClient client;


    @BeforeEach
    void setUp(){
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://192.168.42.150:9200")
                ));
    }
    @AfterEach
    void testDown() throws IOException {
        this.client.close();
    }
}

新建文档,代码如下:

    @Test
    void testAddDocument() throws IOException {
        //根据id从数据库查询酒店数据
        Hotel hotel = iHotelService.getById(36934L);
        //将查询出来hotel对象转换为HotelDoc
        HotelDoc hotelDoc = new HotelDoc(hotel);

        //1.准备Request对象
        IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());

        //2.准备json文档
        request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);

        //3.发送请求
        client.index(request, RequestOptions.DEFAULT);

    }
}

注意:

  • 需要把查询出来的对象序列化成json的风格,利用 FastJson里面的API  JSON利用方法.toJSONString(),可以帮助我们把对象序列化 

4.2.RestClien查询文档

步骤3:根据id查询酒店数据

需要注意,根据id查询到的文档数据是json,需要反序列化为java对,代码如下:

    @Test
    void testSelectById() throws IOException {
        //1.创建request对象
        GetRequest request = new GetRequest("hotel","36934");

        //2.发送请求,得到结果
        GetResponse response = client.get(request, RequestOptions.DEFAULT);

        //3.获取结果
        String sourceAsString = response.getSourceAsString();

        //4.反序列化成HotelDoc对象
        HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
        System.out.println(hotelDoc);
    }
}

执行后结果如下:

4.3.RestClien更新文档

步骤4:根据id修改酒店数据,修改文档数据有两种方式:

方式一:全量更新。再次写入id一样的文档(使用之前的新增即可),就会删除旧文档,添加新文档 
方式二:局部更新。只更新部分字段,所以下面演示方式二

    @Test
    void testUpdateDocument() throws IOException {
        //1.创建request
        UpdateRequest request = new UpdateRequest("hotel", "36934");

        //2.准备请求参数
        request.doc("price","399",
                "brand","7天连锁酒店");

        //3.发送请求
        client.update(request, RequestOptions.DEFAULT);
    }

执行后查看文档内容,发现price和brand字段的值已经被修改,查看如下:

4.4.RestClien删除文档

步骤5:根据id删除文档数据,代码如下:

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

执行后在查看文档id为36934的记录已经不存在了,如下:

 

4.5.RestClient批量导入文档

需求:批量查询酒店数据,然后批量导入索引库中,思路:

  1. 利用mybatis-plus查询酒店数据
  2. 将查询到的酒店数据(Hotel)转换为文档类型数据(Hotel Doc)
  3. 利用Java RestClient中的Bulk批处理,实现批量新增文档

案例代码如下:

    @Test
    void testBulkRequest() throws IOException {
        //1.批量查询酒店数据
        List<Hotel> hotels = iHotelService.list();

        //2.创建request
        BulkRequest request = new BulkRequest();

        //3.准备参数
        for (Hotel hotel : hotels) {
            //转换成文件类型 HotelDoc
            HotelDoc hotelDoc = new HotelDoc(hotel);
            //创建新增文档的request
            request.add(
                    new IndexRequest("hotel")
                            .id(hotelDoc.getId().toString())
                            .source(JSON.toJSONString(hotelDoc), XContentType.JSON)
            );
        }

        //4.发送请求
        client.bulk(request,RequestOptions.DEFAULT);
    }

代码执行后,查看所有数据信息,

posted @ 2023-10-26 15:04  酒剑仙*  阅读(147)  评论(0)    收藏  举报