当前位置: 首页 > news >正文

如何在自己的项目中引入ElasticSearch搜索引擎?

听说微信搜索《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连接即可:

@Configuration
public class ElasticSearchConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client=new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.78.128",9200,"http")
                )
        );
        return client;
    }
}

2.3 编写Blog实体类

编写一个类用来存储要存储的数据,我这里为了演示只在es中插入标题和作者的信息

@Data
@AllArgsConstructor
public class BlogDO {
    private String title;
    private String author;
}

2.4 准备controller和service

最后新建一个IndexController和IndexService以及IndexServiceImpl,接下来会使用。最终的目录结构如下:

(三)数据准备

要做数据的搜索,首先第一步就是数据的导入。在真实的业务场景中,数据的导入有很多方式。一种是当新增数据时在代码逻辑中做增量的导入操作,或者是由数仓团队负责数据的增量导入。我接触到的业务中,后端程序员不需要去关注导入的操作,这个步骤是数仓团队做的。

在我们个人的博客系统中,可以在新增博客后立刻同步数据到ES,也可以先通过消息中间件发送一条消息,消费者定期去读取消息新增数据。

这里演示就直接导入了:

@Controller
public class IndexController {
    @Autowired
    private IndexService indexService;
    @ResponseBody
    @GetMapping("/prepareData")
    public String prepareData(){
        String result=indexService.prepareData();
        return result;
    }
}

具体的service实现如下:

@Service
public class IndexServiceImpl implements IndexService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Override
    public String prepareData() {
        List<BlogDO> blogDOS = new ArrayList<>();
        blogDOS.add(new BlogDO("ElasticSearch究竟是个什么东西", "Java鱼仔"));
        blogDOS.add(new BlogDO("SpringBoot+SpringSecurity实现基于真实数据的授权认证", "Java鱼仔"));
        blogDOS.add(new BlogDO("Dubbo两小时快速上手教程(直接代码、Spring、SpringBoot)", "Java鱼仔"));
        blogDOS.add(new BlogDO("浅析五种最常用的Java加密算法", "Java鱼仔"));
        blogDOS.add(new BlogDO("Java程序员需要知道的操作系统知识汇总", "Java鱼仔"));
        blogDOS.add(new BlogDO("一步步教你如何在SpringBoot项目中引入支付功能", "Java鱼仔"));
        blogDOS.add(new BlogDO("Zookeeper实现分布式锁的原理是什么?", "Java鱼仔"));
        blogDOS.add(new BlogDO("一个成熟的Java项目如何优雅地处理异常", "Java鱼仔"));
        blogDOS.add(new BlogDO("基于SpringBoot实现文件的上传下载", "Java鱼仔"));
        blogDOS.add(new BlogDO("如何用Java写一个规范的http接口?", "Java鱼仔"));
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        blogDOS.stream().forEach(x -> {
            bulkRequest.add(new IndexRequest("blog_index").source(JSON.toJSONString(x), XContentType.JSON));
        });
        BulkResponse responses=null;
        try {
            responses = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return String.valueOf(responses.status());
    }
}

我选取了自己的几篇博客文章,多执行几次接口,保证ES中有几十条数据供测试使用即可。

(四)博客搜索

接下来就是搜索的过程了,搜索的逻辑其实比较简单,具体的代码就按照上一篇博客中的方式来编写,在真实业务场景中,每个公司可能会有自己的封装搜索方法:

IndexController中增加一个方法:

@GetMapping("/search")
public String search(@RequestParam("keywords")String keywords, @RequestParam("pageNum")String pageNum, @RequestParam("pageSize")String pageSize, Model model){
    List<Map<String,Object>> list=indexService.searchByKeywords(keywords,pageNum,pageSize);
    model.addAttribute("datas",list);
    return "search";
}

具体实现类中增加方法:

@Override
public List<Map<String, Object>> searchByKeywords(String keywords, String pageNum, String pageSize) {
    return this.searchData(keywords,Integer.parseInt(pageNum),Integer.parseInt(pageSize));
}

public List<Map<String,Object>> searchData(String keywords, int pageNum, int pageSize){
    if (pageNum<1){
        pageNum=1;
    }
    //生成搜索对象
    SearchRequest request = new SearchRequest("blog_index");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    //设置分页参数
    searchSourceBuilder.from(pageNum);
    searchSourceBuilder.size(pageSize);
    //设置搜索的字段
    MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", keywords);
    searchSourceBuilder.query(matchQueryBuilder);
    searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
    request.source(searchSourceBuilder);
    SearchResponse search=null;
    try {
        search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
    } catch (IOException e) {
        e.printStackTrace();
    }
    //将结果返回
    List<Map<String,Object>> result=new ArrayList();
    SearchHit[] hits = search.getHits().getHits();
    for (SearchHit searchHit:hits){
        result.add(searchHit.getSourceAsMap());

    }
    return result;
}

