springboot整合es进行搜索

1.Elasticsearch 安装

1.1 下载软件

  • Elasticsearch 的官方地址:https://www.elastic.co/cn/
  • 这里选择下载 7.8.0 版本
  • 下载地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch

image-20230317165749813

Elasticsearch 分为 Linux 和 Windows 版本,这里我们只是做测试,因此下载 windows 版本即可。

image-20230317165814344

1.2 安装软件

Windows 版的 Elasticsearch 的安装很简单,解压即安装完毕,解压后的 Elasticsearch 的目录结构如下:

image-20230317170403908

目录含义
bin可执行脚本目录
config配置目录
jdk内置JDK目录
lib类库
logs日志目录
modules模块目录
plugins插件目录

1.3 安装 ik 中文分词器

🏠 下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.8.0

  • ik_max_word:会将文本做最细粒度的拆分
  • ik_smart:会将文本做最粗粒度的拆分

将解压后的后的文件夹放入 ES 根目录下的 plugins 目录下。

image-20230322192015954

1.4 启动ES

解压后,进入 bin 文件目录,点击 elasticsearch.bat 文件启动 ES 服务:

image-20230322183001293

image-20230317172316299

⚠️ 注意:

  • 9300 端口为 Elasticsearch 集群间组件的通信端口。
  • 9200 端口为浏览器访问的 http 协议 RESTful 端口。

💻 浏览器访问地址:http://localhost:9200

image-20230317172443120

1.5 问题解决

  • Elasticsearch 是使用 java 开发的,且 7.8 版本的 ES 需要 JDK 版本 1.8 以上,默认安装包带有 jdk 环境,如果系统配置 JAVA_HOME,那么使用系统默认的 JDK,如果没有配置使用自带的 JDK,一般建议使用系统配置的 JDK。

  • 双击启动窗口闪退,通过路径访问追踪错误,如果是“空间不足”,请修改 config/jvm.options 配置文件

    # 设置 JVM 初始内存为 1G。此值可以设置与-Xmx 相同,以避免每次垃圾回收完成后 JVM 重新分配内存
    # Xms represents the initial size of total heap space
    # 设置 JVM 最大可用内存为 1G
    # Xmx represents the maximum size of total heap space
    
    -Xms1g
    -Xmx1g
    

2.Kibana 安装 & 使用

Elasticsearch 的开源分析可视化工具,与存储在 Elasticsearch 中的数据进行交互。

image-20230320182508471

2.1 下载软件

Elasticsearch 下载的版本是 7.8.0,这里我们选择同样的 7.8.0 版本

下载地址:https://www.elastic.co/cn/downloads/past-releases#kibana

image-20230322182847242

image-20230322182812257

2.2 安装软件

解压即安装完毕,解压后的 Kibana 的目录结构如下:

image-20230322185410540

修改配置文件:kibana.yml

image-20230322185442600

添加如下信息:

# 服务端口
server.port: 5601
# 国际化 - 中文
i18n.locale: "zh-CN"
# ES 服务主机地址
elasticsearch.hosts: ["http://localhost:9200"]

server.ssl.enabled: false

2.3 启动 Kibana

image-20230322191530313

image-20230322191802656

访问浏览器:http://localhost:5601/

image-20230322191832005

3.实战开发-前提准备

3.1 开发背景

假设有一个博客模块,即有一个博客圈,用户可以通过搜索来获取与自己输入的关键字相关的数据列表,类似于CSDN的搜索。

由于只是 demo,因此这里会设计的很简单。

  • 不支持通过作者进行搜索。

3.2 数据库表设计

# 如果存在 es_demo 数据库则删除
DROP DATABASE IF EXISTS `es_demo`;
# 创建新数据库
CREATE DATABASE `es_demo`;

