[Spring Boot]12 ElasticSearch实现分词搜索功能

这篇具有很好参考价值的文章主要介绍了[Spring Boot]12 ElasticSearch实现分词搜索功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

我们在使用搜索功能的时候,有时,为了使搜索的结果更多更广,比如搜索字符串“领导力”,希望有这些组合的结果(领导力、领导、领、导、力)都要能够全部展示出来。
这里我们引入ElasticSearch结合分词插件,来实现这样的搜索功能。

二、搜索功能的需求

比如:一款app需要对“课程”进行关键字搜索。
首先:课程包含的信息有:标题、子标题、讲师、简介、所属标签、封面图片、讲数等等。
搜索的需求为:输入关键字,要能分词匹配课程的这些信息:标题、子标题、讲师、所属标签。
搜索需求:搜索结果的原型图如下图所示:
[Spring Boot]12 ElasticSearch实现分词搜索功能
红色高亮为搜索结果,覆盖了标题、子标题、所属标签、讲师名称,并能进行分词搜索且关键字高亮。

三、需求开发

1、服务器安装ElasticSearch和IK分词器

参考链接: [Linux安装软件详解系列]05 安装ElasticSearch和IK分词器

2、需求开发

1)pom.xml引入jar包:
 <!--elasticsearch-->
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
 </dependency>       
2)yml增加配置
  // 安装ElasticSearch时对应的信息
  elasticsearch:
    rest:
      uris: http://ip:9200
      username: test
      password: 123456
3)配置类ElasticsearchConfig

ElasticsearchConfig:

package com.test.api.config;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
/**
 * es 配置
 *
 * @author /
 */
@EnableElasticsearchRepositories(basePackages = {"com.test.bi.*.repository"})
@Configuration
public class ElasticsearchConfig {
    @Value("${spring.elasticsearch.rest.uris}")
    private String hostAndPort;
    @Value("${spring.elasticsearch.rest.username}")
    private String username;
    @Value("${spring.elasticsearch.rest.password}")
    private String password;
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo(hostAndPort)
                .withBasicAuth(username, password)
                .build();
        return RestClients.create(clientConfiguration).rest();
    }
}
4)工具类ElasticsearchUtil

ElasticsearchUtil:

package com.test.common.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.github.pagehelper.PageInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * es 工具类
 *
 * @author /
 */
@Slf4j
@Component
public class ElasticsearchUtil {
    @Resource
    private RestHighLevelClient restHighLevelClient;
    /**
     * 普通查询
     *
     * @param index               索引名称
     * @param searchSourceBuilder 查询条件构建
     * @param resultClass         类
     * @param currentPage         当前页 分页的页码,不是es 的
     * @param size                每页显示数据
     */
    public <T> PageInfo<T> page(String index, SearchSourceBuilder searchSourceBuilder, Class<T> resultClass,
                                int currentPage, int size, List<String> highFields) {
        SearchRequest request = new SearchRequest(index);
        // 高亮字段设置
        if (CollectionUtil.isNotEmpty(highFields)) {
            buildHighLight(searchSourceBuilder, highFields);
        }
        // 分页
        int num = (currentPage - 1) * size;
        searchSourceBuilder.from(num)
                .size(size);
        request.source(searchSourceBuilder);
        SearchResponse response = null;
        try {
            response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        assert response != null;
        return analysisResponse(response, resultClass, currentPage, size, highFields);
    }

    /**
     * 解析es 查询结果
     *
     * @param response    返回
     * @param resultClass 转换成对象类
     */
    private <T> PageInfo<T> analysisResponse(SearchResponse response, Class<T> resultClass, int currentPage, int size, List<String> highFields) {
        SearchHit[] searchHits = response.getHits().getHits();
        List<T> retList = new ArrayList<>(searchHits.length);
        for (SearchHit searchHit : searchHits) {
            String strJson = searchHit.getSourceAsString();
            T t = JSONUtil.toBean(strJson, resultClass);
            try {
                setId(resultClass, t, String.valueOf(searchHit.getId()));
            } catch (Exception e) {
                log.info("es 查询数据设置主键id值异常", e);
            }
            // 高亮字段设置后,组织结果,es 结果建议与java 对象 名称一直,基本要求
            if (!CollectionUtils.isEmpty(highFields)) {
                Map<String, HighlightField> highlightFieldMap = searchHit.getHighlightFields();
                HighlightField highlightField;
                for (String field : highFields) {
                    highlightField = highlightFieldMap.get(field);
                    if (highlightField != null) {
                        // 获取指定字段的高亮片段
                        Text[] fragments = highlightField.getFragments();
                        // 将这些高亮片段拼接成一个完整的高亮字段
                        StringBuilder builder = new StringBuilder();
                        for (Text text : fragments) {
                            builder.append(text);
                        }
                        // 设置到实体类中
                        setValue(resultClass, t, builder.toString(), field);
                    }
                }
            }
            retList.add(t);
        }
        long totalNum = response.getHits().getTotalHits();
        PageInfo<T> pageVo = new PageInfo<>();
        pageVo.setPageNum(currentPage);
        pageVo.setPageSize(size);
        pageVo.setTotal(totalNum);
        pageVo.setList(retList);
        return pageVo;
    }

    /**
     * 对象id 为空时,给id 赋值
     *
     * @param resultClass 类
     * @param t           对象
     * @param id          主键id 的值
     */
    @SneakyThrows
    private <T> void setId(Class<T> resultClass, T t, Object id) {
        Field field = ReflectionUtils.findField(resultClass, "id");
        if (null != field) {
            field.setAccessible(true);
            Object object = ReflectionUtils.getField(field, t);
            if (object == null) {
                Method method = resultClass.getMethod("setId", String.class);
                ReflectionUtils.invokeMethod(method, t, id);
            }
        }
    }

    /**
     * 指定字段赋值
     *
     * @param resultClass 类
     * @param t           对象
     * @param fieldValue  字段名
     * @param fieldName   字段值
     */
    @SneakyThrows
    private <T> void setValue(Class<T> resultClass, T t, Object fieldValue, String fieldName) {
        Field field = ReflectionUtils.findField(resultClass, fieldName);
        if (null != field) {
            field.setAccessible(true);
            String methodName = "set".concat(captureName(fieldName));
            Method method = resultClass.getMethod(methodName, String.class);
            ReflectionUtils.invokeMethod(method, t, fieldValue);
        }
    }

    /**
     * 进行字母的ascii编码前移,效率要高于截取字符串进行转换的操作
     *
     * @param str /
     */
    private String captureName(String str) {
        char[] cs = str.toCharArray();
        cs[0] -= 32;
        return String.valueOf(cs);
    }

    /**
     * 设置高亮
     *
     * @param searchSourceBuilder /
     * @param fields              高亮字段
     */
    private void buildHighLight(SearchSourceBuilder searchSourceBuilder, List<String> fields) {
        // 设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        fields.forEach(highlightBuilder::field);
        highlightBuilder.preTags("<em>");
        highlightBuilder.postTags("</em>");
        // 给请求设置高亮
        searchSourceBuilder.highlighter(highlightBuilder);
    }

    /**
     * es Scroll 深分页 定义bean
     */
    @AllArgsConstructor
    @Data
    public class ScrollPageBean<T> {
        private String scrollId;
        private PageInfo<T> scrollPage;
    }
}
5)返回的数据BO封装
package com.test.bi.bo.course;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.io.Serializable;