简单写一个前端页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><!--引入thymeleaf-->
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <div th:each="datas:${datas}">
        <span th:text="${datas.author}"/>
        <span th: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支持高亮查询,下面就通过代码来实现。

稍微修改一下搜索的代码,增加高亮配置,在返回值中用高亮字符串替换原来的字符串。

public List<Map<String,Object>> searchHighLightData(String keywords, int pageNum, int pageSize){
    if (pageNum<1){
        pageNum=1;
    }
    SearchRequest request = new SearchRequest("blog_index");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.from(pageNum);
    searchSourceBuilder.size(pageSize);
    //高亮构造器
    HighlightBuilder highlightBuilder=new HighlightBuilder();
    //高亮查询字段
    highlightBuilder.field("title");
    //是否将所有匹配到的字段高亮显示,false表示只显示一个
    highlightBuilder.requireFieldMatch(false);
    //高亮的标签
    highlightBuilder.preTags("<span style='color:red'>");
    highlightBuilder.postTags("</span>");
    searchSourceBuilder.highlighter(highlightBuilder);
    
    MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", keywords);
    searchSourceBuilder.query(matchQueryBuilder);
    searchSourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
    request.source(searchSourceBuilder);
    SearchResponse search=null;
    try {
        search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
    } catch (IOException e) {
        e.printStackTrace();
    }
    List<Map<String,Object>> result=new ArrayList();
    SearchHit[] hits = search.getHits().getHits();
    //遍历结果,将高亮返回值title替换到原来的title中
    for (SearchHit searchHit:hits){
        Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
        Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
        HighlightField title = highlightFields.get("title");
        if (title!=null){
            StringBuilder highLightTitle=new StringBuilder();
            Text[] texts = title.fragments();
            for(Text text:texts){
                highLightTitle.append(text);
            }
            sourceAsMap.put("title",highLightTitle);
        }
        result.add(sourceAsMap);
    }
    return result;
}

继续访问

http://localhost:8080/search?keywords=Java&pageNum=1&pageSize=10,

通过断点可以看到,搜索的关键词已经被我们设置的span标签包住了。

在前端thymeaf中,我是用了th:utext,这个标签可以将Html解析,最终的高亮显示如下:

(六)总结

ES的应用到这里就结束了,ES可以很方便地嵌入到真实的项目中,对于应用来讲,了解到这一步已经足够,对于想要提高的人来说,还远远不够。作为最流行的分布式搜索引擎,ES还有许多值得学的地方,任重而道远。我是鱼仔,我们下期再见!

相关文章:

  • JSON--百度百科
  • 一个双非本科程序员工作一年的经历
  • guava 并发
  • Oauth2是个什么东西?
  • [javaSE] 看知乎学习工厂模式
  • 数据库分库分表扫盲,不会用也得知道概念
  • 写了那么久的String字符串,你可能根本不懂它!
  • 2012 借教室
  • 怎样才能写出规范的好代码?
  • 让Android Studio支持系统签名
  • 详解Java中的BIO、NIO、AIO
  • 取经阿里十年技术大佬,得到Java线上问题排查攻略!
  • Understanding memory usage on Linux
  • Java中的拦截器和过滤器有什么区别
  • 为什么要引入分布式任务调度系统?
  • C语言笔记(第一章:C语言编程)
  • DataBase in Android
  • iOS | NSProxy
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • java概述
  • java中的hashCode
  • js
  • js对象的深浅拷贝
  • mongodb--安装和初步使用教程
  • MySQL的数据类型
  • node学习系列之简单文件上传
  • October CMS - 快速入门 9 Images And Galleries
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • vue.js框架原理浅析
  • Web Storage相关
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 从setTimeout-setInterval看JS线程
  • 关于springcloud Gateway中的限流
  • 使用API自动生成工具优化前端工作流
  • 使用Swoole加速Laravel(正式环境中)
  • 收藏好这篇,别再只说“数据劫持”了
  • 系统认识JavaScript正则表达式
  • 正则学习笔记
  • 阿里云ACE认证学习知识点梳理
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (TOJ2804)Even? Odd?
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (排序详解之 堆排序)
  • (一)VirtualBox安装增强功能
  • (转)原始图像数据和PDF中的图像数据
  • **python多态
  • ../depcomp: line 571: exec: g++: not found
  • .form文件_SSM框架文件上传篇
  • .net core 连接数据库,通过数据库生成Modell
  • .Net Redis的秒杀Dome和异步执行
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET程序员迈向卓越的必由之路
  • .NET中统一的存储过程调用方法(收藏)