Spring Boot 2.0 整合 ES 5 文章内容搜索实战

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 上一篇讲了在怎么在 Spring Boot 2.0 上整合 ES 5 ,这一篇聊聊具体实战。简单讲下如何实现文章、问答这些内容搜索的具体实现。

本章内容
文章内容搜索思路
搜索内容分词
搜索查询语句
筛选条件
分页、排序条件
小结

一、文章内容搜索思路
上一篇讲了在怎么在 Spring Boot 2.0 上整合 ES 5 ,这一篇聊聊具体实战。简单讲下如何实现文章、问答这些内容搜索的具体实现。实现思路很简单:

基于「短语匹配」并设置最小匹配权重值
哪来的短语,利用 IK 分词器分词
基于 Fiter 实现筛选
基于 Pageable 实现分页排序
这里直接调用搜索的话,容易搜出不尽人意的东西。因为内容搜索关注内容的连接性。所以这里处理方法比较 low ,希望多交流一起实现更好的搜索方法。就是通过分词得到很多短语,然后利用短语进行短语精准匹配。

ES 安装 IK 分词器插件很简单。第一步,在下载对应版本 https://github.com/medcl/elasticsearch-analysis-ik/releases。第二步,在 elasticsearch-5.5.3/plugins 目录下,新建一个文件夹 ik,把 elasticsearch-analysis-ik-5.5.3.zip 解压后的文件拷贝到 elasticsearch-5.1.1/plugins/ik 目录下。最后重启 ES 即可。

二、搜索内容分词

安装好 IK ,如何调用呢?

第一步,我这边搜搜内容会以 逗号 拼接传入。所以会先将逗号分割

第二步,在搜索词中加入自己本身,因为有些词经过 ik 分词后就没了... 这是个 bug

第三步,利用 AnalyzeRequestBuilder 对象获取 IK 分词后的返回值对象列表

第四步,优化分词结果,比如都为词,则保留全部;有词有字,则保留词;只有字,则保留字

核心实现代码如下:

   /**
     * 搜索内容分词
     */
    protected List<String>      handlingSearchContent(String searchContent) {
 
             List<String> searchTermResultList = new ArrayList<>();
             // 按逗号分割,获取搜索词列表
             List<String> searchTermList = Arrays.asList(searchContent.split(SearchConstant.STRING_TOKEN_SPLIT));
 
             // 如果搜索词大于 1 个字,则经过 IK 分词器获取分词结果列表
             searchTermList.forEach(searchTerm -> {
                 // 搜索词 TAG 本身加入搜索词列表,并解决 will 这种问题
                 searchTermResultList.add(searchTerm);
                 // 获取搜索词 IK 分词列表
                 searchTermResultList.addAll(getIkAnalyzeSearchTerms(searchTerm));
             });
 
             return searchTermResultList;
    }
 
    /**
     * 调用 ES 获取 IK 分词后结果
     */
    protected List<String>      getIkAnalyzeSearchTerms(String searchContent) {
             AnalyzeRequestBuilder ikRequest = new AnalyzeRequestBuilder(elasticsearchTemplate.getClient(),
                     AnalyzeAction.INSTANCE, SearchConstant.INDEX_NAME,      searchContent);
             ikRequest.setTokenizer(SearchConstant.TOKENIZER_IK_MAX);
             List<AnalyzeResponse.AnalyzeToken> ikTokenList =      ikRequest.execute().actionGet().getTokens();
 
             // 循环赋值
             List<String> searchTermList = new ArrayList<>();
             ikTokenList.forEach(ikToken -> {
                 searchTermList.add(ikToken.getTerm());
             });
 
             return handlingIkResultTerms(searchTermList);
    }
 
    /**
     * 如果分词结果:洗发水(洗发、发水、洗、发、水)
     * - 均为词,保留
     * - 词 + 字,只保留词
     * - 均为字,保留字
     */
    private List<String>      handlingIkResultTerms(List<String> searchTermList) {
             Boolean isPhrase = false;
             Boolean isWord = false;
             for (String term : searchTermList) {
                 if (term.length() > SearchConstant.SEARCH_TERM_LENGTH)      {
                     isPhrase = true;
                 } else {
                     isWord = true;
                 }
             }
 
             if (isWord & isPhrase) {
                 List<String> phraseList = new ArrayList<>();
                 searchTermList.forEach(term -> {
                     if (term.length() > SearchConstant.SEARCH_TERM_LENGTH)      {
                         phraseList.add(term);
                     }
                 });
                 return phraseList;
             }
 
             return searchTermList;
    }

