Elasticsearch(看这一篇就够了)

目录:

  • Elasticsearch
    • 介绍
    • 正排索引和倒排索引
    • Elasticsearch安装
      • 安装ES服务
      • 安装服务
      • 安装kibana
    • 索引操作
      • 创建索引
      • 查询索引库
      • 修改索引库
      • 删除索引库
    • Elasticsearch常用操作
      • 文档操作
        • 新增文档
        • 查询文档
        • 删除文档
        • 根据id批量查询文档
        • 查询所有文档
        • 修改文档部分字段
    • 域的属性
    • 分词器
      • 默认分词器
      • IK分词器
      • 拼音分词器
      • 自定义分词器
    • Elasticsearch搜索文档
      • 搜索方式
    • SpringDaraES案例
      • 使用Repository继承的方法查询文档
      • 使用DSL语句查询文档
      • 按照规则命名方法查询文档
      • 分页查询
      • 结果排序
      • template工具类
        • 操作索引
        • 操作文档
        • 查询文档
        • 复杂条件查询
        • 分页查询
        • 结果排序
    • Elasticsearch集群
      • 搭建集群
      • 测试集群状态
      • 故障应对和水平扩容
      • 内存设置
      • 磁盘选择
      • 分片策略
    • Elasticsearch案例
      • 需求说明
      • ES自动补全
      • 创建索引
      • mysql数据导入es
      • 项目搭建
      • 创建实体类
      • 创建Repository接口
      • 自动补全功能
      • 搜索关键字功能
      • 创建Controller类
      • 前端页面

在这里插入图片描述

Elasticsearch

介绍

在这里插入图片描述
Elasticsearch是一个全文检索服务器

全文检索是一种非结构化数据的搜索方式

  • 结构化数据:指具有固定格式固定长度的数据,如数据库中的字段。

  • 非结构化数据:指格式和长度不固定的数据,如电商网站的商品详情。

结构化数据一般存入数据库,使用sql语句即可快速查询。但由于非结构化数据的数据量大且格式不固定,我们需要采用全文检索的方式进行搜索。全文检索通过建立倒排索引加快搜索效率。

正排索引和倒排索引

索引

将数据中的一部分信息提取出来,重新组织成一定的数据结构,我们可以根据该结构进行快速搜索,这样的结构称之为索引。

索引即目录,例如字典会将字的拼音提取出来做成目录,通过目录即可快速找到字的位置。

索引分为正排索引倒排索引

正排索引(正向索引)
将文档id建立为索引,通过id快速可以快速查找数据。如数据库中的主键就会创建正排索引。
在这里插入图片描述

倒排索引(反向索引)
非结构化数据中我们往往会根据关键词查询数据。此时我们将数据中的关键词建立为索引,指向文档数据,这样的索引称为倒排索引。
在这里插入图片描述

Elasticsearch安装

安装ES服务

准备工作
1.准备一台搭载有CentOS7系统的虚拟机,使用XShell连接虚拟机

2.关闭防火墙,方便访问ES

#关闭防火墙:
systemctl stop firewalld.service


#禁止防火墙自启动:
systemctl disable firewalld.service

3.配置最大可创建文件数大小

#打开系统文件:
vim /etc/sysctl.conf


#添加以下配置:
vm.max_map_count=655360


#配置生效:
sysctl -p
  1. 由于ES不能以root用户运行,我们需要创建一个非root用户,此处创建一个名为es的用户:
#创建用户:
useradd es

安装服务

  1. 使用rz命令将linux版的ES上传至虚拟机

  2. 解压ES

#解压:
tar -zxvf elasticsearch-8.10.4-linux-x86_64.tar.gz


#重命名:
mv elasticsearch-8.10.4 elasticsearch1


#移动文件夹:
mv elasticsearch1 /usr/local/


#es用户取得该文件夹权限:
chown -R es:es /usr/local/elasticsearch1
  1. 启动ES服务:
#切换为es用户:
su es


#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/


#启动ES服务:
./elasticsearch 
  1. 当启动成功,可以看到类似以下的日志输出。首次启动Elasticsearch,默认会启用安全配置功能,启用身份认证和授权,内置超级用户elastic,并生成默认密码,此时要注意保存,否则之后启动不会再显示。
    在这里插入图片描述
# 重置默认密码:
cd /usr/local/elasticsearch1/bin/
./elasticsearch-reset-password -u elastic


# 自定义密码:
cd /usr/local/elasticsearch1/bin/
./elasticsearch-reset-password --username elastic -i
  1. 连接ES,查询ES服务是否启动成功
# 参数 --cacert指定了证书
curl --cacert /usr/local/elasticsearch1/config/certs/http_ca.crt -u elastic https://localhost:9200

安装kibana

Kibana是一款开源的数据分析和可视化平台,设计用于和Elasticsearch协作。我们可以使用Kibana对Elasticsearch索引中的数据进行搜索、查看、交互操作。

  1. 使用rz工具将Kibana压缩文件上传到Linux虚拟机

  2. 解压

tar -zxvf kibana-8.10.4-linux-x86_64.tar.gz -C /usr/local/
  1. 修改配置
# 进入Kibana解压路径
cd /usr/local/kibana-8.10.4/config

# 修改配置文件
vim kibana.yml

# 加入以下内容
# 主机IP,服务名
server.host: "虚拟机IP"
server.name: "kibana"
  1. 启动:

kibana不能以root用户运行,我们给es用户设置kibana目录的权限,并使用es用户运行kibana

# 给es用户设置kibana目录权限
chown -R es:es /usr/local/kibana-8.10.4/


# 切换为es用户
su es


# 启动kibana
cd /usr/local/kibana-8.10.4/bin/
./kibana
  1. 访问kibana:http://虚拟机IP:5601
    首次访问Kibana管理台会提示输入ES生成的token秘钥,可以在ES首次启动日志中找。

在这里插入图片描述
如果token已失效或不正确,你也可以重新生成token。

# 进入es安装目录
cd /usr/local/elasticsearch1/bin

# 重新生成kibana的token
.elasticsearch-create-enrollment-token --scope kibana

紧接着输入登录账号 elastic,密码也同样是从ES首次启动日志中找。

  1. 点击Management => Index Management可以查看es索引信息。

索引操作

创建索引

Elasticsearch是使用RESTful风格的http请求访问操作的,请求参数和返回值都是Json格式的,我们可以使用kibana发送http请求操作ES。

创建没有结构的索引
路径:ip地址:端口号/索引名

注:在kibana中所有的请求都会省略ip地址:端口号,之后的路径我们省略写ip地址:端口号

格式:

PUT /索引库名称
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...}
  }
}

基本语法:

  • 请求方式:PUT
  • 请求路径:/索引库名,可以自定义
  • 请求参数:mapping映射

格式

为索引添加结构

POST /索引名/_mapping
{
    "properties":{
        "域名1":{
            "type":域的类型,
            "store":是否存储,
            "index":是否创建索引,
      "analyzer":分词器
       },
       "域名2":{ 
            ...
        }
    }
}
PUT /索引库名称
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...}
  }
}