/**
 * 课程-搜索
 *
 * @author /
 */
@Document(indexName = "course_es", type = "_doc", replicas = 0)
@Data
public class CourseEsDTO implements Serializable {

    @ApiModelProperty(value = "课程编号")
    @Id
    private String id;

    @ApiModelProperty(value = "标题")
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String name;

    @ApiModelProperty(value = "讲师名称")
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String teacherName;

    @ApiModelProperty(value = "子标题")
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String subtitle;

    @ApiModelProperty(value = "所属标签名称列表")
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String labelName;

    @ApiModelProperty(value = "封面图片")
    @Field(type = FieldType.Text)
    private String pic;

    @ApiModelProperty(value = "讲数")
    @Field(type = FieldType.Integer)
    private Integer count;
}
6)保存数据至ElasticSearch

保存数据到ElasticSearch中,通常有两种方式:一种是通过ElasticsearchRepository 接口,另一种是通过ElasticsearchTemplate接口,我们使用ElasticsearchRepository接口来实现。
实现接口:CourseEsRepository,可以放在mapper里面。

package com.test.bi.mapper;

import com.test.bi.bo.course.CourseEsDTO;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

/**
 * 课程-搜索
 *
 * @author /
 */
@Repository
public interface CourseEsRepository extends ElasticsearchRepository<CourseEsDTO, String> {
}

保存数据:

@Resource
private CourseEsRepository courseEsRepository;

@Override
public void saveCourseEs() {
    try {
        courseEsRepository.deleteAll();
        // 从库中取数据
        List<CourseEsDTO> list = courseMapper.getCourseEsList();
        if (IterUtil.isNotEmpty(list)) {
            courseEsRepository.saveAll(list);
        }
    } catch (Exception e) {
        log.error("es error:{}", e.getMessage());
    }
}
7)根据关键字搜索,分页返回数据
@Override
public PageInfo<CourseEsDTO> getCourseEs(String keywords, Integer pageNum, Integer pageSize) {
    // 使用best_fields模式, 对多个字段进行查询
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    String[] queryFields = {"teacherName", "subtitle", "labelName"
    };
    QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keywords, queryFields)
            .field("name", 2F)
            .tieBreaker(0.3F);
    searchSourceBuilder.query(queryBuilder);
    // 高亮
    List<String> highFields = ListUtil.toList(queryFields);
    highFields.add("name");
    // 分页返回数据
    return elasticsearchUtil.page("course_es", searchSourceBuilder, CourseEsDTO.class, pageNum,
            pageSize, highFields);
}

8)总结