# 创建一张博客表
CREATE TABLE `blog`(
	`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键id',
	`user_id` BIGINT NOT NULL COMMENT '用户id(雪花算法生成)',
	`title` VARCHAR(255) NOT NULL COMMENT '标题',
	`tags` VARCHAR(64) NOT NULL COMMENT '标签',
	`introduce` VARCHAR(512) NOT NULL COMMENT '介绍',
	`content` TEXT NOT NULL COMMENT '文章内容', 
	`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
	`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
	PRIMARY KEY(`id`),
	KEY `idx_user_create`(`user_id`,`create_time` DESC)
)ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='博客信息表';

3.3 创建es索引与映射

PUT /blog
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "userId": {
        "type": "long"
      },
      "title": {
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart",
        "type": "text"
      },
      "tags": {
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart",
        "type": "text"
      },
      "introduce":{
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart",
        "type": "text"
      },
      "content":{
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart",
        "type": "text"
      },
      "createTime":{
        "format": "yyyy-MM-dd HH:mm:ss",
        "type": "date"
      },
      "updateTime":{
        "format": "yyyy-MM-dd HH:mm:ss",
        "type": "date"
      }
    }
  }
}

image-20230322210329240

3.4 es文档数据

其实按照正常的逻辑,应当在往数据库表添加记录后也写入到es中,可以使用java代码实现。但这里为了简单起见,我们直接往es中添加数据进行测试。

POST /blog/_doc/1000
{
  "id":1000,
  "userId":1626989073847750657,
  "title":"Java语言",
  "introduce":"Java的起源",
  "content":"Java最初是由任职于太阳微系统的詹姆斯-高斯林(James-Gosling)等人于1990年代初开发。最初被命名为Oak;当时发现Oak被其他公司注册了,不得不重新起名,当时他们正在咖啡馆喝着印尼爪哇(JAVA)岛出产的咖啡,有人提议就叫JAVA怎么样,然后就这样定下来了。",
  "tags":"编程,java,语言",
  "createTime":"2023-03-23 00:40:20",
  "updateTIme":"2023-03-23 00:40:20"
}

POST /blog/_doc/1001
{
  "id":1001,
  "userId":1626989073847750657,
  "title":"C语言",
  "introduce":"C的起源",
  "content":"C语言是由美国贝尔实验室的Dennis Ritchie于1972年设计发明的,最初在UNIX操作系统的DEC PDP-11计算机上使用。它由早期的编程语言BCPL(Basic Combind Programming Language)发展演变而来。",
  "tags":"编程,C,语言",
  "createTime":"2023-03-23 00:40:20",
  "updateTIme":"2023-03-23 00:40:20"
}

POST /blog/_doc/1002
{
  "id":1002,
  "userId":1626989073847750657,
  "title":"C与JAVA语言",
  "introduce":"c和java关系",
  "content":"C语言和Java都是计算机编程语言,但它们有很多不同之处。C语言是一种面向过程的编程语言,而Java是一种面向对象的编程语言。",
  "tags":"C,java,语言",
  "createTime":"2023-03-23 00:40:20",
  "updateTIme":"2023-03-23 00:40:20"
}

4.实战开发-后端代码

4.1 pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.fox</groupId>
    <artifactId>elasticsearch-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.8.0</version>
        </dependency>
        <!--fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.33</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.7</version>
        </dependency>
    </dependencies>


</project>

4.2 application.yml配置

server:
  # 服务端口
  port: 9999
elasticsearch:
  # es访问ip
  hostname: 127.0.0.1
  # es访问port
  port: 9200
  blog:
    # 访问索引
    index: blog
    # 搜索返回字段
    source_fields: id,userId,title,introduce,createTime

4.3 entity包

4.3.1 Result.java

package com.fox.es.entity;

import lombok.Data;

/**
 * 统一返回对象
 * 
 * @author 狐狸半面添
 * @create 2023-03-22 18:34
 */
@Data
public class Result {
    private Integer code;
    private String msg;
    private Object data;

    private Result(Integer code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    private Result(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public static Result ok() {
        return new Result(200, "success");
    }

    public static Result ok(Object data) {
        return new Result(200, "success", data);
    }

    public static Result error(String msg) {
        return new Result(500, msg);
    }

    public Result error(Integer code, String msg) {
        return new Result(code, msg);
    }

}

4.3.2 JacksonObjectMapper.java

package com.fox.es.entity;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 *
 * @author 狐狸半面添
 * @create 2023-01-18 20:34
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

4.4 config包

4.4.1 ElasticsearchConfig.java

package com.fox.es.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 狐狸半面添
 * @create 2023-03-22 17:51
 */
@Configuration
public class ElasticsearchConfig {
    @Value("${elasticsearch.hostname}")
    private String hostname;
    @Value("${elasticsearch.port}")
    private Integer port;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestClientBuilder builder = RestClient.builder(
                new HttpHost(hostname, port, "http")
        );
        return new RestHighLevelClient(builder);
    }
}

4.4.2 WebMvcConfig.java

package com.fox.es.config;

import com.fox.es.entity.JacksonObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

/**
 * @author 狐狸半面添
 * @create 2023-02-11 23:25
 */

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        converters.add(0, messageConverter);
    }
}

4.5 BlogSimpleInfoDTO.java

package com.fox.es.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * @author 狐狸半面添
 * @create 2023-03-22 21:33
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BlogSimpleInfoDTO {
    /**
     * 主键id
     */
    private Integer id;
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 标题
     */
    private String title;
    /**
     * 介绍
     */
    private String introduce;
    /**
     * 创建时间
     */
    private LocalDateTime createTime;

}

4.5 主启动类

package com.fox.es;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author 狐狸半面添
 * @create 2023-03-22 18:07
 */
@SpringBootApplication
public class ElasticsearchDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(ElasticsearchDemoApplication.class, args);
    }
}

4.6 ⭐测试是否连接 es 成功

package com.fox.es.controller;

import com.fox.es.entity.Result;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.MainResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.IOException;

/**
 * @author 狐狸半面添
 * @create 2023-03-22 18:33
 */
@RestController
public class TestController {
    @Resource
    private RestHighLevelClient restHighLevelClient;

    /**
     * 用于测试是否连接 es 成功
     *
     * @return 返回 es 的基本信息,等价于访问:http://127.0.0.1:9200
     * @throws IOException 异常信息
     */
    @GetMapping("/getEsInfo")
    public Result getEsInfo() throws IOException {
        MainResponse info = restHighLevelClient.info(RequestOptions.DEFAULT);
        return Result.ok(info);
    }
}

浏览器访问:http://localhost:9999/getEsInfo

image-20230323013746988

4.7 ⭐搜索服务

4.7.1 controller层

package com.fox.es.controller;

import com.fox.es.entity.Result;
import com.fox.es.service.ShareResourceSearchService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author 狐狸半面添
 * @create 2023-03-22 20:16
 */
@RestController
@RequestMapping("/search")
public class ShareResourceSearchController {

    @Resource
    private ShareResourceSearchService shareResourceSearchService;

    /**
     * 通过关键词获取数据列表
     *
     * @param keyWords 关键词
     * @param pageNo   页码
     * @param pageSize 每页大小
     * @return 数据列表,按照相关性从高到低进行排序
     */
    @GetMapping("/list")
    public Result list(@RequestParam("keyWords") String keyWords,
                       @RequestParam("pageNo") Integer pageNo,
                       @RequestParam("pageSize") Integer pageSize) {
        return shareResourceSearchService.list(keyWords, pageNo, pageSize);
    }
}

4.7.2 service接口层

package com.fox.es.service;

import com.fox.es.entity.Result;

/**
 * @author 狐狸半面添
 * @create 2023-03-22 20:18
 */
public interface ShareResourceSearchService {

    /**
     * 通过关键词获取数据列表
     *
     * @param keyWords 关键词
     * @param pageNo 页码
     * @param pageSize 每页大小
     * @return 数据列表,按照相关性从高到低进行排序
     */
    Result list(String keyWords, int pageNo, int pageSize);
}

4.7.3 service实现层

package com.fox.es.service.impl;

import com.alibaba.fastjson.JSON;
import com.fox.es.dto.BlogSimpleInfoDTO;
import com.fox.es.entity.Result;
import com.fox.es.service.ShareResourceSearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 狐狸半面添
 * @create 2023-03-22 20:18
 */
@Slf4j
@Service
public class ShareResourceSearchServiceImpl implements ShareResourceSearchService {
    @Resource
    private RestHighLevelClient restHighLevelClient;
    @Value("${elasticsearch.blog.index}")
    private String blogIndexStore;
    @Value("${elasticsearch.blog.source_fields}")
    private String blogFields;


    public Result list(String keyWords, int pageNo, int pageSize) {
        // 1.设置索引 - blog
        SearchRequest searchRequest = new SearchRequest(blogIndexStore);

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 2.source源字段过虑
        String[] sourceFieldsArray = blogFields.split(",");
        searchSourceBuilder.fetchSource(sourceFieldsArray, new String[]{});

        // 3.关键字
        if (StringUtils.hasText(keyWords)) {
            // 哪些字段匹配关键字
            MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(keyWords, "title", "tags", "introduce", "content");
            // 设置匹配占比(表示最少匹配的子句个数,例如有五个可选子句,最少的匹配个数为5*70%=3.5.向下取整为3,这就表示五个子句最少要匹配其中三个才能查到)
            multiMatchQueryBuilder.minimumShouldMatch("70%");
            // 提升字段的Boost值
            multiMatchQueryBuilder.field("title", 10);
            multiMatchQueryBuilder.field("tags", 7);
            multiMatchQueryBuilder.field("introduce", 5);

            boolQueryBuilder.must(multiMatchQueryBuilder);
        }

        // 4.分页
        int start = (pageNo - 1) * pageSize;
        searchSourceBuilder.from(start);
        searchSourceBuilder.size(pageSize);

        // 布尔查询
        searchSourceBuilder.query(boolQueryBuilder);

        // 6.高亮设置
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<font color='red'>");
        highlightBuilder.postTags("</font>");
        // 设置高亮字段
        ArrayList<HighlightBuilder.Field> fields = new ArrayList<>();
        fields.add(new HighlightBuilder.Field("title"));
        fields.add(new HighlightBuilder.Field("introduce"));
        highlightBuilder.fields().addAll(fields);
        searchSourceBuilder.highlighter(highlightBuilder);

        // 请求搜索
        searchRequest.source(searchSourceBuilder);

        SearchResponse searchResponse;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("博客搜索异常:{}", e.getMessage());
            return Result.error(e.getMessage());
        }

        // 结果集处理
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        // 记录总数
        long totalHitsCount = hits.getTotalHits().value;
        // 数据列表
        List<BlogSimpleInfoDTO> list = new ArrayList<>();

        for (SearchHit hit : searchHits) {

            String sourceAsString = hit.getSourceAsString();
            // json 转 对象
            BlogSimpleInfoDTO blog = JSON.parseObject(sourceAsString, BlogSimpleInfoDTO.class);

            // 取出高亮字段内容
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields != null) {
                blog.setTitle(parseHighlightStr(blog.getTitle(), highlightFields.get("title")));
                blog.setIntroduce(parseHighlightStr(blog.getIntroduce(), highlightFields.get("introduce")));
            }

            list.add(blog);
        }

        // 封装信息返回前端
        HashMap<String, Object> resultMap = new HashMap<>(4);
        // 页码
        resultMap.put("pageNo", pageNo);
        // 每页记录数量
        resultMap.put("pageSize", pageSize);
        // 总记录数
        resultMap.put("total", totalHitsCount);
        // 该页信息
        resultMap.put("items", list);

        return Result.ok(resultMap);

    }

    public String parseHighlightStr(String text, HighlightField field) {
        if (field != null) {
            Text[] fragments = field.getFragments();
            StringBuilder stringBuilder = new StringBuilder();
            for (Text str : fragments) {
                stringBuilder.append(str.string());
            }
            return stringBuilder.toString();
        } else {
            return text;
        }
    }
}

这里我们使用 apipost7 进行测试:

image-20230323014222816

image-20230323014418837

5.源码获取

这个demo如果你按照我的前三步进行了执行,那么直接运行源码应该是没有问题的。

源码地址:Mr-Write/SpringbootDemo: 各种demo案例 (github.com)

对应的是 elasticsearch-demo 包模块。

6.存在的问题与进阶说明

我们还存在另外一个需要解决的重要问题:与MySQL的数据一致性问题

❓ 为什么不直接使用ES存储所有的项目数据呢?

  • 非关系型表达,大量的反范式存储,导致维护基础数据异常(新增异常,修改异常,删除异常),需要MySQL做托底。
  • 不支持事务,一致性需要自己维护。
  • 从存储的角度来说,Elasticsearch是基于“字段”的,大行超多字段性能并不好。

数据同步的方式有很多种,比如通过ES的API进行增删改查,或者通过中间件像canalMQ进行数据全量、增量的同步。

image-20230323095942776
具体解决方案,可参考:docker环境安装mysql、canal、elasticsearch,基于binlog利用canal实现mysql的数据同步到elasticsearch中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是谢添啊

感谢你的支持,我会继续加油的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值