查询索引库

基本语法

  • 请求方式:GET
  • 请求路径:/索引库名
  • 请求参数:无

格式

GET /索引库名

修改索引库

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping

虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。

语法说明

PUT /索引库名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}

删除索引库

语法

  • 请求方式:DELETE

  • 请求路径:/索引库名

  • 请求参数:无

格式

DELETE /索引库名

Elasticsearch常用操作

文档操作

新增文档
POST /索引/_doc/[id值]
{
    "field名":field值
}

POST /索引库名/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
    // ...
}

示例:

POST /jjy/_doc/1
{
    "info": "jjy最牛啦",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

注:id值不写时自动生成文档id,id和已有id重复时修改文档

查询文档
GET /索引/_doc/id值

示例:

GET /jjy/_doc/1
删除文档
DELETE /索引/_doc/id值

示例:

DELETE /jjy/_doc/1
根据id批量查询文档
GET /索引/_mget
{
  "docs":[
     {"_id":id值},
     {"_id":id值}
   ] 
}

示例:

GET /jjy/_mget
{
  "docs":[
     {"_id":1},
     {"_id":2}
   ] 
}
查询所有文档
GET /索引/_search
{
   "query": {
     "match_all": {}
   }
}

示例:

GET /jjy/_search
{
   "query": {
     "match_all": {}
   }
}
修改文档部分字段
POST /索引/_update/1/
{ 
  "doc":{ 
    域名:值
    } 
}

示例:

POST /jjy/_update/id值/
{ 
  "doc":{ 
    info:"jjy好厉害哦"
    } 
}

注:

Elasticsearch执行删除操作时,ES先标记文档为deleted状态,而不是直接物理删除。当ES存储空间不足或工作空闲时,才会执行物理删除操作。

Elasticsearch执行修改操作时,ES不会真的修改Document中的数据,而是标记ES中原有的文档为deleted状态,再创建一个新的文档来存储数据。

域的属性

index

该域是否创建索引。只有值设置为true,才能根据该域的关键词查询文档。

// 根据关键词查询文档
GET /索引名/_search
{
    "query":{
    "term":{ 
            搜索字段: 关键字
        } 
   }
}

type

域的类型

核心类型具体类型
字符串类型text
整数类型long, integer, short, byte
浮点类型double, float
日期类型date
布尔类型boolean
数组类型array
对象类型object
不分词的字符串keyword

store
是否单独存储。如果设置为true,则该域能够单独查询。

// 单独查询某个域:
GET /索引名/_search
{
 "stored_fields": ["域名"]
}

分词器

默认分词器

ES文档的数据拆分成一个个有完整含义的关键词,并将关键词与文档对应,这样就可以通过关键词查询文档。要想正确的分词,需要选择合适的分词器。

standard analyzer:Elasticsearch默认分词器,根据空格和标点符号对英文进行分词,会进行单词的大小写转换。

  • 查看分词效果
GET /_analyze
{
    "text":测试语句, 
    "analyzer":分词器
}

IK分词器

IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。提供了两种分词算法:

  • ik_smart:最少切分

  • ik_max_word:最细粒度划分

安装IK分词器

  1. 关闭es服务

  2. 使用rz命令将ik分词器上传至虚拟机

注:ik分词器的版本要和es版本保持一致。

  1. 解压ik分词器到elasticsearch的plugins目录下

unzip elasticsearch-analysis-ik-8.10.4.zip -d /usr/local/elasticsearch1/plugins/analysis-ik

  1. 启动ES服务
su es


#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/


#启动ES服务:
./elasticsearch

测试分词器效果

GET /_analyze
{
    "text":"测试语句", 
    "analyzer":"ik_smart/ik_max_word"
}

IK分词器词典
IK分词器根据词典进行分词,词典文件在IK分词器的config目录中。

  • main.dic:IK中内置的词典。记录了IK统计的所有中文单词。

  • IKAnalyzer.cfg.xml:用于配置自定义词库。

<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext_dict">ext_dict.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords">ext_stopwords.dic</entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <!-- <entry key="remote_ext_dict">words_location</entry> -->
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

拼音分词器

拼音分词器可以将中文分成对应的全拼,全拼首字母等。

安装拼音分词器

  1. 关闭es服务

  2. 使用rz命令将拼音分词器上传至虚拟机

注:拼音分词器的版本要和es版本保持一致。

解压pinyin分词器到elasticsearch的plugins目录下

unzip elasticsearch-analysis-pinyin-8.10.4 -d /usr/local/elasticsearch1/plugins/analysis-pinyin
启动ES服务

su es

#进入ES安装文件夹:
cd /usr/local/elasticsearch1/bin/

#启动ES服务:
./elasticsearch

测试分词效果

GET /_analyze
{
    "text":测试语句, 
    "analyzer":"pinyin"
}

自定义分词器

真实开发中我们往往需要对一段内容既进行文字分词,又进行拼音分词,此时我们需要自定义ik+pinyin分词器。

创建自定义分词器
在创建索引时自定义分词器

PUT /索引名
{
  "settings" : {
    "analysis" : {
      "analyzer" : {
        "ik_pinyin" : { //自定义分词器名
          "tokenizer":"ik_max_word", // 基本分词器
          "filter":"pinyin_filter" // 配置分词器过滤
         }
       },
      "filter" : { // 分词器过滤时配置另一个分词器,相当于同时使用两个分词器
        "pinyin_filter" : { 
          "type" : "pinyin", // 另一个分词器
          // 拼音分词器的配置
          "keep_separate_first_letter" : false, // 是否分词每个字的首字母
          "keep_full_pinyin" : true, // 是否分词全拼
          "keep_original" : true, // 是否保留原始输入
          "remove_duplicated_term" : true // 是否删除重复项
         }
       }
     }
   },
  "mappings":{
    "properties":{
      "域名1":{
        "type":域的类型,
        "store":是否单独存储,
        "index":是否创建索引,
             "analyzer":分词器
       },
      "域名2":{ 
        ...
       }
     }
   }
}

测试自定义分词器

GET /索引/_analyze
{
 "text": "你好程序员",
 "analyzer": "ik_pinyin"
}

Elasticsearch搜索文档

添加一些文档数据

PUT /students
{
  "mappings":{
    "properties":{
      "id": {
            "type": "integer",
        "index": true
       },
         "name": {
            "type": "text",
            "store": true,
        "index": true,
            "analyzer": "ik_smart"
         },
      "info": {
            "type": "text",
            "store": true,
        "index": true,
            "analyzer": "ik_smart"
         }
     }
   }
}


POST /students/_doc/
{
 "id":1,
 "name":"程序员",
 "info":"I love baizhan"
}


POST /students/_doc/
{
 "id":2,
 "name":"美羊羊",
 "info":"美羊羊是羊村最漂亮的人"
}


POST /students/_doc/
{
 "id":3,
 "name":"懒羊羊",
 "info":"懒羊羊的成绩不是很好"
}


POST /students/_doc/
{
 "id":4,
 "name":"小灰灰",
 "info":"小灰灰的年纪比较小"
}


POST /students/_doc/
{
 "id":5,
 "name":"沸羊羊",
 "info":"沸羊羊喜欢美羊羊"
}


POST /students/_doc/
{
 "id":6,
 "name":"灰太狼",
 "info":"灰太狼是小灰灰的父亲,每次都会说我一定会回来的"
}

搜索方式

match_all:查询所有文档

{
    "query":{
    "match_all":{}
   }
}

match:全文检索。将查询条件分词后再进行搜索。

{
    "query":{
    "match":{
      搜索字段:搜索条件
     }
   }
}

注:在搜索时关键词有可能会输入错误,ES搜索提供了自动纠错功能,即ES的模糊查询。使用match方式可以实现模糊查询。模糊查询对中文的支持效果一般,我们使用英文数据测试模糊查询。

{
    "query": {
        "match": {
            "域名": {
            "query": 搜索条件,
            "fuzziness": 最多错误字符数,不能超过2
            }
        }
    }
}

range:范围搜索。对数字类型的字段进行范围搜索

{
    "query":{
    "range":{
      搜索字段:{ 
        "gte":最小值,
        "lte":最大值
       } 
     }
   }
}

gt/lt:大于/小于
gte/lte:大于等于/小于等于
match_phrase:短语检索。搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配。

{
    "query":{
    "match_phrase":{
      搜索字段:搜索条件
     }
   }
}

term/terms:单词/词组搜索。搜索条件不做任何分词解析,在搜索字段对应的倒排索引中精确匹配

{
    "query":{
    "term":{ 
            搜索字段: 搜索条件
     }
   }
}

{
    "query":{
    "terms":{ 
            搜索字段: [搜索条件1,搜索条件2]
     }
   }
}

复合搜索

GET /索引/_search
{ 
    "query": { 
    "bool": { 
      // 必须满足的条件 
      "must": [ 
                搜索方式:搜索参数,
                搜索方式:搜索参数
       ],
      // 多个条件有任意一个满足即可
      "should": [
                搜索方式:搜索参数,
               搜索方式:搜索参数
           ],
            // 必须不满足的条件
           "must_not":[
               搜索方式:搜索参数,
               搜索方式:搜索参数
           ]
       } 
   } 
}

结果排序

ES中默认使用相关度分数实现排序,可以通过搜索语法定制化排序。

GET /索引/_search
{ 
  "query": 搜索条件,
  "sort": [
       {
           "字段1":{
               "order":"asc"
           } 
       },
       { 
           "字段2":{ 
               "order":"desc" 
           } 
       }
   ] 
}

由于ES对text类型字段数据会做分词处理,使用哪一个单词做排序都是不合理的,所以 ES中默认不允许对text类型的字段做排序。如果需要使用字符串做结果排序,可以使用 keyword类型的字段作为排序依据,因为keyword字段不做分词处理。

高亮查询

在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮。

我们可以在关键字左右加入标签字符串,数据传入前端即可完成高亮显示,ES可以对查询出的内容中关键字部分进行标签和样式的设置。

GET /索引/_search
{ 
    "query":搜索条件,
    "highlight":{
        "fields": { 
         "高亮显示的字段名": {
        // 返回高亮数据的最大长度
             "fragment_size":100,
                // 返回结果最多可以包含几段不连续的文字
             "number_of_fragments":5
         } 
     },
    "pre_tags":["前缀"], 
    "post_tags":["后缀"]
   } 
}

SQL查询

在ES7之后,支持SQL语句查询文档:

GET /_sql?format=txt
{
    "query": SQL语句
}

开源版本的ES并不支持通过Java操作SQL进行查询,如果需要操作 SQL查询,则需要氪金(购买白金版)

SpringDaraES案例

Java原生代码可以操作Elasticsearch,但操作比较繁琐,类似于数据库中的JDBC,我们还需要将ES文档手动封装为Java对象。所以开发中我们一般使用框架操作Elasticsearch。

Spring Data ElasticSearch是JAVA操作Elasticsearch的框架。它通过对原生API的封装,使得JAVA程序员可以简单的对Elasticsearch进行操作。

使用Repository继承的方法查询文档

  1. 创建SpringBoot项目,加入Spring Data Elasticsearch起步依赖:
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <scope>provided</scope>
</dependency>
  1. 编写配置文件,连接elasticsearch
spring:
  elasticsearch:
   uris: https://192.168.0.187:9200
   username: elastic
   password: elastic

#日志格式
logging:
  pattern:
    console: '%d{HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n'
  1. 创建配置类,跳过SSL证书检查。

从ES8开始,访问ES的协议从http变成了https,访问https请求需要SSL证书,在开发环境下我们不需要配置该证书,在项目中添加一个配置类,跳过SSL证书检查即可。

@Component
public class RestClientBuilderCustomizerImpl implements RestClientBuilderCustomizer {
  @Override
  public void customize(RestClientBuilder builder) {
   }


  /**
   * 跳过SSL的证书检查
   */
  @Override
  public void customize(HttpAsyncClientBuilder builder) {
    SSLContextBuilder sscb = SSLContexts.custom();
    try {
      sscb.loadTrustMaterial((chain, authType) -> {
        return true;
       });
     } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException(e);
     } catch (KeyStoreException e) {
      throw new RuntimeException(e);
     }


    try {
      builder.setSSLContext(sscb.build());
     } catch (KeyManagementException | NoSuchAlgorithmException e) {
      e.printStackTrace();
     }
   }
}
  1. 创建实体类:
// 一个实体类的所有对象都会存入ES的一个索引中,所以我们在创建实体类时关联ES索引
@Document(indexName = "product",createIndex = true)
public class Product {
  @Id
  @Field(type = FieldType.Integer,store = true,index = true)
  private Integer id;
  @Field(type = FieldType.Text,store = true,index = true,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
  private String productName;
  @Field(type = FieldType.Text,store = true,index = true,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
  private String productDesc;
}

@Document:标记在类上,标记实体类为文档对象,一般有如下属性:

indexName:对应索引的名称

createIndex:是否自动创建索引

@Id:标记在成员变量上,标记一个字段为主键,该字段的值会同步到ES该文档的id值。

@Field:标记在成员变量上,标记为文档中的域,一般有如下属性:

type:域的类型

index:是否创建索引,默认是 true

store:是否单独存储,默认是 false

analyzer:分词器

searchAnalyzer:搜索时的分词器

  1. 创建Repository接口
// Repository接口继承ElasticsearchRepository,该接口提供了文档的增删改查方法
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product,Integer> {
}
  1. 测试Repository接口的方法
@SpringBootTest
public class ProductRepositoryTest {
  @Autowired
  private ProductRepository repository;

  @Test
  public void addProduct(){
    Product product = new Product(1, "iphone30", "iphone30是苹果最新手机");
    repository.save(product);
   }

  @Test
  public void updateProduct(){
    Product product = new Product(1, "iphone31", "iphone31是苹果最新手机");
    repository.save(product);
   }

  @Test
  public void findAllProduct(){
    Iterable<Product> all = repository.findAll();
    for (Product product : all) {
      System.out.println(product);
     }
   }

  @Test
  public void findProductById(){
    Optional<Product> product = repository.findById(1);
    System.out.println(product.get());
   }

  @Test
  public void deleteProduct(){
    repository.deleteById(1);
   }
}

接下来我们讲解SpringDataES支持的查询方式,首先准备一些文档数据:

// 添加一些数据
repository.save(new Product(2, "三体1", "三体1是优秀的科幻小说"));
repository.save(new Product(3, "三体2", "三体2是优秀的科幻小说"));
repository.save(new Product(4, "三体3", "三体3是优秀的科幻小说"));
repository.save(new Product(5, "elasticsearch", "elasticsearch是基于lucene开发的优秀的搜索引擎"));

使用DSL语句查询文档

ES通过json类型的请求体查询文档,方法如下:

GET /索引/_search
{
  "query":{
    搜索方式:搜索参数
   }
}

query后的json对象称为DSL语句,我们可以在接口方法上使用@Query注解自定义DSL语句查询。

@Query("{" +
    "   \"match\": {" +
    "    \"productDesc\": \"?0\"" +
    "   }" +
    "  }")
List<Product> findByProductDescMatch(String keyword);


@Query("{" +
    " \"match\": {" +
    "  \"productDesc\": {" +
    "   \"query\": \"?0\"," +
    "   \"fuzziness\": 1" +
    "  }" +
    " }" +
    "}")
List<Product> findByProductDescFuzzy(String keyword);

按照规则命名方法查询文档

  • 只需在Repository接口中按照一定的规则命名方法,该方法就能完成相应的查询。
  • 规则:查询方法以findBy开头,涉及查询条件时,条件的属性用条件关键字连接。
关键字命名规则解释示例
andfindByField1AndField2根据Field1和Field2 获得数据findByTitleAndContent
orfindByField1OrField2根据Field1或Field2 获得数据findByTitleOrContent
isfindByField根据Field获得数据findByTitle
notfindByFieldNot根据Field获得补集数据findByTitleNot
betweenfindByFieldBetween获得指定范围的数据findByPriceBetween
List<Product> findByProductName(String productName);
List<Product> findByProductNameOrProductDesc(String productName,String productDesc);
List<Product> findByIdBetween(Integer startId,Integer endId);

分页查询

在使用继承或自定义的方法时,在方法中添加Pageable类型的参数,返回值为Page类型即可进行分页查询。

// 测试继承的方法:
@Test
public void testFindPage(){
  // 参数1:页数,参数2:每页条数
  Pageable pageable = PageRequest.of(1, 3);
  Page<Product> page = repository.findAll(pageable);
  System.out.println("总条数"+page.getTotalElements());
  System.out.println("总页数"+page.getTotalPages());
  System.out.println("数据"+page.getContent());
}


// 自定义方法
Page<Product> findByProductDescMatch(String keyword, Pageable pageable);


// 测试自定义方法
@Test
public void testFindPage2(){
  Pageable pageable = PageRequest.of(1, 2);
  Page<Product> page = repository.findByProductDescMatch("我喜欢三体", pageable);
  System.out.println("总条数"+page.getTotalElements());
  System.out.println("总页数"+page.getTotalPages());
  System.out.println("数据"+page.getContent());
}

结果排序

使用继承或自定义的方法时,在方法中添加Sort类型的参数即可进行结果排序。

// 结果排序
@Test
public void testFindSort(){
  Sort sort = Sort.by(Sort.Direction.DESC, "id");
  Iterable<Product> all = repository.findAll(sort);
  for (Product product : all) {
    System.out.println(product);
   }
}


// 测试分页加排序
@Test
public void testFindPage2(){
  Sort sort = Sort.by(Sort.Direction.DESC,"id");
  Pageable pageable = PageRequest.of(0, 2,sort);
  Page<Product> page = repository.findByProductDescMatch("我喜欢三体", pageable);
  System.out.println("总条数"+page.getTotalElements());
  System.out.println("总页数"+page.getTotalPages());
  System.out.println("数据"+page.getContent());
}

template工具类

SpringDataElasticsearch提供了一个工具类ElasticsearchTemplate,我们使用该类对象也可以对ES进行操作。

操作索引
@SpringBootTest
public class TemplateTest {
  @Autowired
  private ElasticsearchTemplate template;


  // 新增索引
  @Test
  public void addIndex() {
    // 获得索引操作对象
    IndexOperations indexOperations = template.indexOps(Product.class);
    // 创建索引,注:该方法无法设置索引结构,不推荐使用
    indexOperations.create();
   }


  // 删除索引
  @Test
  public void delIndex() {
    // 获得索引操作对象
    IndexOperations indexOperations = template.indexOps(Product.class);
    // 删除索引
    indexOperations.delete();
   }
}

操作文档
// 新增/修改文档
@Test
public void addDocument() {
  Product product = new Product(7, "es1", "es是一款优秀的搜索引擎");
  template.save(product);
}


// 删除文档
@Test
public void delDocument() {
  template.delete("7", Product.class);
}


// 根据id查询
@Test
public void findAllDocument() {
  Product product = template.get("1", Product.class);
  System.out.println(product);
}

查询文档

template的search方法可以查询文档:

SearchHits<T> search(Query query, Class<T> clazz):查询文档,query是查询条件对象,clazz是结果类型。

用法如下:

// 查询文档2
@Test
public void searchDocument2() {
  String productName = "三体";
  String productDesc = "优秀";


  // 1.构建查询条件
  NativeQueryBuilder nativeQueryBuilder = new NativeQueryBuilder();
  // 如果没有传入参数,查询所有
  if (productName == null && productDesc == null) {
    nativeQueryBuilder.withQuery(Queries.matchAllQueryAsQuery());
   } else {
    BoolQuery.Builder boolQuery = QueryBuilders.bool();
    if (productName != null) {
      boolQuery.must(Queries.matchQueryAsQuery("productName", productName, null, null));
     }
    if (productDesc != null) {
      boolQuery.must(Queries.matchQueryAsQuery("productDesc", productDesc, null, null));
     }
    nativeQueryBuilder.withQuery(boolQuery.build()._toQuery());
   }
  NativeQuery query = nativeQueryBuilder.build();


  // 2.查询
  SearchHits<Product> result = template.search(query, Product.class);
  // 3.处理查询结果
  for (SearchHit<Product> productSearchHit : result) {
    Product product = productSearchHit.getContent();
    System.out.println(product);
   }
}
复杂条件查询
// 查询文档2
@Test
public void searchDocument2(){
  String productName = "三体";
  String productDesc = "优秀";


  // 1.构造查询条件
  NativeQueryBuilder nativeQueryBuilder = new NativeQueryBuilder();
  // 如果没有传入参数,查询所有
  if (productName == null && productDesc == null){
    nativeQueryBuilder.withQuery(Queries.matchAllQueryAsQuery());
   }else {
    BoolQuery.Builder boolQuery = QueryBuilders.bool();
    if (productName != null){
      boolQuery.must(Queries.matchQueryAsQuery("productName",productName,null,null));
     }
    if (productDesc != null){
      boolQuery.must(Queries.matchQueryAsQuery("productDesc",productDesc,null,null));
     }
    nativeQueryBuilder.withQuery(boolQuery.build()._toQuery());
   }
  NativeQuery query = nativeQueryBuilder.build();


  // 2.查询
  SearchHits<Product> result = template.search(query, Product.class);
  // 3.处理查询结果
  for (SearchHit<Product> productSearchHit : result) {
    Product product = productSearchHit.getContent();
    System.out.println(product);
   }
}

分页查询
// 分页查询文档
@Test
public void searchDocumentPage() {


  // 1.构建查询条件
  Pageable pageable = PageRequest.of(0, 3);
  NativeQuery query = new NativeQueryBuilder()
     .withQuery(Queries.matchAllQueryAsQuery())
     .withPageable(pageable)
     .build();
  // 2.查询
  SearchHits<Product> result = template.search(query, Product.class);


  // 3.处理查询结果
  List<Product> content = new ArrayList();
  for (SearchHit<Product> productSearchHit : result) {
    Product product = productSearchHit.getContent();
    content.add(product);
   }


  /**
     * 封装Page对象,参数1:具体数据,参数2:分页条件对象,参数3:总条数
     */
  Page<Product> page = new PageImpl(content, pageable, result.getTotalHits());


  System.out.println(page.getTotalElements());
  System.out.println(page.getTotalPages());
  System.out.println(page.getContent());
}

结果排序
// 结果排序
@Test
public void searchDocumentSort() {
  // 1.构建查询条件
  NativeQuery query = new NativeQueryBuilder()
     .withQuery(Queries.matchAllQueryAsQuery())
     .withSort(Sort.by(Sort.Direction.DESC, "id"))
     .build();
  // 2.查询
  SearchHits<Product> result = template.search(query, Product.class);


  // 3.处理查询结果
  for (SearchHit<Product> productSearchHit : result) {
    Product product = productSearchHit.getContent();
    System.out.println(product);
   }
}

Elasticsearch集群

在这里插入图片描述
在单台ES服务器上,随着一个索引内数据的增多,会产生存储、效率、安全等问题。

  1. 假设项目中有一个500G大小的索引,但我们只有几台200G硬盘的服务器,此时是不可能将索引放入其中某一台服务器中的。

在这里插入图片描述

  1. 此时我们需要将索引拆分成多份,分别放入不同的服务器中,此时这几台服务器维护了同一个索引,我们称这几台服务器为一个集群,其中的每一台服务器为一个节点,每一台服务器中的数据称为一个分片

在这里插入图片描述

  1. 此时如果某个节点故障,则会造成集群崩溃,所以每个节点的分片往往还会创建副本,存放在其他节点中,此时一个节点的崩溃就不会影响整个集群的正常运行。
    在这里插入图片描述
    节点(node):一个节点是集群中的一台服务器,是集群的一部分。它存储数据,参与集群的索引和搜索功能。集群中有一个为主节点,主节点通过ES内部选举产生。

集群(cluster):一组节点组织在一起称为一个集群,它们共同持有整个的数据,并一起提供索引和搜索功能。

分片(shards):ES可以把完整的索引分成多个分片,分别存储在不同的节点上。

副本(replicas):ES可以为每个分片创建副本,提高查询效率,保证在分片数据丢失后的恢复。

粗样式**

注:

分片的数量只能在索引创建时指定,索引创建后不能再更改分片数量,但可以改变副本的数量。

为保证节点发生故障后集群的正常运行,ES不会将某个分片和它的副本存在同一台节点上。

搭建集群

在这里插入图片描述

安装第一个ES节点

  1. 修改系统进程最大打开文件数
#修改系统文件
vim /etc/security/limits.conf


#添加如下内容
es soft nofile 65535
es hard nofile 131072
  1. 安装
#解压:
tar -zxvf elasticsearch-8.10.4-linux-x86_64.tar.gz


#重命名:
mv elasticsearch-8.10.4 myes1


#移动文件夹:
mv myes1 /usr/local/


#安装ik分词器
unzip elasticsearch-analysis-ik-8.10.4.zip -d /usr/local/myes1/plugins/analysis-ik


#安装拼音分词器
unzip elasticsearch-analysis-pinyin-8.10.4.zip -d /usr/local/myes1/plugins/analysis-pinyin


#es用户取得该文件夹权限:
chown -R es:es /usr/local/myes1
  1. 修改配置文件
#打开节点一配置文件:
vim /usr/local/myes1/config/elasticsearch.yml

配置如下信息:

#集群名称,保证唯一
cluster.name: my_elasticsearch
#节点名称,必须不一样
node.name: node1
#可以访问该节点的ip地址
network.host: 0.0.0.0
#该节点服务端口号
http.port: 9200
#集群间通信端口号
transport.port: 9300
#候选主节点的设备地址
discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
#候选主节点的节点名
cluster.initial_master_nodes: ["node1","node2","node3"]
#关闭安全认证
xpack.security.enabled: false
  1. 启动
#切换为es用户:
su es


#后台启动第一个节点:
ES_JAVA_OPTS="-Xms512m -Xmx512m" /usr/local/myes1/bin/elasticsearch -d

安装第二个ES节点

  1. 安装
#解压:
tar -zxvf elasticsearch-8.10.4-linux-x86_64.tar.gz

#重命名:
mv elasticsearch-8.10.4 myes2

#移动文件夹:
mv myes2 /usr/local/

#安装ik分词器
unzip elasticsearch-analysis-ik-8.10.4.zip -d /usr/local/myes2/plugins/analysis-ik

#安装拼音分词器
unzip elasticsearch-analysis-pinyin-8.10.4.zip -d /usr/local/myes2/plugins/analysis-pinyin

#es用户取得该文件夹权限:
chown -R es:es /usr/local/myes2
  1. 修改配置文件
#打开节点二配置文件:
vim /usr/local/myes2/config/elasticsearch.yml
配置如下信息:
#集群名称,保证唯一
cluster.name: my_elasticsearch
#节点名称,必须不一样
node.name: node2
#可以访问该节点的ip地址
network.host: 0.0.0.0
#该节点服务端口号
http.port: 9201
#集群间通信端口号
transport.port: 9301
#候选主节点的设备地址
discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
#候选主节点的节点名
cluster.initial_master_nodes: ["node1","node2","node3"]
#关闭安全认证
xpack.security.enabled: false
  1. 启动
#切换为es用户:
su es

#后台启动第二个节点:
ES_JAVA_OPTS="-Xms512m -Xmx512m" /usr/local/myes2/bin/elasticsearch -d

安装第三个ES节点

  1. 安装
#解压:
tar -zxvf elasticsearch-8.10.4-linux-x86_64.tar.gz

#重命名:
mv elasticsearch-8.10.4 myes3

#移动文件夹:
mv myes3 /usr/local/

#安装ik分词器
unzip elasticsearch-analysis-ik-8.10.4.zip -d /usr/local/myes3/plugins/analysis-ik

#安装拼音分词器
unzip elasticsearch-analysis-pinyin-8.10.4.zip -d /usr/local/myes3/plugins/analysis-pinyin

#es用户取得该文件夹权限:
chown -R es:es /usr/local/myes3
  1. 修改配置文件
#打开节点一配置文件:
vim /usr/local/myes3/config/elasticsearch.yml

配置如下信息:

#集群名称,保证唯一
cluster.name: my_elasticsearch
#节点名称,必须不一样
node.name: node3
#可以访问该节点的ip地址
network.host: 0.0.0.0
#该节点服务端口号
http.port: 9202
#集群间通信端口号
transport.port: 9302
#候选主节点的设备地址
discovery.seed_hosts: ["127.0.0.1:9300","127.0.0.1:9301","127.0.0.1:9302"]
#候选主节点的节点名
cluster.initial_master_nodes: ["node1","node2","node3"]
#关闭安全认证
xpack.security.enabled: false
  1. 启动
#切换为es用户:
su es

#后台启动第三个节点:
ES_JAVA_OPTS="-Xms512m -Xmx512m" /usr/local/myes3/bin/elasticsearch -d

测试集群
访问http://虚拟机IP:9200/_cat/nodes查看集群是否搭建成功。

kibana连接es集群

  1. 在kibana中访问集群
# 打开kibana配置文件
vim /usr/local/kibana-8.10.4/config/kibana.yml

添加如下配置

# 该集群的所有节点
elasticsearch.hosts: ["http://127.0.0.1:9200","http://127.0.0.1:9201","http://127.0.0.1:9202"]

启动kibana

#切换为es用户:
su es

#启动kibana:
cd /usr/local/kibana-8.10.4/bin
./kibana
  1. 访问kibana:http://虚拟机IP:5601

测试集群状态

  1. 在集群中创建一个索引
PUT /product1
{
 "settings": {
  "number_of_shards": 5, // 分片数
  "number_of_replicas": 1 // 每个分片的副本数
  },
 "mappings": {
  "properties": {
   "id": {
    "type": "integer",
    "store": true,
    "index": true
    },
   "productName": {
    "type": "text",
    "store": true,
    "index": true
    },
   "productDesc": {
    "type": "text",
    "store": true,
    "index": true
    }
   }
  }
}
  1. 查看集群状态
# 查看集群健康状态
GET /_cat/health?v
# 查看索引状态
GET /_cat/indices?v
# 查看分片状态
GET /_cat/shards?v 

故障应对和水平扩容

  1. 关闭一个节点,可以发现ES集群可以自动进行故障应对。

  2. 重新打开该节点,可以发现ES集群可以自动进行水平扩容。

  3. 分片数不能改变,但是可以改变每个分片的副本数、

PUT /索引/_settings
{
  "number_of_replicas": 副本数
}

内存设置

ES默认占用内存是4GB,我们可以修改config/jvm.option设置ES的堆内存大小,Xms表示堆内存的初始大小,Xmx表示可分配的最大内存。

  • Xmx和Xms的大小设置为相同的,可以减轻伸缩堆大小带来的压力。
  • Xmx和Xms不要超过物理内存的50%,因为ES内部的Lucene也要占据一部分物理内存。
  • Xmx和Xms不要超过32GB,由于Java语言的特性,堆内存超过32G会浪费大量系统资源,所以在内存足够的情况下,最终我们都会采用设置为31G:
-Xms 31g
-Xmx 31g

例如:在一台128GB内存的机器中,我们可以创建两个节点,每个节点分配31GB内存。

磁盘选择

ES的优化即通过调整参数使得读写性能更快

磁盘通常是服务器的瓶颈。Elasticsearch重度使用磁盘,磁盘的效率越高,Elasticsearch的执行效率就越高。这里有一些优化磁盘的技巧:

  • 使用SSD(固态硬盘),它比机械磁盘优秀多了。
  • 使用RAID0模式(将连续的数据分散到多个硬盘存储,这样可以并行进行IO操作),代价是一块硬盘发生故障就会引发系统故障。
  • 不要使用远程挂载的存储。

分片策略

分片和副本数并不是越多越好。每个分片的底层都是一个Lucene索引,会消耗一定的系统资源。且搜索请求需要命中索引中的所有分片,分片数过多会降低搜索性能。索引的分片数需要架构师和技术人员对业务的增长有预先的判断,一般来说我们遵循以下原则:

  • 每个分片占用的硬盘容量不超过ES的最大JVM的堆空间设置(一般设置不超过32G)。比如:如果索引的总容量在500G左右,那分片数量在16个左右即可。

  • 分片数一般不超过节点数的3倍。比如:如果集群内有10个节点,则分片数不超过30个。

  • 推迟分片分配:节点中断后集群会重新分配分片。但默认集群会等待一分钟来查看节点是否重新加入。我们可以设置等待的时长,减少重新分配的次数:

PUT /索引/_settings
{
  "settings":{
    "index.unassianed.node_left.delayed_timeout":"5m"
   }
}
  • 减少副本数量:进行写入操作时,需要把写入的数据都同步到副本,副本越多写入的效率就越慢。我们进行大批量进行写入操作时可以先设置副本数为0,写入完成后再修改回正常的状态。

Elasticsearch案例

需求说明

使用ES模仿百度搜索,即自动补全+搜索引擎效果:

ES自动补全

GET /索引/_search
{
  "suggest": {
    "prefix_suggestion": {// 自定义推荐名
      "prefix": "elastic",// 被补全的关键字
      "completion": {
        "field": "productName",// 查询的域
        "skip_duplicates": true, // 忽略重复结果
        "size": 10 //最多查询到的结果数
       }
     }
   }
}

注:自动补全对性能要求极高,ES不是通过倒排索引来实现的,所以需要将对应的查询字段类型设置为completion。

PUT /product2
{
  "mappings":{
    "properties":{
      "id":{
        "type":"integer",
        "store":true,
        "index":true
       },
      "productName":{ 
        "type":"completion"
       },
      "productDesc":{ 
        "type":"text",
        "store":true,
        "index":true
       }
     }
   }
}


POST /product2/_doc
{
  "id":1,
  "productName":"elasticsearch1",
  "productDesc":"elasticsearch1 is a good search engine"
}


POST /product2/_doc
{
  "id":2,
  "productName":"elasticsearch2",
  "productDesc":"elasticsearch2 is a good search engine"
}


POST /product2/_doc
{
  "id":3,
  "productName":"elasticsearch3",
  "productDesc":"elasticsearch3 is a good search engine"
}

创建索引

PUT /news
{
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1,
    "analysis": {
      "analyzer": {
        "ik_pinyin": {
          "tokenizer": "ik_smart",
          "filter": "pinyin_filter"
         },
        "tag_pinyin": {
          "tokenizer": "keyword",
          "filter": "pinyin_filter"
         }
       },
      "filter": {
        "pinyin_filter": {
          "type": "pinyin",
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "remove_duplicated_term": true
         }
       }
     }
   },
  "mappings": {
    "properties": {
      "id": {
        "type": "integer",
        "index": true
       },
      "title": {
        "type": "text",
        "index": true,
        "analyzer": "ik_pinyin",
        "search_analyzer": "ik_smart"
       },
      "content": {
        "type": "text",
        "index": true,
        "analyzer": "ik_pinyin",
        "search_analyzer": "ik_smart"
       },
      "url": {
        "type": "keyword",
        "index": true
       },
      "tags": {
        "type": "completion",
        "analyzer": "tag_pinyin",
        "search_analyzer": "tag_pinyin"
       }
     }
   }
}

mysql数据导入es

使用logstash工具可以将mysql数据同步到es中:

  1. 解压logstash-8.10.4-windows-x86_64

logstash要和elastisearch版本一致

  1. 在logstash解压路径下的/config中创建mysql.conf文件,文件写入以下脚本内容:
input {
   jdbc {
     jdbc_driver_library => "F:\Elasticsearch8\mysql-connector-java-5.1.37-bin.jar"
     jdbc_driver_class => "com.mysql.jdbc.Driver"
     jdbc_connection_string => "jdbc:mysql:///news"
     jdbc_user => "root"
     jdbc_password => "root"
     schedule => "* * * * *"
     jdbc_default_timezone => "Asia/Shanghai"
     statement => "SELECT * FROM news;"
   }
}


filter {
    mutate {
        split => {"tags" => ","}
    }
}


output {
   elasticsearch {
        hosts => ["http://192.168.0.187:9200","http://192.168.0.187:9201","http://192.168.0.187:9202"]
     index => "news"
       document_id => "%{id}"
   }
}
  1. 在解压路径下打开cmd黑窗口,运行命令:
bin\logstash -f config\mysql.conf
  1. 测试自动补齐
GET /news/_search
{
  "suggest": {
    "my_suggest": {
      "prefix": "li",
      "completion": {
        "field": "tags",
        "skip_duplicates": true,
        "size": 10
       }
     }
   }
}

项目搭建

创建Springboot项目,加入SpringDataElasticsearch和SpringMVC的起步依赖

<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>

写配置文件:

# 连接elasticsearch
spring:
  elasticsearch:
   uris: 192.168.0.187:9200,192.168.0.187:9201,192.168.0.187:9202


# 日志格式
logging:
  pattern:
  console: '%d{HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n'

创建实体类

@Document(indexName = "news")
@Data
public class News {
  @Id
  @Field
  private Integer id;
  @Field
  private String title;
  @Field
  private String content;
  @Field
  private String url;
  @CompletionField
  @Transient
  private Completion tags;
}

创建Repository接口

@Repository
public interface NewsRepository extends ElasticsearchRepository<News, Integer> {
  
}

自动补全功能

@Service
public class NewsService {
  @Autowired
  private ElasticsearchClient client;
 