总结一下,就是通过一个小的需求例子,很好地实现了分词搜索,并且要能够高亮显示关键字。
具体的ElasticSearch和对应iK分词的代码功能,我就不一一展开了,可以去官方查看,这里只是演示一下实现搜索功能的简单过程,希望对大家有所帮助。文章来源地址https://www.toymoban.com/news/detail-436457.html

到了这里,关于[Spring Boot]12 ElasticSearch实现分词搜索功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring Boot进阶(19):探索ElasticSearch:如何利用Spring Boot轻松实现高效数据搜索与分析

            ElasticSearch是一款基于Lucene的开源搜索引擎,具有高效、可扩展、分布式的特点,可用于全文搜索、日志分析、数据挖掘等场景。Spring Boot作为目前最流行的微服务框架之一,也提供了对ElasticSearch的支持。本篇文章将介绍如何在Spring Boot项目中整合ElasticSearch,并展

    2024年02月11日
    浏览(48)
  • spring boot使用elasticsearch分词,排序,分页,高亮简单示例

    记,写一个简单的es分词demo,es版本6.8.12 如果使用es7有些方法可能会有所改变,请参考7的文档 es安装教程:http://t.csdn.cn/BSh12 怎么简单怎么来 商品名称加了 @Field(type = FieldType.Text, analyzer = “ik_max_word”) 会自动分词 分页处理 3.2.1 分词 当输入衣服鞋子的时候会将分

    2024年02月08日
    浏览(38)
  • 《Spring Boot 实战派》--13.集成NoSQL数据库,实现Elasticsearch和Solr搜索引擎

             关于搜索引擎 我们很难实现 Elasticseach 和 Solr两大搜索框架的效果;所以本章针对两大搜索框架,非常详细地讲解 它们的原理和具体使用方法, 首先 介绍什么是搜索引擎 、如何用 MySQL实现简单的搜索引擎,以及Elasticseach 的 概念和接口类; 然后介绍Elasticseach

    2023年04月09日
    浏览(88)
  • Spring Boot 集成 ElasticSearch:实现模糊查询、批量 CRUD、排序、分页和高亮功能

    文章来源:https://blog.csdn.net/qq_52355487/article/details/123805713 在pom.xml里加入如下依赖 非常重要:检查依赖版本是否与你当前所用的版本是否一致,如果不一致,会连接失败! 1.创建、判断存在、删除索引 2.对文档的CRUD 创建文档: 注意:如果添加时不指定文档ID,他就会随机生成

    2024年02月04日
    浏览(40)
  • spring boot 3使用 elasticsearch 提供搜索建议

    用户输入内容,快速返回建议,示例效果如下 spring boot 3 elasticsearch server 7.17.4 spring data elasticsearch 5.0.1 elasticsearch-java-api 8.5.3 为了启动时候自己创建相关的index,以及存储搜索内容 数据导入时候,因为有数据格式要求,必须使用实体类进行写入

    2024年02月12日
    浏览(38)
  • Spring boot 2.3.12集成ElasticSearch7.6.2并进行CRUD

    本篇博客主要讲解Spring boot 2.3.12集成ElasticSearch7.6.2并进行CRUD操作。其它版本的spring boot集成ElasticSearch类似,只需要具体各自的版本是否匹配。通过本篇博客能够成功集成ElasticSearch并进行CRUD操作,适合刚接触ElasticSearch需要进行简单CRUD操作的读者。 在集成ElasticSearch之前需要明

    2023年04月08日
    浏览(48)
  • 从零开始学Spring Boot系列-前言

    在数字化和信息化的时代,Java作为一种成熟、稳定且广泛应用的编程语言,已经成为构建企业级应用的首选。而在Java生态系统中,Spring框架无疑是其中最为耀眼的一颗明星。它提供了全面的编程和配置模型,用于构建企业级应用。随着Spring Boot的出现,这一框架变得更加易于

    2024年02月22日
    浏览(56)
  • Elasticsearch搜索功能的实现(五)-- 实战

    实战环境 elastic search 8.5.0 + kibna 8.5.0 + springboot 3.0.2 + spring data elasticsearch 5.0.2 + jdk 17 实现效果图片: 实际执行的DSL语句: 注意: 当指定排序条件时 _score 会被置空 加权前效果: 加权后效果: DSL 语句:

    2023年04月18日
    浏览(34)
  • Elasticsearch 全文搜索引擎 ---- IK分词器

            原理:分词的原理:二叉树                  首先讲一下为什么要出这个文章,前面我们讲过分词方法: 中文分词搜索 pscws (感兴趣的同学可以去爬楼看一下),那为什么要讲 IK分词 ?最主要的原因是:pscws分词 颗粒度 不如IK分词的颗粒度高,现在的需求

    2024年02月10日
    浏览(49)
  • Elasticsearch-queryStringQuery进行不分词搜索

    queryStringQuery 会对查询的先进行分词,然后在进行匹配。 但是如果要让它已空格分割,分割后每个词进行精确查询呢 // 一般需要精确查询的字段,在存储的时候都不建议分词。但是已经分词了,还想精确精确查询,使用queryStringQuery,在需要精确查询的词语外面使用双

    2024年02月02日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包