600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > ElasticSearch 使用 High Level REST Client 实现搜索等功能实战

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

时间:2021-07-22 20:26:44

相关推荐

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

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

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")publicclassUserSearchRecordPO{@Idprivatelongid;privateStringusername;privateStringsearchKeyWord;publiclonggetId(){returnid;}publicvoidsetId(longid){this.id=id;}publicStringgetUsername(){returnusername;}publicvoidsetUsername(Stringusername){this.username=username;}publicStringgetSearchKeyWord(){returnsearchKeyWord;}publicvoidsetSearchKeyWord(StringsearchKeyWord){this.searchKeyWord=searchKeyWord;}}

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

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceEsDeclare{Stringindex()defaultStringUtils.EMPTY;}

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

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

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

/***获取topic和type**@paramclz*@return*/privatePair<String/*topic*/,String/*type*/>getTopicAndType(Class<?>clz){//通过反射去获取注解中的index值EsDeclareesDeclare=clz.getAnnotation(EsDeclare.class);if(null==esDeclare||StringUtils.isEmpty(esDeclare.index())){logger.warn("getTopicAndType,esDeclareisillegal,class:{}",clz);returnnull;}returnPair.of(esDeclare.index(),clz.getSimpleName());}

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

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

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

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

三者的关系如下图所示:

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

packageorg.idea.es.project.template.api.service;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.parser.Feature;mon.collect.Lists;mons.collections.CollectionUtils;mons.lang3.StringUtils;mons.lang3.reflect.FieldUtils;mons.lang3.tuple.Pair;importorg.elasticsearch.action.delete.DeleteRequest;importorg.elasticsearch.action.index.IndexRequest;importorg.elasticsearch.action.index.IndexResponse;importorg.elasticsearch.action.search.SearchRequest;importorg.elasticsearch.action.search.SearchResponse;importorg.elasticsearch.client.RestHighLevelClient;mon.xcontent.XContentType;importorg.elasticsearch.search.SearchHit;importorg.elasticsearch.search.builder.SearchSourceBuilder;importorg.idea.es.project.template.api.config.EsDeclare;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.annotation.Id;importorg.springframework.stereotype.Repository;importjavax.naming.directory.SearchResult;importjava.io.IOException;importjava.lang.reflect.Field;importjava.util.Collections;importjava.util.List;@RepositorypublicclassEsDao<T>{privatefinalLoggerlogger=LoggerFactory.getLogger(getClass());@AutowiredprivateRestHighLevelClientrestHighLevelClient;/***条件查询**@return*/publicList<SearchResult>searchByCondition(Stringindex,Stringtype){try{SearchRequestsearchRequest=newSearchRequest(index).types(type);SearchSourceBuildersourceBuilder=newSearchSourceBuilder();sourceBuilder.from(1).size(2);searchRequest.source(sourceBuilder);SearchResponsesearchResponse=restHighLevelClient.search(searchRequest);System.out.println(searchResponse);}catch(IOExceptione){e.printStackTrace();}returnnull;}publicTqueryOne(SearchSourceBuildersourceBuilder,Class<T>clz){List<T>resultList=this.queryList(sourceBuilder,clz);if(CollectionUtils.isNotEmpty(resultList)){returnresultList.get(0);}returnnull;}/***查询**@paramsourceBuilder*@paramclz*@return*/publicList<T>queryList(SearchSourceBuildersourceBuilder,Class<T>clz){Pair<String,String>topicAndType=getTopicAndType(clz);if(null==topicAndType){logger.warn("query,nulltopicAndType,clz:{}",clz);returnCollections.emptyList();}FieldidField=getIdField(clz);if(null==idField){logger.warn("query,nullidfield,clz:{}",clz);returnCollections.emptyList();}try{SearchRequestsearchRequest=newSearchRequest(topicAndType.getLeft()).types(topicAndType.getRight()).source(sourceBuilder);SearchResponsesearchResponse=restHighLevelClient.search(searchRequest);SearchHit[]hits=searchResponse.getHits().getHits();List<T>result=Lists.newArrayListWithCapacity(hits.length);for(SearchHithit:hits){Tobj=JSON.parseObject(hit.getSourceAsString(),clz,Feature.AllowISO8601DateFormat);ObjectidObj=FieldUtils.readField(idField,obj,true);if(null==idObj){FieldUtils.writeField(idField,obj,hit.getId(),true);}result.add(obj);}returnresult;}catch(Exceptione){logger.warn("query,e:{}",e.getMessage());returnCollections.emptyList();}}/***插入或者更新,根据id字段来判断是否已有数据**@parampo*/publicvoidsaveOrUpdate(Tpo){if(po==null){thrownewIllegalArgumentException("pocannotbenull!");}try{Pair<String/*topic*/,String/*type*/>pair=getTopicAndType(po.getClass());FieldidField=getIdField(po.getClass());idField.setAccessible(true);ObjectidObj=idField.get(po);IndexRequestindexRequest=newIndexRequest(pair.getLeft(),pair.getRight(),idObj==null?null:idObj.toString());IndexResponseindexResponse=restHighLevelClient.index(indexRequest.source(JSON.toJSONStringWithDateFormat(po,"yyyy-MM-dd'T'HH:mm:ss+08:00"),XContentType.JSON));System.out.println(indexResponse);}catch(Exceptione){e.printStackTrace();}}/***删除单个元素**@parampo*/publicvoiddeleteOne(Tpo){if(po==null){thrownewIllegalArgumentException("pocannotbenull!");}try{Pair<String/*index*/,String/*type*/>pair=getTopicAndType(po.getClass());FieldidField=getIdField(po.getClass());idField.setAccessible(true);ObjectidObj=idField.get(po);DeleteRequestdeleteRequest=newDeleteRequest(pair.getLeft(),pair.getRight(),idObj==null?null:idObj.toString());restHighLevelClient.delete(deleteRequest);}catch(IllegalAccessException|IOExceptione){e.printStackTrace();}}/***根据id删除**@paramindex*@paramtype*@param_id*/publicvoiddeleteBy_Id(Stringindex,Stringtype,String_id){DeleteRequestdeleteRequest=newDeleteRequest(index,type,_id);try{restHighLevelClient.delete(deleteRequest);}catch(IOExceptione){e.printStackTrace();}}/***获取id的域**@paramclz*@return*/publicFieldgetIdField(Class<?>clz){List<Field>listWithAnnotation=FieldUtils.getFieldsListWithAnnotation(clz,Id.class);if(listWithAnnotation.size()!=1){logger.warn("getIdField,idisilleage,class:{}",clz);returnnull;}returnlistWithAnnotation.get(0);}/***获取topic和type**@paramclz*@return*/privatePair<String/*topic*/,String/*type*/>getTopicAndType(Class<?>clz){EsDeclareesDeclare=clz.getAnnotation(EsDeclare.class);if(null==esDeclare||StringUtils.isEmpty(esDeclare.index())){logger.warn("getTopicAndType,esDeclareisillegal,class:{}",clz);returnnull;}returnPair.of(esDeclare.index(),clz.getSimpleName());}}

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

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

首先是接口部分的定义:

packageorg.idea.es.project.template.api.service;importorg.idea.es.project.template.api.bo.UserSearchRecordPO;importjavax.naming.directory.SearchResult;importjava.util.List;publicinterfaceIUserSearchRecordService{/***条件查询**@paramindex*@paramtype*@return*/List<SearchResult>searchByCondition(Stringindex,Stringtype);/***查询操作**@paramuserSearchRecordPO*@return*/UserSearchRecordPOqueryByParam(UserSearchRecordPOuserSearchRecordPO);/***写入记录**@return*/UserSearchRecordPOsaveOrUpdate();/***删除单个元素*/voiddeleteOne(UserSearchRecordPOuserSearchRecordPO);}

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

packageorg.idea.es.project.template.api.service.impl;importorg.elasticsearch.index.query.BoolQueryBuilder;importorg.elasticsearch.index.query.QueryBuilders;importorg.elasticsearch.search.builder.SearchSourceBuilder;importorg.idea.es.project.template.api.bo.UserSearchRecordPO;importorg.idea.es.project.template.api.service.EsDao;importorg.idea.es.project.template.api.service.IUserSearchRecordService;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;importjavax.naming.directory.SearchResult;importjava.util.List;@ServicepublicclassUserSearchRecordServiceImplimplementsIUserSearchRecordService{@ResourceprivateEsDao<UserSearchRecordPO>esDao;@OverridepublicList<SearchResult>searchByCondition(Stringindex,Stringtype){returnesDao.searchByCondition(index,type);}@OverridepublicUserSearchRecordPOqueryByParam(UserSearchRecordPOuserSearchRecordPO){try{BoolQueryBuilderqueryBuilder=QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("id",userSearchRecordPO.getId()));SearchSourceBuildersourceBuilder=SearchSourceBuilder.searchSource().query(queryBuilder).from(0).size(1);returnesDao.queryOne(sourceBuilder,UserSearchRecordPO.class);}catch(Exceptione){e.printStackTrace();}returnnull;}@OverridepublicUserSearchRecordPOsaveOrUpdate(){UserSearchRecordPOuserSearchRecordPO=newUserSearchRecordPO();userSearchRecordPO.setId(System.currentTimeMillis());userSearchRecordPO.setUsername("idea");userSearchRecordPO.setSearchKeyWord("key-word");esDao.saveOrUpdate(userSearchRecordPO);returnuserSearchRecordPO;}@OverridepublicvoiddeleteOne(UserSearchRecordPOuserSearchRecordPO){esDao.deleteOne(userSearchRecordPO);}}

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

packageorg.idea.es.project.template.api.controller;importorg.idea.es.project.template.api.bo.UserSearchRecordPO;importorg.idea.es.project.template.api.service.IUserSearchRecordService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;@RestController@RequestMapping(value="/user-search-record")publicclassUserSearchRecordController{@AutowiredprivateIUserSearchRecordServiceiUserSearchRecordService;@GetMapping(value="/save-or-update")publicbooleansaveOrUpdate(){iUserSearchRecordService.saveOrUpdate();System.out.println("success");returntrue;}@GetMapping(value="/query-by-param")publicUserSearchRecordPOqueryByParam(Longid){UserSearchRecordPOuserSearchRecordPO=newUserSearchRecordPO();userSearchRecordPO.setId(id);returniUserSearchRecordService.queryByParam(userSearchRecordPO);}@GetMapping(value="/delete-one")publicbooleandeleteOne(longid){UserSearchRecordPOuserSearchRecordPO=newUserSearchRecordPO();userSearchRecordPO.setId(id);iUserSearchRecordService.deleteOne(userSearchRecordPO);System.out.println("success");returntrue;}}

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

好了。

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

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

推荐

Java面试题宝典

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

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。