  // 自动补齐
  public List<String> autoSuggest(String keyword) throws IOException {
    // 1.自动补齐查询条件
    Suggester suggester = Suggester.of(
        s -> s.suggesters("prefix_suggestion", FieldSuggester.of(
            fs -> fs.completion(
                cs -> cs.skipDuplicates(true)
                     .size(10)
                     .field("tags")


             )
         )).text(keyword)
     );
    // 2.自动补齐查询
    SearchResponse<Map> response = client.search(s -> s.index("news")
         .suggest(suggester), Map.class);


    // 3.处理查询结果
    Map resultMap = response.suggest();
    List<Suggestion> suggestionList = (List) resultMap.get("prefix_suggestion");
    Suggestion suggestion = suggestionList.get(0);
    List<CompletionSuggestOption> resultList = suggestion.completion().options();


    List<String> result = new ArrayList<>();
    for (CompletionSuggestOption completionSuggestOption : resultList) {
      String text = completionSuggestOption.text();
      result.add(text);
     }
    return result;
   }
}

搜索关键字功能

在repository接口中添加高亮搜索关键字方法

// 高亮搜索关键字
@Highlight(fields = {@HighlightField(name = "title"), @HighlightField(name = "content")})
List<SearchHit<News>> findByTitleMatchesOrContentMatches(String title, String content);

service类中调用该方法

// 查询关键字
public List<News> highLightSearch(String keyword){
  List<SearchHit<News>> result = repository.findByTitleMatchesOrContentMatches(keyword, keyword);
  // 处理结果,封装为News类型的集合
  List<News> newsList = new ArrayList();
  for (SearchHit<News> newsSearchHit : result) {
    News news = newsSearchHit.getContent();
    // 高亮字段
    Map<String, List<String>> highlightFields = newsSearchHit.getHighlightFields();
    if (highlightFields.get("title") != null){
      news.setTitle(highlightFields.get("title").get(0));
     }
    if (highlightFields.get("content") != null){
      news.setContent(highlightFields.get("content").get(0));
     }
    newsList.add(news);
   }
  return newsList;
}

创建Controller类

@RestController
public class NewsController {
  @Autowired
  private NewsService newsService;
  