三、搜索查询语句

构造内容枚举对象,罗列需要搜索的字段,ContentSearchTermEnum 代码如下:

import lombok.AllArgsConstructor;
 
@AllArgsConstructor
public enum ContentSearchTermEnum {
 
    // 标题
    TITLE("title"),
    // 内容
    CONTENT("content");
 
    /**
     * 搜索字段
     */
    private String      name;
 
    public String      getName() {
             return name;
    }
 
    public void      setName(String name) {
             this.name = name;
    }
 
}

循环进行「短语搜索匹配」搜索字段,然后并设置最低权重值为 1。核心代码如下:

   /**
     * 构造查询条件
     */
    private void      buildMatchQuery(BoolQueryBuilder queryBuilder, List<String>      searchTermList) {
             for (String searchTerm : searchTermList) {
                 for (ContentSearchTermEnum searchTermEnum : ContentSearchTermEnum.values())      {
                     queryBuilder.should(QueryBuilders.matchPhraseQuery(searchTermEnum.getName(),      searchTerm));
                 }
             }
             queryBuilder.minimumShouldMatch(SearchConstant.MINIMUM_SHOULD_MATCH);
    }

四、筛选条件
搜到东西不止,有时候需求是这样的。需要在某个品类下搜索,比如电商需要在某个 品牌 下搜索商品。那么需要构造一些 fitler 进行筛选。对应 SQL 语句的 Where 下的 OR 和 AND 两种语句。在 ES 中使用 filter 方法添加过滤。代码如下:

   /**
     * 构建筛选条件
     */
    private void      buildFilterQuery(BoolQueryBuilder boolQueryBuilder, Integer type, String      category) {
             // 内容类型筛选
             if (type != null) {
                 BoolQueryBuilder typeFilterBuilder = QueryBuilders.boolQuery();
                 typeFilterBuilder.should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME,      type).lenient(true));
                 boolQueryBuilder.filter(typeFilterBuilder);
             }
 
             // 内容类别筛选
             if (!StringUtils.isEmpty(category)) {
                 BoolQueryBuilder categoryFilterBuilder = QueryBuilders.boolQuery();
                 categoryFilterBuilder.should(QueryBuilders.matchQuery(SearchConstant.CATEGORY_NAME,      category).lenient(true));
                 boolQueryBuilder.filter(categoryFilterBuilder);
             }
    }

type 是大类,category 是小类,这样就可以支持 大小类 筛选。但是如果需要在 type = 1 或者 type = 2 中搜索呢?具体实现代码很简单:

typeFilterBuilder
    .should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME,      1)
    .should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME,      2)
    .lenient(true));

通过链式表达式,两个 should 实现或,即 SQL 对应的 OR 语句。通过两个 BoolQueryBuilder 实现与,即 SQL 对应的 AND 语句。

五、分页、排序条件

