ElasticSearch 使用 High Level REST Client 实现搜索等功能实战

Posted 程序员大咖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ElasticSearch 使用 High Level REST Client 实现搜索等功能实战相关的知识,希望对你有一定的参考价值。

👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇

作者丨idea

来源丨Java知音(ID:Java_friends)

ES 全称 Elasticsearch 是一款分布式的全文搜索引擎,在互联网公司中,这款搜索引擎一直被程序员们所推崇。常见的使用场景如ELK日志分析,电商APP的商品推荐,社交APP的同城用户推荐等等。

在ES的官网文档中,目前主要提供了两种方式访问,一种叫做Low Client,一种叫做High Level Rest Client。在今天这篇文章中,我们主要介绍High Level Rest Client的使用方式和一些经验分享。

ES操作记录

那么我们该如何去通过High Level Rest Client的方式来使用es呢?来看接下来的这块实战案例。

首先我们需要合理的es配置依赖,下边这份是对应的pom文件配置:

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

在配置中指定了es依赖之后,我们开始定义一个用于测试es增删查改操作的对象类UserSearchRecordPO。

@EsDeclare(index = "user_search")
public class UserSearchRecordPO 
    @Id
    private long id;
    private String username;
    private String searchKeyWord;
    public long getId() 
        return id;
    
    public void setId(long id) 
        this.id = id;
    
    public String getUsername() 
        return username;
    
    public void setUsername(String username) 
        this.username = username;
    
    public String getSearchKeyWord() 
        return searchKeyWord;
    
    public void setSearchKeyWord(String searchKeyWord) 
        this.searchKeyWord = searchKeyWord;
    

在UserSearchRecordPO这个对象的头部我用了一个自定义的注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EsDeclare 


    String index() default StringUtils.EMPTY;

这个注解用于声明对象所映射的文档具体名称。

奇怪,为什么我们要声明这个注解呢?嘿嘿,别着急,在下边的这个EsDao中就有使用到这个注解的影子了。

在ESDao中,我的整体设计思路是,通过反射获取一个Bean对象是否携带有@EsDeclare注解,如果有,就从注解中提取对应的topic。这部分的核心逻辑如下所示:

/**
 * 获取topic和type
 *
 * @param clz
 * @return
 */
private Pair<String/* topic */, String/* type */> getTopicAndType(Class<?> clz) 
    //通过反射去获取注解中的index值
    EsDeclare esDeclare = clz.getAnnotation(EsDeclare.class);
    if (null == esDeclare || StringUtils.isEmpty(esDeclare.index())) 
        logger.warn("getTopicAndType , esDeclare is illegal , class:", clz);
        return null;
    
    return Pair.of(esDeclare.index(), clz.getSimpleName());

这里有几个概念需要和大家简单梳理下,关于index,type,document三个概念的含义:

  • index可以类比为mysql中的表这个概念,他是一类型数据存储的集合。

  • document其实就是index这个集合里面单条数据的一种称呼,这个概念和MySQL中的行记录比较类似。

  • type是这个代表document属于index中的哪个类别(type),一个index通常会划分为多个type,逻辑上对index中有些许不同的几类数据进行分类:因为一批相同的数据,可能有很多相同的fields,但是还是可能会有一些轻微的不同,可能会有少数fields是不一样的,举个例子,就比如说,商品,可能划分为电子商品,生鲜商品,日化商品,等等。

三者的关系如下图所示:

好了,现在让我们再来看看基于ES进行CRUD该如何执行操作,具体代码见下边这个类:

package org.idea.es.project.template.api.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.idea.es.project.template.api.config.EsDeclare;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.stereotype.Repository;
import javax.naming.directory.SearchResult;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
@Repository
public class EsDao<T> 
    private final Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private RestHighLevelClient restHighLevelClient;


    /**
     * 条件查询
     *
     * @return
     */
    public List<SearchResult> searchByCondition(String index, String type) 
        try 
            SearchRequest searchRequest = new SearchRequest(index).types(type);
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.from(1).size(2);
            searchRequest.source(sourceBuilder);
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
            System.out.println(searchResponse);
         catch (IOException e) 
            e.printStackTrace();
        
        return null;
    


    public T queryOne(SearchSourceBuilder sourceBuilder, Class<T> clz) 
        List<T> resultList = this.queryList(sourceBuilder, clz);
        if (CollectionUtils.isNotEmpty(resultList)) 
            return resultList.get(0);
        
        return null;
    
    /**
     * 查询
     *
     * @param sourceBuilder
     * @param clz
     * @return
     */
    public List<T> queryList(SearchSourceBuilder sourceBuilder, Class<T> clz) 
        Pair<String, String> topicAndType = getTopicAndType(clz);
        if (null == topicAndType) 
            logger.warn("query , null topicAndType , clz:", clz);
            return Collections.emptyList();
        
        Field idField = getIdField(clz);
        if (null == idField) 
            logger.warn("query , null id field , clz:", clz);
            return Collections.emptyList();
        
        try 
            SearchRequest searchRequest = new SearchRequest(topicAndType.getLeft()).types(topicAndType.getRight()).source(sourceBuilder);
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
            SearchHit[] hits = searchResponse.getHits().getHits();
            List<T> result = Lists.newArrayListWithCapacity(hits.length);
            for (SearchHit hit : hits) 
                T obj = JSON.parseObject(hit.getSourceAsString(), clz, Feature.AllowISO8601DateFormat);
                Object idObj = FieldUtils.readField(idField, obj, true);
                if (null == idObj) 
                    FieldUtils.writeField(idField, obj, hit.getId(), true);
                
                result.add(obj);
            
            return result;
         catch (Exception e) 
            logger.warn("query , e:", e.getMessage());
            return Collections.emptyList();
        
    
    /**
     * 插入或者更新,根据id字段来判断是否已有数据
     *
     * @param po
     */
    public void saveOrUpdate(T po) 
        if (po == null) 
            throw new IllegalArgumentException("po can not be null!");
        
        try 
            Pair<String/*topic*/, String /*type*/> pair = getTopicAndType(po.getClass());
            Field idField = getIdField(po.getClass());
            idField.setAccessible(true);
            Object idObj = idField.get(po);
            IndexRequest indexRequest = new IndexRequest(pair.getLeft(), pair.getRight(), idObj == null ? null : idObj.toString());
            IndexResponse indexResponse = restHighLevelClient.index(indexRequest.source(JSON.toJSONStringWithDateFormat(po, "yyyy-MM-dd'T'HH:mm:ss+08:00"), XContentType.JSON));
            System.out.println(indexResponse);
         catch (Exception e) 
            e.printStackTrace();
        
    
    /**
     * 删除单个元素
     *
     * @param po
     */
    public void deleteOne(T po) 
        if (po == null) 
            throw new IllegalArgumentException("po can not be null!");
        
        try 
            Pair<String/*index*/, String /*type*/> pair = getTopicAndType(po.getClass());
            Field idField = getIdField(po.getClass());
            idField.setAccessible(true);
            Object idObj = idField.get(po);
            DeleteRequest deleteRequest = new DeleteRequest(pair.getLeft(), pair.getRight(), idObj == null ? null : idObj.toString());
            restHighLevelClient.delete(deleteRequest);
         catch (IllegalAccessException | IOException e) 
            e.printStackTrace();
        
    
    /**
     * 根据id删除
     *
     * @param index
     * @param type
     * @param _id
     */
    public void deleteBy_Id(String index,String type,String _id)
        DeleteRequest deleteRequest = new DeleteRequest(index, type, _id);
        try 
            restHighLevelClient.delete(deleteRequest);
         catch (IOException e) 
            e.printStackTrace();
        
    


    /**
     * 获取id的域
     *
     * @param clz
     * @return
     */
    public Field getIdField(Class<?> clz) 
        List<Field> listWithAnnotation = FieldUtils.getFieldsListWithAnnotation(clz, Id.class);
        if (listWithAnnotation.size() != 1) 
            logger.warn("getIdField , id is illeage , class:", clz);
            return null;
        
        return listWithAnnotation.get(0);
    
    /**
     * 获取topic和type
     *
     * @param clz
     * @return
     */
    private Pair<String/* topic */, String/* type */> getTopicAndType(Class<?> clz) 
        EsDeclare esDeclare = clz.getAnnotation(EsDeclare.class);
        if (null == esDeclare || StringUtils.isEmpty(esDeclare.index())) 
            logger.warn("getTopicAndType , esDeclare is illegal , class:", clz);
            return null;
        
        return Pair.of(esDeclare.index(), clz.getSimpleName());
    

这里需要注意下saveOrUpdate函数中,它会根据传入的对象参数中带有 @Id 注解的字段值去判断是否已经有具体数据,如果有的话则只做更新操作,反之就是插入操作。这一点就有点类似于MySQL的insertOrUpdate方法。

接下来就是对于我们所定义的对象实现crud操作了,下边是对应的service接口和相关的实现类,这部分的代码如下所示:

首先是接口部分的定义:

package org.idea.es.project.template.api.service;


