个人笔记-ES的RestHighLevelClient操作

1,491 阅读5分钟

前言

ElasticSearch6.8.4的实战练习记录 《诗词搜索ver 1.0》

适合有一定ES基础的同学,只说HighLevelClient的功能实现。

环境配置:Docker(ElasticSearch6.8.4+Kibana6.8.4+IK分词器)+SpringBoot 2.2.13.RELEASE + MySQL5.7 + MyBatis + Redis(未使用,后续添加热点词汇记录,从而控制扩展词典和禁用词典)

展示

image.png

image.png

image.png

源码

gitee.com/ven1ce/elas…

如何运行?

在application.yml中修改数据库账号密码

image.png 创建es数据库,在其中创建poem表

create table poem
(
    id            bigint auto_increment
        primary key,
    no            varchar(255)             not null,
    title         varchar(100)             not null,
    author        varchar(10)              not null,
    content       varchar(500)             not null,
    type          tinyint     default 0    null comment '诗歌类型 四言绝句 五言绝句',
    author_detail varchar(500)             not null,
    source        varchar(50) default '网络' not null comment '来源',
    create_time   datetime                 null,
    update_time   datetime                 null,
    constraint no
        unique (no)
)
    comment '诗' charset = utf8mb4;

配置elasticSearch地址

image.png

配置索引

PUT /es
{
  "mappings": {
    "poem":{
      "properties":{
        "id":{
          "type":"keyword"
        },
        "no":{
          "type":"keyword"
        },
        "title":{
          "type":"text",
          "analyzer":"ik_max_word"
        },
        "author":{
          "type":"text",
          "analyzer":"ik_max_word"
        },
        "content":{
          "type":"text",
          "analyzer":"ik_max_word"
        },
        "type":{
          "type":"integer"
        },
        "authorDetail":{
          "type":"text",
          "analyzer":"ik_max_word"
        },
        "source":{
          "type":"keyword"
        },
        "createTime":{
          "type":"date",
          "format":"yyyy-MM-dd HH:mm:ss"
        },
        "updateTime":{
          "type":"date",
          "format":"yyyy-MM-dd HH:mm:ss"
        }
      }
    }
  }
}

启动前端,npm run serve image.png

image.png

RestHighLevelClient操作

学习ElasticSearch的时候,ElasticSearch升级挺快的,在网上也搜索不到什么好文章,学RestHighLevelClient的操作一头雾水。好在官网文档还算详尽,果然还得是官方文档最好了,虽然不够综合,但是慢慢磨也能下来。所以

推荐直接看官方英文文档。

推荐直接看官方英文文档。

推荐直接看官方英文文档。

重要的事情说三遍,不管你是什么版本6.x还是7.x,不要在网上找其他文章(只能当作综合方法的参考)!

怕有同学找不到,我就直接帮大家贴在下面,注意版本一致。

ElasticSearch官方Rest Client文档(英文)

ElasticSearch官方文档(英文)

生成所有数据库中的数据的ES文档

这个功能第一反应是ES的批处理方法,我们来看ES的官方文档。

image.png

当然我们也可以在批处理请求中添加其他请求

image.png

官网中还有其他可配置信息以及如何同步(异步)请求,根据自己需要翻阅吧。

这里使用同步请求,首先获取数据库中的全部数据(用作案例,勿喷),然后根据数据库数据生成新的索引请求并加入批处理中去。随后用highLevelClient发起请求,获取到批处理的返回数据bulkResponse,并进行处理。

代码:

    /**
     * 生成所有数据库中数据的es文档
     */
    @Override
    public void insertEsAllDate() throws IOException {
        List<Poem> poems = poemMapper.queryAll();
        BulkRequest bulkRequest = new BulkRequest();
        for (Poem poem : poems) {
            String s = JSON.toJSONString(poem);
            bulkRequest.add(new IndexRequest("es","poem",poem.getNo()).source(s, XContentType.JSON));
        }
        BulkResponse bulkResponse = highLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
   
   if (bulkResponse.hasFailures()){
            for (BulkItemResponse item : bulkResponse.getItems()) {
                System.out.println(item.getFailureMessage());
            }
        }
    }

清空ES中的所有文档

查看文档我们可以发现有一个delete-by-query操作。从字面意思上来说就是根据查询到的删除。那么如果我们要实现清空文档的操作,那么只需要查询所有,再根据查询删除即可。

image.png

那么我们如何获得查询所有文档,查看官方的搜索文档就可以发现已经给出演示。

image.png

这里和文档略有差异。道理一样。

代码:

    /**
     * 清空es中`es`索引的表
     */
    @Override
    public void clearEsAllDate() throws IOException {
        DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest("es");
        deleteByQueryRequest.setQuery(new MatchAllQueryBuilder());
        deleteByQueryRequest.setConflicts("proceed");//版本冲突不中止进程
        highLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
    }

分页获取文档

直接查看搜索文档

image.png

可以看到设置sourceBuilder的form和size属性就是分页查询,再对query设置查下全部即可。我们还可以设置超时时间。

设置好sourceBuilder后,我们就可以把它传给搜索请求了。接下来处理返回值即可。

image.png

