Elasticsearch客户端

389 阅读6分钟

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

客户端介绍

在elasticsearch官网中提供了各种语言的客户端:www.elastic.co/guide/en/el…

注意点击进入后,选择版本到 6.2.4 ,因为我们之前按照的都是 6.2.4 版本:

image.png

创建Demo工程

初始化项目

image.png

pom文件

注意,这里我们直接导入了SpringBoot的启动器,方便后续讲解。不过还需要手动引入elasticsearch的High-level-Rest-Client的依赖:

<properties>
    <java.version>11</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.8.1</version>
    </dependency>
    <!--Apache开源组织提供的用于操作JAVA BEAN的工具包-->
    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>1.9.1</version>
    </dependency>
    <!--ES高级Rest Client-->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>6.4.3</version>
    </dependency>
</dependencies>

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

配置文件

我们在resource下创建application.yml

image.png

索引库及映射

image.png

创建索引库的同时,我们也会创建type及其映射关系,但是这些操作不建议使用java客户端完成,原因如下:

  • 索引库和映射往往是初始化时完成,不需要频繁操作,不如提前配置好
  • 官方提供的创建索引库及映射API非常繁琐,需要通过字符串拼接json结构:

image.png

因此,这些操作建议还是使用我们昨天学习的Rest风格API去实现。

我们接下来以这样一个商品数据为例来创建索引库:

package com.lagou.es.pojo;

public class Product {
    private Long id;
    private String title; //标题
    private String category;// 分类
    private String brand; // 品牌
    private Double price; // 价格
    private String images; // 图片地址

}

分析一下数据结构:

  • id:可以认为是主键,将来判断数据是否重复的标示,不分词,可以使用keyword类型
  • title:搜索字段,需要分词,可以用text类型
  • category:商品分类,这个是整体,不分词,可以使用keyword类型
  • brand:品牌,与分类类似,不分词,可以使用keyword类型
  • price:价格,这个是double类型
  • images:图片,用来展示的字段,不搜索,index为false,不分词,可以使用keyword类型

我们可以编写这样的映射配置:

PUT /lagou
{
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 1
    },
    "mappings": {
        "item": {
            "properties": {
                "id": {
                    "type": "keyword"
                },
                "title": {
                    "type": "text",
                    "analyzer": "ik_max_word"
                },
                "category": {
                    "type": "keyword"
                },
                "brand": {
                    "type": "keyword"
                },
                "images": {
                    "type": "keyword",
                    "index": false
                },
                "price": {
                    "type": "double"
                }
            }
        }
    }
}

索引数据操作

有了索引库,我们接下来看看如何新增索引数据

操作MYSQL数据库:

  1. 获取数据库连接
  2. 完成数据的增删改查
  3. 释放资源

初始化客户端

完成任何操作都需要通过HighLevelRestClient客户端,看下如何创建。

我们先编写一个测试类:

image.png

然后再@Before的方法中编写client初始化:

public class ElasticSearchTest {

    private RestHighLevelClient client;
    // Json工具
    private Gson gson = new Gson();

    @Before
    public void init(){
        // 初始化HighLevel客户端
        client = new RestHighLevelClient(
                RestClient.builder(
                    new HttpHost("127.0.0.1", 9201, "http"),
                    new HttpHost("127.0.0.1", 9202, "http"),
                    new HttpHost("127.0.0.1", 9203, "http")
                )
        );

    }

    @After
    public void close() throws IOException {
        // 关闭客户端
        client.close();
    }

}

新增文档

@Test
public void testInsert() throws IOException {
    //1.文档数据
    Product product = new Product();
    product.setBrand("华为");
    product.setCategory("手机");
    product.setId(1L);
    product.setImages("http://image.huawei.com/1.jpg");
    product.setPrice(5999.99);
    product.setTitle("华为P50就是棒");
    //2.将文档数据转换为json格式
    String source = gson.toJson(product);
    //3.创建索引请求对象 访问哪个索引库、哪个type、指定文档ID
    //public IndexRequest(String index, String type, String id)
    IndexRequest request = new IndexRequest("lagou","item",product.getId().toString());
    request.source(source, XContentType.JSON);
    //4.发出请求
    IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
    System.out.println(response);
}

查看文档

根据rest风格,查看应该是根据id进行get查询,难点是对结果的解析:

@Test
public void testFindIndex() throws IOException {
    // 创建get请求,并指定id
    GetRequest request = new GetRequest("lagou", "item", "1");

    // 查询,得到响应
    GetResponse response = client.get(request, RequestOptions.DEFAULT);

    // 解析响应,应该是json
    String source = response.getSourceAsString();
    // 转换json数据
    Product item = gson.fromJson(source, Product.class);
    System.out.println(item);
}

修改文档

新增时,如果传递的id是已经存在的,则会完成修改操作,如果不存在,则是新增。

删除文档

根据id删除:

@Test
public void testDeleteIndex() throws IOException {
    // 准备删除的请求,参数为id
    DeleteRequest request = new DeleteRequest("lagou", "item", "1");
    // 发起请求
    DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
    System.out.println("response = " + response);
}

