当前位置:   article > 正文

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

elasticsearch-rest-high-level-client

点击关注公众号,实用技术文章及时了解a0cba72dc623f05073c6ae27effc850b.png

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

a57f5305076a2a839f1452491106de0a.png

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

5d27031510d3c1ce1440c94156d76dd3.png

ES操作记录

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

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

  1. <dependency>
  2.     <groupId>org.elasticsearch.client</groupId>
  3.     <artifactId>elasticsearch-rest-high-level-client</artifactId>
  4.     <version>5.6.11</version>
  5. </dependency>
  6. <dependency>
  7.     <groupId>org.elasticsearch</groupId>
  8.     <artifactId>elasticsearch</artifactId>
  9.     <version>5.6.11</version>
  10. </dependency>

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

  1. @EsDeclare(index = "user_search")
  2. public class UserSearchRecordPO {
  3.     @Id
  4.     private long id;
  5.     private String username;
  6.     private String searchKeyWord;
  7.     public long getId() {
  8.         return id;
  9.     }
  10.     public void setId(long id) {
  11.         this.id = id;
  12.     }
  13.     public String getUsername() {
  14.         return username;
  15.     }
  16.     public void setUsername(String username) {
  17.         this.username = username;
  18.     }
  19.     public String getSearchKeyWord() {
  20.         return searchKeyWord;
  21.     }
  22.     public void setSearchKeyWord(String searchKeyWord) {
  23.         this.searchKeyWord = searchKeyWord;
  24.     }
  25. }

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

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface EsDeclare {
  4.     String index() default StringUtils.EMPTY;
  5. }

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

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

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

  1. /**
  2.  * 获取topic和type
  3.  *
  4.  * @param clz
  5.  * @return
  6.  */
  7. private Pair<String/* topic */, String/* type */> getTopicAndType(Class<?> clz) {
  8.     //通过反射去获取注解中的index值
  9.     EsDeclare esDeclare = clz.getAnnotation(EsDeclare.class);
  10.     if (null == esDeclare || StringUtils.isEmpty(esDeclare.index())) {
  11.         logger.warn("getTopicAndType , esDeclare is illegal , class:{}", clz);
  12.         return null;
  13.     }
  14.     return Pair.of(esDeclare.index(), clz.getSimpleName());
  15. }

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

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

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

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

三者的关系如下图所示:

d1e072ade0a255643833469dfbd66d1d.png

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

  1. package org.idea.es.project.template.api.service;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.parser.Feature;
  4. import com.google.common.collect.Lists;
  5. import org.apache.commons.collections.CollectionUtils;
  6. import org.apache.commons.lang3.StringUtils;
  7. import org.apache.commons.lang3.reflect.FieldUtils;
  8. import org.apache.commons.lang3.tuple.Pair;
  9. import org.elasticsearch.action.delete.DeleteRequest;
  10. import org.elasticsearch.action.index.IndexRequest;
  11. import org.elasticsearch.action.index.IndexResponse;
  12. import org.elasticsearch.action.search.SearchRequest;
  13. import org.elasticsearch.action.search.SearchResponse;
  14. import org.elasticsearch.client.RestHighLevelClient;
  15. import org.elasticsearch.common.xcontent.XContentType;
  16. import org.elasticsearch.search.SearchHit;
  17. import org.elasticsearch.search.builder.SearchSourceBuilder;
  18. import org.idea.es.project.template.api.config.EsDeclare;
  19. import org.slf4j.Logger;
  20. import org.slf4j.LoggerFactory;
  21. import org.springframework.beans.factory.annotation.Autowired;
  22. import org.springframework.data.annotation.Id;
  23. import org.springframework.stereotype.Repository;
  24. import javax.naming.directory.SearchResult;
  25. import java.io.IOException;
  26. import java.lang.reflect.Field;
  27. import java.util.Collections;
  28. import java.util.List;
  29. @Repository
  30. public class EsDao<T> {
  31.     private final Logger logger = LoggerFactory.getLogger(getClass());
  32.     @Autowired
  33.     private RestHighLevelClient restHighLevelClient;
  34.     /**
  35.      * 条件查询
  36.      *
  37.      * @return
  38.      */
  39.     public List<SearchResult> searchByCondition(String index, String type) {
  40.         try {
  41.             SearchRequest searchRequest = new SearchRequest(index).types(type);
  42.             SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  43.             sourceBuilder.from(1).size(2);
  44.             searchRequest.source(sourceBuilder);
  45.             SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
  46.             System.out.println(searchResponse);
  47.         } catch (IOException e) {
  48.             e.printStackTrace();
  49.         }
  50.         return null;
  51.     }
  52.     public T queryOne(SearchSourceBuilder sourceBuilder, Class<T> clz) {
  53.         List<T> resultList = this.queryList(sourceBuilder, clz);
  54.         if (CollectionUtils.isNotEmpty(resultList)) {
  55.             return resultList.get(0);
  56.         }
  57.         return null;
  58.     }
  59.     /**
  60.      * 查询
  61.      *
  62.      * @param sourceBuilder
  63.      * @param clz
  64.      * @return
  65.      */
  66.     public List<T> queryList(SearchSourceBuilder sourceBuilder, Class<T> clz) {
  67.         Pair<String, String> topicAndType = getTopicAndType(clz);
  68.         if (null == topicAndType) {
  69.             logger.warn("query , null topicAndType , clz:{}", clz);
  70.             return Collections.emptyList();
  71.         }
  72.         Field idField = getIdField(clz);
  73.         if (null == idField) {
  74.             logger.warn("query , null id field , clz:{}", clz);
  75.             return Collections.emptyList();
  76.         }
  77.         try {
  78.             SearchRequest searchRequest = new SearchRequest(topicAndType.getLeft()).types(topicAndType.getRight()).source(sourceBuilder);
  79.             SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
  80.             SearchHit[] hits = searchResponse.getHits().getHits();
  81.             List<T> result = Lists.newArrayListWithCapacity(hits.length);
  82.             for (SearchHit hit : hits) {
  83.                 T obj = JSON.parseObject(hit.getSourceAsString(), clz, Feature.AllowISO8601DateFormat);
  84.                 Object idObj = FieldUtils.readField(idField, obj, true);
  85.                 if (null == idObj) {
  86.                     FieldUtils.writeField(idField, obj, hit.getId(), true);
  87.                 }
  88.                 result.add(obj);
  89.             }
  90.             return result;
  91.         } catch (Exception e) {
  92.             logger.warn("query , e:{}", e.getMessage());
  93.             return Collections.emptyList();
  94.         }
  95.     }
  96.     /**
  97.      * 插入或者更新,根据id字段来判断是否已有数据
  98.      *
  99.      * @param po
  100.      */
  101.     public void saveOrUpdate(T po) {
  102.         if (po == null) {
  103.             throw new IllegalArgumentException("po can not be null!");
  104.         }
  105.         try {
  106.             Pair<String/*topic*/, String /*type*/> pair = getTopicAndType(po.getClass());
  107.             Field idField = getIdField(po.getClass());
  108.             idField.setAccessible(true);
  109.             Object idObj = idField.get(po);
  110.             IndexRequest indexRequest = new IndexRequest(pair.getLeft(), pair.getRight(), idObj == null ? null : idObj.toString());
  111.             IndexResponse indexResponse = restHighLevelClient.index(indexRequest.source(JSON.toJSONStringWithDateFormat(po, "yyyy-MM-dd'T'HH:mm:ss+08:00"), XContentType.JSON));
  112.             System.out.println(indexResponse);
  113.         } catch (Exception e) {
  114.             e.printStackTrace();
  115.         }
  116.     }
  117.     /**
  118.      * 删除单个元素
  119.      *
  120.      * @param po
  121.      */
  122.     public void deleteOne(T po) {
  123.         if (po == null) {
  124.             throw new IllegalArgumentException("po can not be null!");
  125.         }
  126.         try {
  127.             Pair<String/*index*/, String /*type*/> pair = getTopicAndType(po.getClass());
  128.             Field idField = getIdField(po.getClass());
  129.             idField.setAccessible(true);
  130.             Object idObj = idField.get(po);
  131.             DeleteRequest deleteRequest = new DeleteRequest(pair.getLeft(), pair.getRight(), idObj == null ? null : idObj.toString());
  132.             restHighLevelClient.delete(deleteRequest);
  133.         } catch (IllegalAccessException | IOException e) {
  134.             e.printStackTrace();
  135.         }
  136.     }
  137.     /**
  138.      * 根据id删除
  139.      *
  140.      * @param index
  141.      * @param type
  142.      * @param _id
  143.      */
  144.     public void deleteBy_Id(String index,String type,String _id){
  145.         DeleteRequest deleteRequest = new DeleteRequest(index, type, _id);
  146.         try {
  147.             restHighLevelClient.delete(deleteRequest);
  148.         } catch (IOException e) {
  149.             e.printStackTrace();
  150.         }
  151.     }
  152.     /**
  153.      * 获取id的域
  154.      *
  155.      * @param clz
  156.      * @return
  157.      */
  158.     public Field getIdField(Class<?> clz) {
  159.         List<Field> listWithAnnotation = FieldUtils.getFieldsListWithAnnotation(clz, Id.class);
  160.         if (listWithAnnotation.size() != 1) {
  161.             logger.warn("getIdField , id is illeage , class:{}", clz);
  162.             return null;
  163.         }
  164.         return listWithAnnotation.get(0);
  165.     }
  166.     /**
  167.      * 获取topic和type
  168.      *
  169.      * @param clz
  170.      * @return
  171.      */
  172.     private Pair<String/* topic */, String/* type */> getTopicAndType(Class<?> clz) {
  173.         EsDeclare esDeclare = clz.getAnnotation(EsDeclare.class);
  174.         if (null == esDeclare || StringUtils.isEmpty(esDeclare.index())) {
  175.             logger.warn("getTopicAndType , esDeclare is illegal , class:{}", clz);
  176.             return null;
  177.         }
  178.         return Pair.of(esDeclare.index(), clz.getSimpleName());
  179.     }
  180. }

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

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