  @GetMapping("/autoSuggest")
  public List<String> autoSuggest(String term){ // 前端使用jqueryUI,发送的参数默认名为term
    return newsService.autoSuggest(term);
   }
  
  @GetMapping("/highLightSearch")
  public List<News> highLightSearch(String term){
    return newsService.highLightSearch(term);
   }
}

前端页面

我们使用jqueryUI中的autocomplete插件完成项目的前端实现。

<script>
  $("#newsTag").autocomplete({
    source: "/autoSuggest", // 请求路径
    delay: 100, //请求延迟
    minLength: 1 //最少输入多少字符像服务器发送请求
   })


  function search() {
    var term = $("#newsTag").val();
    $.get("/highLightSearch", {term: term}, function (data) {
      var str = "";
      for (var i = 0; i < data.length; i++) {
        var document = data[i];
        str += "<li>" +
          "    <h4>" +
          "      <a href='" + document.url + "' target='_blank'>" + document.title + "</a>" +
          "    </h4> " +
          "    <p>" + document.content + "</p>" +
          "  </li>";
       }
      $("#news").html(str);
     })
   }
</script>

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/950619.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式技术之Linux(Ubuntu) 一

一、Linux入门 1.硬件和操作系统以及用户的关系 一个传感器&#xff0c;获得数据后&#xff0c;需要向服务器发送数据。传感器传数据给上位机。 上位机需要一个程序来接收数据&#xff0c;那么这个上位机是什么机器&#xff1f; 我们的笔记本电脑就可以当成上位机。 两个手…

【实用技能】如何使用 .NET C# 中的 Azure Key Vault 中的 PFX 证书对 PDF 文档进行签名

TX Text Control 是一款功能类似于 MS Word 的文字处理控件&#xff0c;包括文档创建、编辑、打印、邮件合并、格式转换、拆分合并、导入导出、批量生成等功能。广泛应用于企业文档管理&#xff0c;网站内容发布&#xff0c;电子病历中病案模板创建、病历书写、修改历史、连续打…

oracle闪回恢复数据:(闪回查询,闪回表,闪回库,回收站恢复)

oracle的闪回查询&#xff0c;可以查询提交在表空间的闪回数据&#xff0c;并可以还原所查询的数据&#xff0c;用于恢复短时间内的delele 或者 update 误操作&#xff0c;非常方便&#xff0c;缺点是只能恢复大概几小时内的数据。 文章目录 概要闪回查询恢复数据的主要方法包括…

开放词汇检测新晋SOTA:地瓜机器人开源DOSOD实时检测算法

在计算机视觉领域&#xff0c;目标检测是一项关键技术&#xff0c;旨在识别图像或视频中感兴趣物体的位置与类别。传统的闭集检测长期占据主导地位&#xff0c;但近年来&#xff0c;开放词汇检测&#xff08;Open-Vocabulary Object Detection-OVOD 或者 Open-Set Object Detec…

【网络协议】静态路由详解

网络中的路由器通过以下两种方式之一发现远程网络&#xff1a; 静态配置路由动态路由协议 在本文&#xff0c;我们将学习关于静态路由的各种概念&#xff0c;例如如何配置静态路由、路由表如何进行决策、路由接口等相关知识。 文章目录 引言直连网络静态路由路由表原则原则1原…

(长期更新)《零基础入门 ArcGIS(ArcScene) 》实验七----城市三维建模与分析(超超超详细!!!)

城市三维建模与分析 三维城市模型已经成为一种非常普遍的地理空间数据资源,成为城市的必需品,对城市能化管理至关重要。语义信息丰富的三维城市模型可以有效实现不同领域数据与IS相信息的高层次集成及互操作,从而在城市规划、环境模拟、应急响应和辅助决策等众多领域公挥作用、…

计算机网络--路由器问题

一、路由器问题 1.计算下一跳 计算机网络--根据IP地址和路由表计算下一跳-CSDN博客 2.更新路由表 计算机网络--路由表的更新-CSDN博客 3.根据题目要求给出路由表 4.路由器收到某个分组&#xff0c;解释这个分组是如何被转发的 5.转发分组之路由器的选择 二、举个例子 …

通过Android Studio修改第三方jar包并重新生成jar包

最近接手了来自公司其他同事的一个Unity项目,里面有一个封装的jar包要改动一下,无奈关于这个jar包的原工程文件丢失了,于是自己动手来修改下jar包,并做下记录。 一、导入第三方jar包 1、新建项目EditJarDemo(项目名随便取) 2、新建libs文件夹,把你要修改的third.jar 复制…

33.3K 的Freqtrade:开启加密货币自动化交易之旅

“ 如何更高效、智能地进行交易成为众多投资者关注的焦点。” Freqtrade 是一款用 Python 编写的免费开源加密货币交易机器人。它就像一位不知疲倦的智能交易助手&#xff0c;能够连接到众多主流加密货币交易所&#xff0c;如 Binance、Bitmart、Bybit 等&#xff08;支…

计算机网络 (26)互联网的路由选择协议

一、路由选择协议的基本概念 路由选择协议是计算机网络中用于确定数据包在网络中传输路径的一种协议。它帮助路由器构建和维护路由表&#xff0c;以便根据目的地址将数据包转发到正确的下一跳路由器。路由选择协议分为静态路由选择协议和动态路由选择协议两大类。 二、静态路由…

【MySQL实战】Centos安装MySQL

在CentOS上安装MySQL以及进行性能分析&#xff1a;2种方式&#xff0c;第一种直接装&#xff1b;第二种用docker安装&#xff1a; 直接安装MySQL 首先&#xff0c;更新系统软件包列表&#xff1a; sudo yum update然后&#xff0c;安装MySQL服务器&#xff1a; sudo yum in…

centOS7

特殊权限 set_uid 赋予所有者身份 chmod us 文件 set_gid 赋予所有组身份 chmod gs 文件/目录 sticky_bit 防火墙 firewall-cmd 开启端口 firewall-cmd --zonepublic --add-port8080/tcp --permanent 重启防火墙 systemctl restart firewalld 查看开启的所有端口 fi…

Java后端开发单元测试

测试概览 测试是用于促进鉴定软件正确性、完整性、安全性和软件质量的过程。在开发的过程中测试是必不可少的&#xff0c;测试一般分为四个阶段&#xff1a;单元测试&#xff0c;集成测试&#xff0c;系统测试&#xff0c;验收测试&#xff1b;对于后端开发人员而言&#xff0…

LAMP搭建

LAMP搭建 引子&#xff1a;本篇文章为LAMP的搭建流程&#xff0c;其中L&#xff08;Ubuntu&#xff09;、A&#xff08;Apache&#xff09;、M&#xff08;Mysql&#xff09;、P&#xff08;PHP&#xff09;。 一、L → Ubuntu Step 1&#xff1a;在Vmware Workstation中使…

LabVIEW 系统诊断

LabVIEW 系统诊断是指通过各种工具和方法检测、评估、分析和解决 LabVIEW 程序和硬件系统中可能存在的故障和性能问题。系统诊断不仅涵盖软件层面的调试与优化&#xff0c;还包括硬件交互、数据传输、实时性能等方面的检查和分析。一个成功的系统诊断能够显著提升LabVIEW应用程…

基于 GEE 提取白莲种植范围

目录 1 方法原理 1.1 步骤一 1.2 步骤二 1.3 步骤三 1.4 步骤四 2 完整代码 3 运行结果 近年来&#xff0c;随着乡村振兴战略的提出&#xff0c;我国的农业种植模式呈现出多元化的趋势。白莲具有易种植、经济效益高的特点&#xff0c;由此被广泛种植&#xff0c;本文介绍…

el-table 自定义表头颜色

第一种方法&#xff1a;计算属性 <template><div><el-table:data"formData.detail"border stripehighlight-current-row:cell-style"{ text-align: center }":header-cell-style"headerCellStyle"><el-table-column fixed…

c++类和对象---上

文章目录 类的介绍 类的声明 1.1 类名 1.2 成员变量 1.3 成员函数 1.4 访问权限 类的定义 2.1 成员变量的初始化 2.2 成员函数的实现 对象的创建和销毁 3.1 默认构造函数 3.2 析构函数 3.3 拷贝构造函数 3.4 对象的实例化 3.5 对象的销毁 成员访问控制 4.1 公有成员 4.2 私有…

上汽乘用车研发流程

目的 最近刚入职主机厂&#xff0c;工作中所提到各个阶段名称与之前在供应商那边不一致&#xff0c;概念有点模糊&#xff0c;所以打算学习了解一番 概念 术语 EP: enginerring prototype car 工程样车 Mule Car: 骡子车 Simulator Car&#xff1a;模拟样车 PPV&#xff1a;…

阿里云发现后门webshell,怎么处理,怎么解决?

当收到如下阿里云通知邮件时&#xff0c;大部分管理员都会心里一惊吧&#xff01;出现Webshell&#xff0c;大概是网站被入侵了。 尊敬的 xxxaliyun.com&#xff1a; 云盾云安全中心检测到您的服务器&#xff1a;47.108.x.xx&#xff08;xx机&#xff09;出现了紧急安全事件…