听说微信搜索《Java鱼仔》会变更强!
本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看
(一)介绍
在大多数系统中,都需要支持搜索的功能,以简单博客系统为例,虽然说Mysql也可以通过模糊查询匹配到对应的数据,但是效率实在太低。这个时候就需要拿出分布式搜索引擎ElasticSearch了。本博客重点在于ES的集成使用,因此前端采用最简单的方式呈现,大家只需要关注后端逻辑即可。(本博客基于ES7.6.1,和ES6.X版本有较大差异)
(二)项目搭建
2.1 依赖引入
依赖主要就是web、es以及thymleaf相关:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></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></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><!--thymleaf相关--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf-spring5</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-java8time</artifactId></dependency>
2.2 编写ES的配置类
编写ES的配置类,编写连接信息,之后直接通过Autowired连接即可:
publicclassElasticSearchConfig { publicRestHighLevelClientrestHighLevelClient(){ RestHighLevelClientclient=newRestHighLevelClient( RestClient.builder( newHttpHost("192.168.78.128",9200,"http") ) ); returnclient; } }
2.3 编写Blog实体类
编写一个类用来存储要存储的数据,我这里为了演示只在es中插入标题和作者的信息
publicclassBlogDO { privateStringtitle; privateStringauthor; }
2.4 准备controller和service
最后新建一个IndexController和IndexService以及IndexServiceImpl,接下来会使用。最终的目录结构如下:
(三)数据准备
要做数据的搜索,首先第一步就是数据的导入。在真实的业务场景中,数据的导入有很多方式。一种是当新增数据时在代码逻辑中做增量的导入操作,或者是由数仓团队负责数据的增量导入。我接触到的业务中,后端程序员不需要去关注导入的操作,这个步骤是数仓团队做的。
在我们个人的博客系统中,可以在新增博客后立刻同步数据到ES,也可以先通过消息中间件发送一条消息,消费者定期去读取消息新增数据。
这里演示就直接导入了:
publicclassIndexController { privateIndexServiceindexService; "/prepareData") (publicStringprepareData(){ Stringresult=indexService.prepareData(); returnresult; } }
具体的service实现如下:
publicclassIndexServiceImplimplementsIndexService { privateRestHighLevelClientrestHighLevelClient; publicStringprepareData() { List<BlogDO>blogDOS=newArrayList<>(); blogDOS.add(newBlogDO("ElasticSearch究竟是个什么东西", "Java鱼仔")); blogDOS.add(newBlogDO("SpringBoot+SpringSecurity实现基于真实数据的授权认证", "Java鱼仔")); blogDOS.add(newBlogDO("Dubbo两小时快速上手教程(直接代码、Spring、SpringBoot)", "Java鱼仔")); blogDOS.add(newBlogDO("浅析五种最常用的Java加密算法", "Java鱼仔")); blogDOS.add(newBlogDO("Java程序员需要知道的操作系统知识汇总", "Java鱼仔")); blogDOS.add(newBlogDO("一步步教你如何在SpringBoot项目中引入支付功能", "Java鱼仔")); blogDOS.add(newBlogDO("Zookeeper实现分布式锁的原理是什么?", "Java鱼仔")); blogDOS.add(newBlogDO("一个成熟的Java项目如何优雅地处理异常", "Java鱼仔")); blogDOS.add(newBlogDO("基于SpringBoot实现文件的上传下载", "Java鱼仔")); blogDOS.add(newBlogDO("如何用Java写一个规范的http接口?", "Java鱼仔")); BulkRequestbulkRequest=newBulkRequest(); bulkRequest.timeout("10s"); blogDOS.stream().forEach(x-> { bulkRequest.add(newIndexRequest("blog_index").source(JSON.toJSONString(x), XContentType.JSON)); }); BulkResponseresponses=null; try { responses=restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT); } catch (IOExceptione) { e.printStackTrace(); } returnString.valueOf(responses.status()); } }
我选取了自己的几篇博客文章,多执行几次接口,保证ES中有几十条数据供测试使用即可。
(四)博客搜索
接下来就是搜索的过程了,搜索的逻辑其实比较简单,具体的代码就按照上一篇博客中的方式来编写,在真实业务场景中,每个公司可能会有自己的封装搜索方法:
IndexController中增加一个方法:
"/search") (publicStringsearch( ("keywords")Stringkeywords, ("pageNum")StringpageNum, ("pageSize")StringpageSize, Modelmodel){ List<Map<String,Object>>list=indexService.searchByKeywords(keywords,pageNum,pageSize); model.addAttribute("datas",list); return"search"; }
具体实现类中增加方法:
publicList<Map<String, Object>>searchByKeywords(Stringkeywords, StringpageNum, StringpageSize) { returnthis.searchData(keywords,Integer.parseInt(pageNum),Integer.parseInt(pageSize)); } publicList<Map<String,Object>>searchData(Stringkeywords, intpageNum, intpageSize){ if (pageNum<1){ pageNum=1; } //生成搜索对象SearchRequestrequest=newSearchRequest("blog_index"); SearchSourceBuildersearchSourceBuilder=newSearchSourceBuilder(); //设置分页参数searchSourceBuilder.from(pageNum); searchSourceBuilder.size(pageSize); //设置搜索的字段MatchQueryBuildermatchQueryBuilder=QueryBuilders.matchQuery("title", keywords); searchSourceBuilder.query(matchQueryBuilder); searchSourceBuilder.timeout(newTimeValue(10, TimeUnit.SECONDS)); request.source(searchSourceBuilder); SearchResponsesearch=null; try { search=restHighLevelClient.search(request, RequestOptions.DEFAULT); } catch (IOExceptione) { e.printStackTrace(); } //将结果返回List<Map<String,Object>>result=newArrayList(); SearchHit[] hits=search.getHits().getHits(); for (SearchHitsearchHit:hits){ result.add(searchHit.getSourceAsMap()); } returnresult; }
简单写一个前端页面
<htmlxmlns:th="http://www.thymeleaf.org"><!--引入thymeleaf--><head><metacharset="UTF-8"><title>Title</title></head><body><div><divth:each="datas:${datas}"><spanth:text="${datas.author}"/><spanth:utext="${datas.title}"/><hr/></div></div></body></html>
跑起来看一下,访问
http://localhost:8080/search?keywords=Java&pageNum=1&pageSize=10
在链接中,我关键词填了Java,pageNum是1,每页展示10行,可以看到和Java相关的数据就被查出来了。
(五)实现高亮查询
在百度搜索Java时,可以看到查询出来的Java被高亮显示了,之前在讲ES语法的时候,我们也知道了ES支持高亮查询,下面就通过代码来实现。
稍微修改一下搜索的代码,增加高亮配置,在返回值中用高亮字符串替换原来的字符串。
publicList<Map<String,Object>>searchHighLightData(Stringkeywords, intpageNum, intpageSize){ if (pageNum<1){ pageNum=1; } SearchRequestrequest=newSearchRequest("blog_index"); SearchSourceBuildersearchSourceBuilder=newSearchSourceBuilder(); searchSourceBuilder.from(pageNum); searchSourceBuilder.size(pageSize); //高亮构造器HighlightBuilderhighlightBuilder=newHighlightBuilder(); //高亮查询字段highlightBuilder.field("title"); //是否将所有匹配到的字段高亮显示,false表示只显示一个highlightBuilder.requireFieldMatch(false); //高亮的标签highlightBuilder.preTags("<span style='color:red'>"); highlightBuilder.postTags("</span>"); searchSourceBuilder.highlighter(highlightBuilder); MatchQueryBuildermatchQueryBuilder=QueryBuilders.matchQuery("title", keywords); searchSourceBuilder.query(matchQueryBuilder); searchSourceBuilder.timeout(newTimeValue(10, TimeUnit.SECONDS)); request.source(searchSourceBuilder); SearchResponsesearch=null; try { search=restHighLevelClient.search(request, RequestOptions.DEFAULT); } catch (IOExceptione) { e.printStackTrace(); } List<Map<String,Object>>result=newArrayList(); SearchHit[] hits=search.getHits().getHits(); //遍历结果,将高亮返回值title替换到原来的title中for (SearchHitsearchHit:hits){ Map<String, Object>sourceAsMap=searchHit.getSourceAsMap(); Map<String, HighlightField>highlightFields=searchHit.getHighlightFields(); HighlightFieldtitle=highlightFields.get("title"); if (title!=null){ StringBuilderhighLightTitle=newStringBuilder(); Text[] texts=title.fragments(); for(Texttext:texts){ highLightTitle.append(text); } sourceAsMap.put("title",highLightTitle); } result.add(sourceAsMap); } returnresult; }
继续访问
http://localhost:8080/search?keywords=Java&pageNum=1&pageSize=10,
通过断点可以看到,搜索的关键词已经被我们设置的span标签包住了。
在前端thymeaf中,我是用了th:utext,这个标签可以将Html解析,最终的高亮显示如下:
(六)总结
ES的应用到这里就结束了,ES可以很方便地嵌入到真实的项目中,对于应用来讲,了解到这一步已经足够,对于想要提高的人来说,还远远不够。作为最流行的分布式搜索引擎,ES还有许多值得学的地方,任重而道远。我是鱼仔,我们下期再见!