首先是接口部分的定义:

  1. package org.idea.es.project.template.api.service;
  2. import org.idea.es.project.template.api.bo.UserSearchRecordPO;
  3. import javax.naming.directory.SearchResult;
  4. import java.util.List;
  5. public interface IUserSearchRecordService {
  6.     /**
  7.      * 条件查询
  8.      *
  9.      * @param index
  10.      * @param type
  11.      * @return
  12.      */
  13.     List<SearchResult> searchByCondition(String index,String type);
  14.     /**
  15.      * 查询操作
  16.      *
  17.      * @param userSearchRecordPO
  18.      * @return
  19.      */
  20.     UserSearchRecordPO queryByParam(UserSearchRecordPO userSearchRecordPO);
  21.     /**
  22.      * 写入记录
  23.      *
  24.      * @return
  25.      */
  26.     UserSearchRecordPO saveOrUpdate();
  27.     /**
  28.      * 删除单个元素
  29.      */
  30.     void deleteOne(UserSearchRecordPO userSearchRecordPO);
  31. }

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

  1. package org.idea.es.project.template.api.service.impl;
  2. import org.elasticsearch.index.query.BoolQueryBuilder;
  3. import org.elasticsearch.index.query.QueryBuilders;
  4. import org.elasticsearch.search.builder.SearchSourceBuilder;
  5. import org.idea.es.project.template.api.bo.UserSearchRecordPO;
  6. import org.idea.es.project.template.api.service.EsDao;
  7. import org.idea.es.project.template.api.service.IUserSearchRecordService;
  8. import org.springframework.stereotype.Service;
  9. import javax.annotation.Resource;
  10. import javax.naming.directory.SearchResult;
  11. import java.util.List;
  12. @Service
  13. public class UserSearchRecordServiceImpl implements IUserSearchRecordService {
  14.     @Resource
  15.     private EsDao<UserSearchRecordPO> esDao;
  16.     @Override
  17.     public List<SearchResult> searchByCondition(String index,String type) {
  18.         return esDao.searchByCondition(index,type);
  19.     }
  20.     @Override
  21.     public UserSearchRecordPO queryByParam(UserSearchRecordPO userSearchRecordPO) {
  22.         try {
  23.             BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
  24.                     .filter(QueryBuilders.termQuery("id", userSearchRecordPO.getId()));
  25.             SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource().query(queryBuilder).from(0).size(1);
  26.             return esDao.queryOne(sourceBuilder, UserSearchRecordPO.class);
  27.         } catch (Exception e) {
  28.             e.printStackTrace();
  29.         }
  30.         return null;
  31.     }
  32.     @Override
  33.     public UserSearchRecordPO saveOrUpdate() {
  34.         UserSearchRecordPO userSearchRecordPO = new UserSearchRecordPO();
  35.         userSearchRecordPO.setId(System.currentTimeMillis());
  36.         userSearchRecordPO.setUsername("idea");
  37.         userSearchRecordPO.setSearchKeyWord("key-word");
  38.         esDao.saveOrUpdate(userSearchRecordPO);
  39.         return userSearchRecordPO;
  40.     }
  41.     @Override
  42.     public void deleteOne(UserSearchRecordPO userSearchRecordPO) {
  43.         esDao.deleteOne(userSearchRecordPO);
  44.     }
  45. }

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

  1. package org.idea.es.project.template.api.controller;
  2. import org.idea.es.project.template.api.bo.UserSearchRecordPO;
  3. import org.idea.es.project.template.api.service.IUserSearchRecordService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. @RestController
  9. @RequestMapping(value = "/user-search-record")
  10. public class UserSearchRecordController {
  11.     @Autowired
  12.     private IUserSearchRecordService iUserSearchRecordService;
  13.     @GetMapping(value = "/save-or-update")
  14.     public boolean saveOrUpdate(){
  15.         iUserSearchRecordService.saveOrUpdate();
  16.         System.out.println("success");
  17.         return true;
  18.     }
  19.     @GetMapping(value = "/query-by-param")
  20.     public UserSearchRecordPO queryByParam(Long id){
  21.         UserSearchRecordPO userSearchRecordPO = new UserSearchRecordPO();
  22.         userSearchRecordPO.setId(id);
  23.         return iUserSearchRecordService.queryByParam(userSearchRecordPO);
  24.     }
  25.     @GetMapping(value = "/delete-one")
  26.     public boolean deleteOne(long id){
  27.         UserSearchRecordPO userSearchRecordPO = new UserSearchRecordPO();
  28.         userSearchRecordPO.setId(id);
  29.         iUserSearchRecordService.deleteOne(userSearchRecordPO);
  30.         System.out.println("success");
  31.         return true;
  32.     }
  33. }

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

好了。

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

ca2d9b60cd3d58cbe42a6d21484b4db1.png

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

推荐

Java面试题宝典

技术内卷群,一起来学习!!

d5c5beb49a4e12fcf3f5632e99d35d05.png

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/68656
推荐阅读
相关标签
  

闽ICP备14008679号