分页排序代码就很简单了:

  @Override
    public PageBean      searchContent(ContentSearchBean contentSearchBean) {
 
             Integer pageNumber = contentSearchBean.getPageNumber();
             Integer pageSize = contentSearchBean.getPageSize();
 
             PageBean<ContentEntity> resultPageBean = new PageBean<>();
             resultPageBean.setPageNumber(pageNumber);
             resultPageBean.setPageSize(pageSize);
 
             // 构建搜索短语
             String searchContent = contentSearchBean.getSearchContent();
             List<String> searchTermList =      handlingSearchContent(searchContent);
 
             // 构建查询条件
             BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
             buildMatchQuery(boolQueryBuilder, searchTermList);
 
             // 构建筛选条件
             buildFilterQuery(boolQueryBuilder, contentSearchBean.getType(),      contentSearchBean.getCategory());
 
             // 构建分页、排序条件
             Pageable pageable = PageRequest.of(pageNumber, pageSize);
             if (!StringUtils.isEmpty(contentSearchBean.getOrderName())) {
                 pageable = PageRequest.of(pageNumber, pageSize, Sort.Direction.DESC,      contentSearchBean.getOrderName());
             }
             SearchQuery searchQuery = new NativeSearchQueryBuilder().withPageable(pageable)
                     .withQuery(boolQueryBuilder).build();
 
             // 搜索
             LOGGER.info("\n ContentServiceImpl.searchContent() [" +      searchContent
                     + "] \n DSL  = \n " +      searchQuery.getQuery().toString());
             Page<ContentEntity> contentPage =      contentRepository.search(searchQuery);
 
             resultPageBean.setResult(contentPage.getContent());
             resultPageBean.setTotalCount((int) contentPage.getTotalElements());
             resultPageBean.setTotalPage((int) contentPage.getTotalElements() /      resultPageBean.getPageSize() + 1);
             return resultPageBean;
    }

利用 Pageable 对象,构造分页参数以及指定对应的 排序字段、排序顺序(DESC ASC)即可。

文章来源:http://mp.weixin.qq.com/s/ZoJzF9VpynUBSQWlJJjmEw

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
目录
相关文章
|
2月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
2月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
2月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
6天前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
23 2
|
2月前
|
NoSQL Java Redis
Redis6入门到实战------ 八、Redis与Spring Boot整合
这篇文章详细介绍了如何在Spring Boot项目中整合Redis,包括在`pom.xml`中添加依赖、配置`application.properties`文件、创建配置类以及编写测试类来验证Redis的连接和基本操作。
Redis6入门到实战------ 八、Redis与Spring Boot整合
|
2月前
|
SQL 数据库
Spring5入门到实战------13、使用JdbcTemplate操作数据库(批量增删改)。具体代码+讲解 【下篇】
这篇文章是Spring5框架的实战教程,深入讲解了如何使用JdbcTemplate进行数据库的批量操作,包括批量添加、批量修改和批量删除的具体代码实现和测试过程,并通过完整的项目案例展示了如何在实际开发中应用这些技术。
Spring5入门到实战------13、使用JdbcTemplate操作数据库(批量增删改)。具体代码+讲解 【下篇】
|
2月前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
2月前
|
XML Java 数据格式
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
这篇文章是Spring5框架的入门教程,详细讲解了IOC容器中Bean的自动装配机制,包括手动装配、`byName`和`byType`两种自动装配方式,并通过XML配置文件和Java代码示例展示了如何在Spring中实现自动装配。
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
|
2月前
|
Java API UED
【实战秘籍】Spring Boot开发者的福音:掌握网络防抖动,告别无效请求,提升用户体验!
【8月更文挑战第29天】网络防抖动技术能有效处理频繁触发的事件或请求,避免资源浪费,提升系统响应速度与用户体验。本文介绍如何在Spring Boot中实现防抖动,并提供代码示例。通过使用ScheduledExecutorService,可轻松实现延迟执行功能,确保仅在用户停止输入后才触发操作,大幅减少服务器负载。此外,还可利用`@Async`注解简化异步处理逻辑。防抖动是优化应用性能的关键策略,有助于打造高效稳定的软件系统。
44 2
|
2月前
|
XML Java Maven
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
这篇文章是Spring5框架的入门到实战教程,介绍了Spring5的新功能——整合日志框架Log4j2,包括Spring5对日志框架的通用封装、如何在项目中引入Log4j2、编写Log4j2的XML配置文件,并通过测试类展示了如何使用Log4j2进行日志记录。
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)