import org.idea.es.project.template.api.bo.UserSearchRecordPO;


import javax.naming.directory.SearchResult;
import java.util.List;


public interface IUserSearchRecordService 


    /**
     * 条件查询
     *
     * @param index
     * @param type
     * @return
     */
    List<SearchResult> searchByCondition(String index,String type);


    /**
     * 查询操作
     *
     * @param userSearchRecordPO
     * @return
     */
    UserSearchRecordPO queryByParam(UserSearchRecordPO userSearchRecordPO);


    /**
     * 写入记录
     *
     * @return
     */
    UserSearchRecordPO saveOrUpdate();


    /**
     * 删除单个元素
     */
    void deleteOne(UserSearchRecordPO userSearchRecordPO);




接着是对应的service实现类部分:

package org.idea.es.project.template.api.service.impl;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.idea.es.project.template.api.bo.UserSearchRecordPO;
import org.idea.es.project.template.api.service.EsDao;
import org.idea.es.project.template.api.service.IUserSearchRecordService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.naming.directory.SearchResult;
import java.util.List;
@Service
public class UserSearchRecordServiceImpl implements IUserSearchRecordService 
    @Resource
    private EsDao<UserSearchRecordPO> esDao;
    @Override
    public List<SearchResult> searchByCondition(String index,String type) 
        return esDao.searchByCondition(index,type);
    
    @Override
    public UserSearchRecordPO queryByParam(UserSearchRecordPO userSearchRecordPO) 
        try 
            BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
                    .filter(QueryBuilders.termQuery("id", userSearchRecordPO.getId()));
            SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource().query(queryBuilder).from(0).size(1);
            return esDao.queryOne(sourceBuilder, UserSearchRecordPO.class);
         catch (Exception e) 
            e.printStackTrace();
        
        return null;
    
    @Override
    public UserSearchRecordPO saveOrUpdate() 
        UserSearchRecordPO userSearchRecordPO = new UserSearchRecordPO();
        userSearchRecordPO.setId(System.currentTimeMillis());
        userSearchRecordPO.setUsername("idea");
        userSearchRecordPO.setSearchKeyWord("key-word");
        esDao.saveOrUpdate(userSearchRecordPO);
        return userSearchRecordPO;
    
    @Override
    public void deleteOne(UserSearchRecordPO userSearchRecordPO) 
        esDao.deleteOne(userSearchRecordPO);
    

最后是供外界调用的controller方法:

package org.idea.es.project.template.api.controller;
import org.idea.es.project.template.api.bo.UserSearchRecordPO;
import org.idea.es.project.template.api.service.IUserSearchRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/user-search-record")
public class UserSearchRecordController 
    @Autowired
    private IUserSearchRecordService iUserSearchRecordService;


    @GetMapping(value = "/save-or-update")
    public boolean saveOrUpdate()
        iUserSearchRecordService.saveOrUpdate();
        System.out.println("success");
        return true;
    


    @GetMapping(value = "/query-by-param")
    public UserSearchRecordPO queryByParam(Long id)
        UserSearchRecordPO userSearchRecordPO = new UserSearchRecordPO();
        userSearchRecordPO.setId(id);
        return iUserSearchRecordService.queryByParam(userSearchRecordPO);
    


    @GetMapping(value = "/delete-one")
    public boolean deleteOne(long id)
        UserSearchRecordPO userSearchRecordPO = new UserSearchRecordPO();
        userSearchRecordPO.setId(id);
        iUserSearchRecordService.deleteOne(userSearchRecordPO);
        System.out.println("success");
        return true;
    

将SpringBoot启动之后,分别触发这些http请求接口,就可以验证crud操作的正确性了。

好了。

另外,在测试es的时候,我们可以通过使用 elasticsearch-head 这款插件去查看es内部的数据是否符合我们的预期。

整体来说,通过 elasticsearch-rest-high-level-client 去访问es还是比较容易上手的。另外在实际业务场景中,如果遇到一些非常复杂的条件查询功能的话,自Elasticsearch 5.x之后,我们其实还可以通过使用painless脚本去操作es,可以看出es的功能在变得越来越强大了。

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击👆卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

以上是关于ElasticSearch 使用 High Level REST Client 实现搜索等功能实战的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch java api操作(Java High Level Rest Client)

ElasticSearch 使用 High Level REST Client 实现搜索等功能实战

如何将整数值分配给字符串并在 SQL 中找到总和

Springboot2.1.1使用rest-high-level-client整合Elasticsearch6.4.3

Elasticsearch High Level Rest Client 发起请求的过程分析

Elasticsearch:High Disk Watermark