代码:

    /**
     * 获得所有es文档
     */
    @Override
    public List<Poem> getAllEs() throws IOException {
        List<Poem> result = new ArrayList<>();
        SearchRequest searchRequest = new SearchRequest("es");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.matchAllQuery());
        sourceBuilder.from(0);
        sourceBuilder.size(10);
        searchRequest.source(sourceBuilder);
        SearchResponse search = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits searchHits = search.getHits();
        SearchHit[] hits = searchHits.getHits();
        if ( hits != null && hits.length != 0 ){
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                Poem poem = JSON.parseObject(sourceAsString, Poem.class);
                result.add(poem);
            }
        }
        return result;
    }

高亮搜索

查询官方文档中的Highlighting请求

image.png

我们首先设置SearchSourceBuilder和HighlightBuilder。

设置HighlightBuilder中的filed为高亮的字段,preTags和postTags则是我们的高亮字段的html。

利用boolQuery设置多个字段搜索。其中配置好优先级boost(越大优先级越高)以及分词器。

 //设置搜索字段
        sourceBuilder.query(QueryBuilders.boolQuery()
                .should(QueryBuilders.multiMatchQuery(searchString,"title").boost(4).analyzer("ik_smart"))
                .should(QueryBuilders.multiMatchQuery(searchString,"author").boost(3).analyzer("ik_smart"))
                .should(QueryBuilders.multiMatchQuery(searchString,"content").boost(2).analyzer("ik_smart"))
                .should(QueryBuilders.multiMatchQuery(searchString,"authorDetail").boost(1).analyzer("ik_smart"))
        );

在获得的结果中我们可以单独获取到,高亮的字段。因为高亮的字段String和实体内属性是区分开的。这里需要思考如何把带上高亮标签的String放入实体属性内。那么因为属性是不断变化的,我们不能写死程序,这里想到用反射获取实体类的set方法。利用set方法注入携带高亮标签的属性。

            //获得高亮字段 设置实体
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                //s为实体字段名
                for (String s : highlightFields.keySet()) {
                    HighlightField highlightField = highlightFields.get(s);
                    Text[] fragments = highlightField.getFragments();
                    //获取高亮字段的属性
                    StringBuilder sb = new StringBuilder();
                    for (Text fragment : fragments) {
                        sb.append(fragment.toString());
                    }

                    // 因为不知道实际的是哪个属性  所以使用反射获取到set方法
                    try{
                        Class<? extends Poem> aClass = poem.getClass();
                        //把字段转换为驼峰格式 与set方法命名规范一致 首字母大写
                        String formatString = StringUtils.convertToCamelCase(s);
                        //通过方法名获取方法
                        Method declaredMethod = aClass.getDeclaredMethod("set" + formatString,String.class);
                       //执行方法,把高亮标签注入
                        declaredMethod.invoke(poem,sb.toString());
                    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                        e.printStackTrace();
                    }


                }

代码:

    /**
     * 高亮搜索
     *
     * @param searchString 搜索的字符串
     * @return 结果
     */
    @Override
    public List<Poem> search(String searchString) throws IOException {
        //todo 加入redis热词记录

        //先搜索es
        SearchRequest searchRequest = new SearchRequest("es");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.field("author");
        highlightBuilder.field("content");
        highlightBuilder.field("authorDetail");
        highlightBuilder.preTags("<em style=\"color:red\" >");
        highlightBuilder.postTags("</em>");
        sourceBuilder.highlighter(highlightBuilder);

        //设置搜索字段
        sourceBuilder.query(QueryBuilders.boolQuery()
                .should(QueryBuilders.multiMatchQuery(searchString,"title").boost(4).analyzer("ik_smart"))
                .should(QueryBuilders.multiMatchQuery(searchString,"author").boost(3).analyzer("ik_smart"))
                .should(QueryBuilders.multiMatchQuery(searchString,"content").boost(2).analyzer("ik_smart"))
                .should(QueryBuilders.multiMatchQuery(searchString,"authorDetail").boost(1).analyzer("ik_smart"))
        );
        searchRequest.source(sourceBuilder);
        //获得结果
        SearchResponse search = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits searchHits = search.getHits();
        SearchHit[] hits = searchHits.getHits();
        List<Poem> result = new ArrayList<>();
        if (hits != null && hits.length != 0){
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                //转为实体类
                Poem poem = JSON.parseObject(sourceAsString, Poem.class);

                //获得高亮字段 设置实体
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                for (String s : highlightFields.keySet()) {
                    HighlightField highlightField = highlightFields.get(s);
                    Text[] fragments = highlightField.getFragments();
                    StringBuilder sb = new StringBuilder();
                    for (Text fragment : fragments) {
                        sb.append(fragment.toString());
                    }

                    // 因为不知道实际的是哪个属性  所以使用反射获取到set方法
                    try{
                        Class<? extends Poem> aClass = poem.getClass();
                        //转换为驼峰格式 首字母大写
                        String formatString = StringUtils.convertToCamelCase(s);
                        Method declaredMethod = aClass.getDeclaredMethod("set" + formatString,String.class);
                        declaredMethod.invoke(poem,sb.toString());
                    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                        e.printStackTrace();
                    }


                }
                result.add(poem);
            }
        }

        //todo 如果没有击中则搜索数据库
        //todo 搜索到了之后 加入es
        return result;
    }

总结

只是简单记录一下学习es中的综合操作,大部分也很简单。目前也还有很多目标功能没有实现,最近比较忙,暂时先放下来做其他事情了。有空再回来试试吧。

但是虽然上面说过了,这里还是要说

推荐直接看官方英文文档!

推荐直接看官方英文文档!!

推荐直接看官方英文文档!!!