搜索数据

查询所有match_all

@Test
public void testMatchAll() throws IOException {
    // 创建搜索对象
    SearchRequest request = new SearchRequest();
    // 查询构建工具
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 添加查询条件,通过QueryBuilders获取各种查询
    sourceBuilder.query(QueryBuilders.matchAllQuery());

    request.source(sourceBuilder);

    // 搜索
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    // 解析
    SearchHits hits = response.getHits();
    SearchHit[] searchHits = hits.getHits();
    for (SearchHit hit : searchHits) {
        // 取出source数据
        String json = hit.getSourceAsString();
        // 反序列化
        Product item = gson.fromJson(json, Item.class);
        System.out.println("item = " + item);
    }
}

注意,上面的代码中,搜索条件是通过 sourceBuilder.query(QueryBuilders.matchAllQuery())来添加的。这个 query() 方法接受的参数是: QueryBuilder 接口类型。

这个接口提供了很多实现类,分别对应我们在之前中学习的不同类型的查询,例如:term查询、match查询、range查询、boolean查询等

因此,我们如果要使用各种不同查询,其实仅仅是传递给 sourceBuilder.query() 方法的参数不同而已。而这些实现类不需要我们去 new ,官方提供了 QueryBuilders 工厂帮我们构建各种实现类

关键字搜索match

其实搜索类型的变化,仅仅是利用QueryBuilders构建的查询对象不同而已,其他代码基本一致:

// 添加查询条件,通过QueryBuilders获取各种查询
sourceBuilder.query(QueryBuilders.matchQuery("title","手机"));

因此,我们可以把这段代码封装,然后把查询条件作为参数传递:

private void basicQuery(SearchSourceBuilder sourceBuilder) throws IOException {
    // 创建搜索对象
    SearchRequest request = new SearchRequest();

    request.source(sourceBuilder);
    // 搜索
    SearchResponse response = client.search(request,RequestOptions.DEFAULT);

    // 解析
    SearchHits hits = response.getHits();
    SearchHit[] searchHits = hits.getHits();
    for (SearchHit hit : searchHits) {
        // 取出source数据
        String json = hit.getSourceAsString();
        // 反序列化
        Product item = gson.fromJson(json, Item.class);
        System.out.println("item = " + item);
    }
}

调用封装的方法,并传递查询条件:

@Test
public void testMatchQuery() throws IOException {
    // 查询构建工具
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 添加查询条件,通过QueryBuilders获取各种查询
    sourceBuilder.query(QueryBuilders.matchQuery("title", "手机"));

    basicQuery(sourceBuilder);
}

范围查询range

RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price");

与页面上一样,支持下面的范围关键字:

方法说明
gt(Object from)大于
gte(Object from)大于等于
lt(Object from)小于
lte(Object from)小于等于

示例:

@Test
public void testRangeQuery() throws IOException {
    // 查询构建工具
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 添加查询条件,通过QueryBuilders获取各种查询
    sourceBuilder.query(QueryBuilders.rangeQuery("price").gt(1000).lt(4000));

    basicQuery(sourceBuilder);
}

source过滤

_source:存储原始文档

默认情况下,索引库中所有数据都会返回,如果我们想只返回部分字段,可以通过source fifilter来控制。

@Test
public void testSourceFilter() throws IOException {
    // 创建搜索对象
    SearchRequest request = new SearchRequest();
    // 查询构建工具
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 添加查询条件,通过QueryBuilders获取各种查询
    sourceBuilder.query(QueryBuilders.matchAllQuery());

    // 添加source过滤
    sourceBuilder.fetchSource(new String[]{"id", "title", "price"}, null);

    basicQuery(sourceBuilder);
}

排序

依然是通过sourceBuilder来配置:

@Test
public void testSortQuery() throws IOException {
    // 创建搜索对象
    SearchRequest request = new SearchRequest();
    // 查询构建工具
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 添加查询条件,通过QueryBuilders获取各种查询
    sourceBuilder.query(QueryBuilders.matchAllQuery());

    // 添加排序
    sourceBuilder.sort("price", SortOrder.ASC);

    basicQuery(sourceBuilder);
}

分页

分页需要视图层传递两个参数给我们:

  • 当前页:page
  • 每页大小:size

而elasticsearch中需要的不是当前页,而是起始位置,还好有公式可以计算出:

from-->起始位置,0表示第一条

  • 起始位置:start = (page - 1) * size
  • 第一页:(1-1)*5 = 0
  • 第二页:(2-1)*5 = 5

代码:

@Test
public void testSortAndPageQuery() throws IOException {
    // 创建搜索对象
    SearchRequest request = new SearchRequest();
    // 查询构建工具
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 添加查询条件,通过QueryBuilders获取各种查询
    sourceBuilder.query(QueryBuilders.matchAllQuery());

    // 添加排序
    sourceBuilder.sort("price", SortOrder.ASC);
    // 添加分页
    int page = 1;
    int size = 3;
    int start = (page - 1) * size;
    // 配置分页
    sourceBuilder.from(start);
    sourceBuilder.size(3);

    basicQuery(sourceBuilder);
}