ElasticSearch


一、Elasticsearch

1、是什么

The Elastic Stack, 包括 Elasticsearch、Kibana、Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。Elaticsearch,简称为 ES, ES 是一个开源的高扩展的分布式全文搜索引擎,是整个 Elastic Stack 技术栈的核心。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据。

2、全文搜索引擎

​ Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。

​ 一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。

​ 基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差的:
 搜索的数据对象是大量的非结构化的文本数据。
 文件记录量达到数十万或数百万个甚至更多。
 支持大量基于交互式文本的查询。
 需求非常灵活的全文搜索查询。
 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足

​ 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。 为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全文搜索引擎

​ 这里说到的全文搜索引擎指的是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

二、EL安装

Elasticsearch 的官方地址:https://www.elastic.co/cn/
Elasticsearch 最新的版本是 7.11.2(截止 2021.3.10),我选择 7.8.0 版本(最新版本半
年前的版本)
下载地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch

国内镜像下载(mac和linux一样)

ElasticSearch: https://mirrors.huaweicloud.com/elasticsearch/?C=N&O=D
logstash: https://mirrors.huaweicloud.com/logstash/?C=N&O=D
kibana: https://mirrors.huaweicloud.com/kibana/?C=N&O=D

1、window版本安装

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

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

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

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

打开浏览器(推荐使用谷歌浏览器),输入地址:http://localhost:9200,测试结果

问题解决

1、 Elasticsearch 是使用 java 开发的,且 7.8 版本的 ES 需要 JDK 版本 1.8 以上,默认安装包带有 jdk 环境,如果系统配置 JAVA_HOME,那么使用系统默认的 JDK,如果没有配置使用自带的 JDK,一般建议使用系统配置的 JDK。
2、 双击启动窗口闪退,通过路径访问追踪错误,如果是“空间不足”,请修改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、elasticsearch-head可视化插件

是内嵌于谷歌的 ES 可视化插件

谷歌访问:http://ip:9100

3、kibana

版本必须与 ES 相同

汉化方式

image-20210601222206950

bin/kibana.bat 启动

localhost:5601

4、ik 分词器插件

插件安装

在 ES 插件目录下新建 ik 目录,把 ik 分词器压缩包解压到该目录,重启 ES

什么是IK分词器:

  • 把一句话分词
  • 如果使用中文:推荐IK分词器
  • 两个分词算法:ik_smart(最少切分),ik_max_word(最细粒度划分)
GET _analyze
{
  "analyzer": "ik_smart",
  "text": "我是社会主义接班人"
}
// 最少切分结果:我、是、社会主义、接班人
// 不会重复,词语取自插件中词典

GET _analyze
{
  "analyzer": "ik_max_word",
  "text": "我是社会主义接班人"
}
// 最细粒度切分结果:我、是、社会主义、社会、主义、接班人、接班、人
// 存在重复出现字符

ES 自带分词

# 默认分词器,按字符分词
GET _analyze
{
  "analyzer": "standard",
  "text": "我是社会主义接班人"
}

# 不拆分
GET _analyze
{
  "analyzer": "keyword",
  "text": "我是社会主义接班人"
}

自定义扩展词典

在 ik 插件的 config 目录下新建词典(.dic)文件,内容如下:

我是

IKAnalyzer.cfg.xml 文件配置词典

image-20210601234701145

5、数据格式

关系型数据库 ElasticSearch
数据库 索引
type
document(JSON格式)
field

Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。为了方便大家理解,我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比 ES 里的 Index 可以看做一个库,而 Types 相当于表,Documents 则相当于表的行。
这里 Types 的概念已经被逐渐弱化,Elasticsearch 6.X 中,一个 index 下已经只能包含一个type,Elasticsearch 7.X 中, Type 的概念已经被删除了。

用 JSON 作为文档序列化的格式,比如一条用户信息:

{         
        "name" : "John",
         "sex" : "Male",
         "age" : 25, 
         "birthDate": "1990/05/01", 
         "about" : "I love to go rock climbing", 
         "interests": [ "sports", "music" ] 
}

三、HTTP操作

0、操作方式(RESTful )

​ REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。

​ 在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。

​ 在 REST 样式的 Web 服务中,每个资源都有一个地址。资源本身都是方法调用的目标,方法列表对所有资源都是一样的。这些方法都是标准方法,包括 HTTP GET、POST、PUT、DELETE,还可能包括 HEAD 和 OPTIONS。简单的理解就是,如果想要访问互联网上的资源,就必须向资源所在的服务器发出请求,请求体中必须包含资源的网络路径,以及对资源进行的操作(增删改查)。

kibana 操作

# 创建索引
PUT rewind

# 查询所有索引
GET _cat/indices?v

# 创建文档并指定文档id
POST rewind/_doc/2
{
    "title":"华为",
    "category":"华为",
    "images":"http://www.gulixueyuan.com/xm.jpg",
    "price":3999.00
}

# 通过文档id查询文档
GET rewind/_doc/1

# 修改文档
POST rewind/_update/1
{
    "doc": {
        "price":10000.00
    }
}

# 删除文档
DELETE rewind/_doc/1

# 查询全部文档
GET rewind/_search

# 查询全部文档
GET rewind/_search
{
  "query": {
    "match_all": {}
  }
}

# 条件查询
GET rewind/_search?q=price:47777

# 高级条件查询
# query:查询条件,可通过逗号添加多个字段
# match:单字段2个条件用空格隔开,如要查'华'和'小'2个字
# _source:指定查询后显示字段,数组,不指定默认全部
# sort:排序字段price,数组,不指定默认以_score分数(权重)排序
# from / size:分页查询,开始索引 / 每页条数
GET rewind/_search
{
    "query": {
        "match": {
            "title":"华 小"
        }
    },
    "_source": ["title","price"] ,
    "sort": [
      {
        "price": {
          "order": "desc"
        }
      }
    ],
    "from": 0,
    "size": 10
}

1、索引操作

method url地址 描述
PUT 127.0.0.1:9200/索引名称 创建索引
GET 127.0.0.1:9200/_cat/indices?v 查看所有索引
GET 127.0.0.1:9200/索引名称 查看单个索引
DELETE 127.0.0.1:9200/索引名称 删除索引

(1)创建索引

对比关系型数据库,创建索引就等同于创建数据库

http://127.0.0.1:9200/索引名称

在 Postman 中,向 ES 服务器发 PUT 请求 :http://127.0.0.1:9200/shopping

返回结果:

{     
    "acknowledged"【响应结果】: true, # true 操作成功     
    "shards_acknowledged"【分片结果】: true, # 分片操作成功     
    "index"【索引名称】: "shopping" 
} 

# 注意:创建索引库的分片数默认 1 片,在 7.0.0 之前的 Elasticsearch 版本中,默认 5

如果重复添加索引,会返回错误信息

(2)查看所有索引

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/_cat/indices?v 

这里请求路径中的_cat 表示查看的意思,indices 表示索引,所以整体含义就是查看当前 ES服务器中的所有索引,就好像 MySQL 中的 show tables 的感觉,服务器响应结果如下 :

表头 含义
health 当前服务器健康状态: green(集群完整) yellow(单点正常、集群不完整) red(单点不正常)
status 索引打开、关闭状态
index 索引名
uuid 索引统一编号
pri 主分片数量
rep 副本数量
docs.count 可用文档数量
docs.deleted 文档删除状态(逻辑删除)
store.size 主分片和副分片整体占空间大小
pri.store.size 主分片占空间大小

(3)查看单个索引

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/shopping 

查看索引向 ES 服务器发送的请求路径和创建索引是一致的。但是 HTTP 方法不一致。这里可以体会一下 RESTful 的意义。幂等性问题。

请求后,服务器响应结果如下:

{     
    "shopping"【索引名】: {
        "aliases"【别名】: {},
        "mappings"【映射】: {},
        "settings"【设置】: {
            "index"【设置 - 索引】: {
            "creation_date"【设置 - 索引 - 创建时间】: "1614265373911",
            "number_of_shards"【设置 - 索引 - 主分片数量】: "1",
            "number_of_replicas"【设置 - 索引 - 副分片数量】: "1",
            "uuid"【设置 - 索引 - 唯一标识】: "eI5wemRERTumxGCc1bAk2A",
            "version"【设置 - 索引 - 版本】: {
                "created": "7080099"
                },
            "provided_name"【设置 - 索引 - 名称】: "shopping"
            }         
        }     
    } 
}

(4)删除索引

在 Postman 中,向 ES 服务器发 DELETE 请求 :

http://127.0.0.1:9200/shopping 

重新访问索引时,服务器返回响应:索引不存在

(5)查看索引数据大小

curl --user elastic:password  http://127.0.0.1:9200/_cat/indices/?h=index,health,status,uuid,pri,rep,docs.count,store.size&bytes=b
[
    {
        "index":".security-7",    // 索引名
        "health":"green",        // 健康状态
        "status":"open",        // 状态
        "uuid":"WCZ9HbIlSy6MbUZCJTWpDQ",
        "pri":"1",                // 分片
        "rep":"0",                // 健康分片
        "docs.count":"7",        // 文档数(数据条数)
        "store.size":"24432"    // 索引大小(含数据),单位b,此处大小为0.02M
    },
    {
        "index":"filebeat-7.8.0-2023.09.22-000001",
        "health":"yellow",
        "status":"open",
        "uuid":"c3bhN7FDQbaZ71UWfsnpYA",
        "pri":"1",
        "rep":"1",
        "docs.count":"15960",
        "store.size":"13848833"
    },
    {
        "index":"twitter",
        "health":"yellow",
        "status":"open",
        "uuid":"KQqYVDWmRkqY9HW2-e8bLA",
        "pri":"1",
        "rep":"1",
        "docs.count":"100",
        "store.size":"41740"
    },
    {
        "index":"log_index",
        "health":"yellow",
        "status":"open",
        "uuid":"kHAOkpilS6mUswRXl68Gvw",
        "pri":"1",
        "rep":"1",
        "docs.count":"0",
        "store.size":"208"
    }
]

2、文档操作

文档类型一般为_doc

method url地址 描述
POST localhost:9200/索引名称/文档类型/文档id 创建文档(指定文档id)
POST localhost:9200/索引名称/文档类型 创建文档(随机文档id)
POST localhost:9200/索引名称/_update/文档id 修改文档
DELETE localhost:9200/索引名称/文档类型/文档id 删除文档
GET localhost:9200/索引名称/文档类型/文档id 通过文档id查询文档
POST localhost:9200/索引名称/_search 查询所有的数据
POST localhost:9200/索引名称/_search?q=name:rewind 条件查询

(1)创建文档

索引已经创建好了,接下来我们来创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式。

在 Postman 中,向 ES 服务器发 POST 请求 :

http://127.0.0.1:9200/shopping/_doc

请求体内容为:

{
    "title":"小米手机",
    "category":"小米",
    "images":"http://www.gulixueyuan.com/xm.jpg",
    "price":3999.00
}

服务器响应结果如下:

{
    "_index"【索引】 : "shopping",
    "_type"【类型-文档】 : "_doc",
    "_id"【唯一标识】 : "Xhsa2ncBlvF_7lxyCE9G", #可以类比为 MySQL 中的主键,随机生成
    "_version"【版本】 : 1,
    "result"【结果】 : "created", #这里的 create 表示创建成功
    "_shards"【分片】 : {
    "total"【分片 - 总数】 : 2,
        "successful"【分片 - 成功】 : 1,
        "failed"【分片 - 失败】 : 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

上面的数据创建后,由于没有指定数据唯一性标识(ID),默认情况下, ES 服务器会随机生成一个。
如果想要自定义唯一性标识,需要在创建时指定:

http://127.0.0.1:9200/shopping/_doc/1

:创建文档发送请求的方式必须为 POST,不能是 PUT,否则会发生错误 ;如果增加数据时明确数据主键,那么请求方式也可以为 PUT

(2)查看文档

查看文档时,需要指明文档的唯一性标识,类似于 MySQL 中数据的主键查询
在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/shopping/_doc/1

查询成功后,服务器响应结果:

{
    "_index"【索引】 : "shopping",
    "_type"【文档类型】 : "_doc",
    "_id": "1",
    "_version": 2,
    "_seq_no": 2, //并发控制字段,每次更新加一,用来作为乐观锁
    "_primary_term": 2,    //同上,主分片重新分配,如重启,就会变化
    "found"【查询结果】 : true, // true 表示查找到, false 表示未查找到
    "_source"【文档源信息】 : {
        "title": "华为手机",
        "category": "华为",
        "images": "http://www.gulixueyuan.com/hw.jpg",
        "price": 4999.00
    }
}

(4)修改文档

和新增文档一样,输入相同的 URL 地址请求,如果请求体变化,会将原有的数据内容覆盖
在 Postman 中,向 ES 服务器发 POST 请求 :

http://127.0.0.1:9200/shopping/_doc/1

请求体内容为:

{
    "title":"华为手机",
    "category":"华为",
    "images":"http://www.gulixueyuan.com/hw.jpg",
    "price":4999.00
}

修改成功后,服务器响应结果:

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1",
    "_version"【版本】 : 2,
    "result"【结果】 : "updated", # updated 表示数据被更新
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 2,
    "_primary_term": 2
}

(4)修改字段

修改数据时,也可以只修改某一给条数据的局部信息

在 Postman 中,向 ES 服务器发 POST 请求 :

http://127.0.0.1:9200/shopping/_update/1

请求体内容为:

{
    "doc": {
        "price":3000.00
    }
}

修改成功后,服务器响应结果:

image-20210531100003958

根据唯一性标识,查询文档数据,文档数据已经更新

(5)删除文档

删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。
在 Postman 中,向 ES 服务器发 DELETE 请求 :

http://127.0.0.1:9200/shopping/_doc/1

删除成功,服务器响应结果:

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1",
    "_version"【版本】 : 4, #对数据的操作,都会更新版本
    "result"【结果】 : "deleted", # deleted 表示数据被标记为删除
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 4,
    "_primary_term": 2
}

删除后再查询当前文档信息

image-20210531101516960

如果删除一个并不存在的文档

image-20210531101537633

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1",
    "_version": 1,
    "result"【结果】 : "not_found", # not_found 表示未查找到
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 5,
    "_primary_term": 2
}

(6)条件删除文档

一般删除数据都是根据文档的唯一性标识进行删除,实际操作时,也可以根据条件对多条数据进行删除
首先分别增加多条数据:

{
    "title":"小米手机",
    "category":"小米",
    "images":"http://www.gulixueyuan.com/xm.jpg",
    "price":4000.00
}
{
    "title":"华为手机",
    "category":"华为",
    "images":"http://www.gulixueyuan.com/hw.jpg",
    "price":4000.00
}

向ES 服务器发 POST 请求 :

http://127.0.0.1:9200/shopping/_delete_by_query

请求体内容为:

{
    "query":{
        "match":{
            "price":4000.00
        }
    }
}

删除成功后,服务器响应结果:

{
    "took"【耗时】 : 175,
    "timed_out"【是否超时】 : false,
    "total"【总数】 : 2,
    "deleted"【删除数量】 : 2,
    "batches": 1,
    "version_conflicts": 0,
    "noops": 0,
    "retries": {
        "bulk": 0,
        "search": 0
    },
    "throttled_millis": 0,
    "requests_per_second": -1.0,
    "throttled_until_millis": 0,
    "failures": []
}

3、映射操作

有了索引库,等于有了数据库中的 database。

接下来就需要建索引库(index)中的映射了,类似于数据库(database)中的表结构(table)。创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)。

(1)创建映射

在 Postman 中,向 ES 服务器发 PUT 请求 :

http://127.0.0.1:9200/student/_mapping

请求体内容为:

{
    "properties": {
        "name":{
            "type": "text",
            "index": true
        },
        "sex":{
            "type": "text",
            "index": false
        },
        "age":{
            "type": "long",
            "index": false
        }
    }
}

服务器响应结果如下:

{
    "acknowledged": true
}

映射数据说明:

  • 字段名:任意填写,下面指定许多属性,例如: title、 subtitle、 images、 price

  • type:类型, Elasticsearch 中支持的数据类型非常丰富,说几个关键的:

    • String 类型,又分两种:

      • text:可分词
      • keyword:不可分词,数据会作为完整字段进行匹配
    • Numerical:数值类型,分两类

      • 基本数据类型: long、 integer、 short、 byte、 double、 float、 half_float
      • 浮点数的高精度类型: scaled_float
    • Date:日期类型

    • Array:数组类型

    • Object:对象

  • index:是否索引,默认为 true,也就是说你不进行任何配置,所有字段都会被索引。

    • true:字段会被索引,则可以用来进行搜索
    • false:字段不会被索引,不能用来搜索
  • store:是否将数据进行独立存储,默认为 false

    原始的文本会存储在_source 里面,默认情况下其他提取出来的字段都不是独立存储的,是从_source 里面提取出来的。当然你也可以独立的存储某个字段,只要设置”store”: true 即可,获取独立存储的字段要比从_source 中解析快得多,但是也会占用更多的空间,所以要根据实际业务需求来设置。

  • analyzer:分词器,这里的 ik_max_word 即使用 ik 分词器

(2)查看映射

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_mapping

(3)索引映射关联

在 Postman 中,向 ES 服务器发 PUT 请求 :

http://127.0.0.1:9200/student1

请求参数:

{
    "settings": {},
    "mappings": {
         "properties": {
            "name":{
                "type": "text",
                "index": true
             },
             "sex":{
                 "type": "text",
                 "index": false
            },
            "age":{
                "type": "long",
                "index": false
            }
        }
    }
}

服务器响应结果如下:

{
    "acknowledged": true,
    "shards_acnowledged": true,
    "index": "student1"
}

4、高级查询

Elasticsearch 提供了基于 JSON 提供完整的查询 DSL 来定义查询

定义数据 :

# POST /student/_doc/1001
{
    "name":"zhangsan",
    "nickname":"zhangsan",
    "sex":"男",
    "age":30
}
# POST /student/_doc/1002
{
    "name":"lisi",
    "nickname":"lisi",
    "sex":"男",
    "age":20
}
# POST /student/_doc/1003
{
    "name":"wangwu",
    "nickname":"wangwu",
    "sex":"女",
    "age":40
}
# POST /student/_doc/1004
{
    "name":"zhangsan1",
    "nickname":"zhangsan1",
    "sex":"女",
    "age":50
}
# POST /student/_doc/1005
{
    "name":"zhangsan2",
    "nickname":"zhangsan2",
    "sex":"女",
    "age":30
}

(1)查询所有文档

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search
{
    "query": {
        "match_all": {}
    }
}
# "query":这里的 query 代表一个查询对象,里面可以有不同的查询属性
# "match_all":查询类型,例如: match_all(代表查询所有), match, term , range 等等
# {查询条件}:查询条件会根据类型的不同,写法也有差异

服务器响应结果如下:

{
    "took【查询花费时间,单位毫秒】 " : 1116,
    "timed_out【是否超时】 " : false,
    "_shards【分片信息】 " : {
        "total【总数】 " : 1,
        "successful【成功】 " : 1,
        "skipped【忽略】 " : 0,
        "failed【失败】 " : 0
    },
    "hits【搜索命中结果】 " : {
        "total"【搜索条件匹配的文档总数】 : {
            "value"【总命中计数的值】 : 3,
            "relation"【计数规则】 : "eq" # eq 表示计数准确, gte 表示计数不准确
        },
        "max_score【匹配度分值】 " : 1.0,
        "hits【命中结果集合】 " : [
            。。。
        ]
    }
}

(2)匹配查询

match 匹配类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是 or 的关系
在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数:

{
    "query": {
        "match": {
            "name":"zhangsan"
        }
    }
}

返回参数:

(3)字段匹配查询

multi_match 与 match 类似,不同的是它可以在多个字段中查询。
在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数

{
    "query": {
        "multi_match": {
            "query": "zhangsan",
            "fields": ["name","nickname"]
        }
    }
}

服务器响应结果:

(4)关键字精确查询

term 查询,精确的关键词匹配查询,不对查询条件进行分词。
在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数

{
    "query": {
        "term": {
            "name": {
                "value": "zhangsan"
            }
        }
    }
}

服务器响应结果:

(5)多关键字精确查询

terms 查询和 term 查询一样,但它允许你指定多值进行匹配。
如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件,类似于 mysql 的 in。
在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数:

{
    "query": {
        "terms": {
            "name": ["zhangsan","lisi"]
        }
    }
}
  • term,直接查询精确的
  • match,会使用分词器解析!(先分析文档,然后通过分析的文档进行查询)

(6)指定查询字段

默认情况下, Elasticsearch 在搜索的结果中,会把文档中保存在_source 的所有字段都返回。
如果我们只想获取其中的部分字段,我们可以添加_source 的过滤
在 Postman 中,向 ES 服务器发 GET 请求 :

{
    "_source": ["name","nickname"],
        "query": {
            "terms": {
            "nickname": ["zhangsan"]
        }
    }
}

(7)过滤字段

  • includes:来指定想要显示的字段
  • excludes:来指定不想要显示的字段

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数

{
    "_source": {
        "includes": ["name","nickname"]
        # "excludes": ["name","nickname"]
    },
    "query": {
        "terms": {
            "nickname": ["zhangsan"]
        }
    }
}

(8)组合查询 bool 多条件

  • bool
    • must 相当于 and
    • must_not 相当于 非!
    • should 相当于 or

bool把各种其它查询通过must(必须 / and)、 must_not(必须不 / not)、 should(应该 / or)的方式进行组合
在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数:

{
    "query": {
        "bool": {
            "must": [
                {
                       "match": {
                        "name": "zhangsan"
                    }
                }
            ],
            "must_not": [
                {
                    "match": {
                        "age": "40"
                    }
                }
            ],
            "should": [
                {
                    "match": {
                        "sex": "男"
                    }
                }
            ]
        }
    }
}

(9)范围查询

range 查询找出那些落在指定区间内的数字或者时间。 range 查询允许以下字符

操作符 说明
gt 大于>
gte 大于等于>=
lt 小于<
lte 小于等于<=

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数:(查询年龄30-35之间的)

{
    "query": {
        "range": {
            "age": {
                "gte": 30,
                "lte": 35
            }
        }
    }
}

(10)模糊查询

返回包含与搜索字词相似的字词的文档。
编辑距离是将一个术语转换为另一个术语所需的一个字符更改的次数。这些更改可以包括:

  • 更改字符(box → fox)

  • 删除字符(black → lack)

  • 插入字符(sic → sick)

  • 转置两个相邻字符(act → cat)

为了找到相似的术语, fuzzy 查询会在指定的编辑距离内创建一组搜索词的所有可能的变体或扩展。然后查询返回每个扩展的完全匹配。

http://127.0.0.1:9200/student/_search

在 Postman 中,向 ES 服务器发 GET 请求 :

{
    "query": {
        "fuzzy": {
            "title": {
                "value": "zhangsan"
            }
        }
    }
}

通过 fuzziness 修改编辑距离。一般使用默认值 AUTO,根据术语的长度生成编辑距离。

{
    "query": {
        "fuzzy": {
            "title": {
                "value": "zhangsan",
                "fuzziness": 2
            }
        }
    }
}

(11)单字段排序

sort 可以让我们按照不同的字段进行排序,并且通过 order 指定排序的方式。 desc 降序, asc升序。

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数

{
    "query": {
        "match": {
            "name":"zhangsan"
        }
    },
    "sort": [{
        "age": {
            "order":"desc"
        }
    }]
}

(12)多字段排序

假定我们想要结合使用 age 和 _score 进行查询,并且匹配的结果首先按照年龄排序,然后按照相关性得分排序
在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数

{
    "query": {
        "match_all": {}
    },
    "sort": [
        {
            "age": {
                "order": "desc"
            }
        },
        {
            "_score":{
                "order": "desc"
            }
        }
    ]
}

(13)高亮查询

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

Elasticsearch 可以对查询内容中的关键字部分,进行标签和样式(高亮)的设置。
在使用 match 查询的同时,加上一个 highlight 属性:

  • pre_tags:前置标签

  • post_tags:后置标签

  • fields:需要高亮的字段

  • title:这里声明 title 字段需要高亮,后面可以为这个字段设置特有配置, 也可以空

在 Postman 中,向 ES 服务器发 GET 请求 :

{
    "query": {
        "match": {
            "name": "zhangsan"
        }
    },
    "highlight": {
        "pre_tags": "<font color='red'>",
        "post_tags": "</font>",
        "fields": {
            "name": {}
        }
    }
}

(14)分页查询

from:当前页的起始索引,默认从 0 开始。 from = (pageNum - 1) * size

size:每页显示多少条

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数

{
    "query": {
        "match_all": {}
    },
    "sort": [
        {
            "age": {
                "order": "desc"
            }
        }
    ],
    "from": 0,
    "size": 2
}

(15)聚合查询

聚合允许使用者对 es 文档进行统计分析,类似与关系型数据库中的 group by,当然还有很多其他的聚合,例如取最大值、平均值等等。

  • 对某个字段取最大值 max
  • 对某个字段取最小值 min
  • 对某个字段求和 sum
  • 对某个字段取平均值 avg
  • 对某个字段的值进行去重之后再取总数 cardinality
  • stats 聚合,对某个字段一次性返回 count, max, min, avg 和 sum 五个指标

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数(最大值)

{
    "aggs":{
        "max_age":{
            "max":{"field":"age"}
        }
    },
    "size":0
}

请求参数(stats 聚合)

{
    "aggs":{
        "stats_age":{
            "stats":{"field":"age"}
        }
    },
    "size":0
}

(16)桶聚合查询(分组)

桶聚和相当于 sql 中的 group by 语句

  • terms 聚合,分组统计

在 Postman 中,向 ES 服务器发 GET 请求 :

http://127.0.0.1:9200/student/_search

请求参数

{
    "aggs":{
        "age_groupby":{
            "terms":{"field":"age"}
        }
    },
    "size":0
}

在 terms 分组下再进行聚合

{
    "aggs":{
        "age_groupby":{
            "terms":{"field":"age"},
            "aggs":{
                "sum_age":{
                    "sum":{"field":"age"}
                }
            }
        }
    },
    "size":0
}

三、Java Api 操作

Elasticsearch 软件是由 Java 语言开发的,所以也可以通过 Java API 的方式对 Elasticsearch服务进行访问

1、依赖

springboot 版本如果是 2.3.0 以下会出现 jar 包冲突的情况,故采用 2.3.6.RELEASE

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.8.0</version>
</dependency>
<!-- elasticsearch 的客户端 -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.8.0</version>
</dependency>
<!-- elasticsearch 依赖 2.x 的 log4j -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.8.2</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.8.2</version>
</dependency>

<!-- elasticsearch -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.8.0</version>
    <!-- 排除依赖 -->
    <exclusions>
        <exclusion>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 手动添加es相关依赖,防止版本不一造成问题 -->
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.8.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.8.0</version>
</dependency>

2、创建客户端

@Configuration
public class ElasticSearchConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient(){
        // 1 创建 ElasticSearch 客户端
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        // ES 所在的服务器的 ip、端口
                        new HttpHost("192.168.2.233", 9200, "http")));
        return client;
    }
}

集群方式如下

@Data
@Configuration
@ConfigurationProperties(prefix = "elastic-search")
public class ElasticSearchConfig {

    /**
     * es host ip:port(集群),英文逗号分隔
     */
    private String hosts;

    /**
     * es用户名
     */
    private String userName;

    /**
     * es密码
     */
    private String password;

    /**
     * es 请求方式(http/https)
     */
    private String scheme;

    /**
     * es集群名称
     */
    private String clusterName;

    /**
     * es 连接超时时间
     */
    private Integer connectTimeOut;

    /**
     * es socket 连接超时时间
     */
    private Integer socketTimeOut;

    /**
     * es 请求超时时间
     */
    private Integer connectionRequestTimeOut;

    /**
     * es 最大连接数
     */
    private Integer maxConnectNum;

    /**
     * es 每个路由的最大连接数
     */
    private Integer maxConnectNumPerRoute;

    @Bean
    public RestHighLevelClient restHighLevelClient(){

        // 集群地址处理
        List<HttpHost> hostLists = new ArrayList<>();
        String[] hostList = hosts.split(",");
        HttpHost[] httpHost = new HttpHost[hostList.length];
        for (int i = 0; i < hostList.length; i++) {
            String host = hostList[i].split(":")[0];
            String port = hostList[i].split(":")[1];
            httpHost[i] = new HttpHost(host, Integer.parseInt(port), scheme);
        }

        // 构建连接对象
        RestClientBuilder builder = RestClient.builder(httpHost);

        // 连接延时配置
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            if (connectTimeOut != null){
                requestConfigBuilder.setConnectTimeout(connectTimeOut);
            }
            if (socketTimeOut != null){
                requestConfigBuilder.setSocketTimeout(socketTimeOut);
            }
            if (connectionRequestTimeOut != null){
                requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeOut);
            }
            return requestConfigBuilder;
        });

        // 连接数配置
        builder.setHttpClientConfigCallback(httpClientBuilder -> {

            if (maxConnectNum != null){
                httpClientBuilder.setMaxConnTotal(maxConnectNum);
            }

            if (maxConnectNumPerRoute != null){
                httpClientBuilder.setMaxConnPerRoute(maxConnectNumPerRoute);
            }

            // 设置用户名、密码
            if (StringUtils.isNotBlank(userName) && StringUtils.isNotBlank(password)){
                CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                credentialsProvider.setCredentials(AuthScope.ANY,new UsernamePasswordCredentials(userName,password));
                httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            }

            return httpClientBuilder;
        });

        return new RestHighLevelClient(builder);
    }
}

3、索引操作

(1)创建索引

 // 1 创建 ElasticSearch 客户端
RestHighLevelClient client = new RestHighLevelClient(
    // ES 所在的服务器的 ip、端口
    RestClient.builder(new HttpHost("localhost", 9200, "http"))
);

// 创建索引 - 请求对象,参数为创建的索引名称
CreateIndexRequest request = new CreateIndexRequest("user");
// 发送请求,获取响应
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
boolean acknowledged = response.isAcknowledged();
// 响应状态
System.out.println("操作状态 = " + acknowledged);

client.close();

(2)查看索引

// 查询索引 - 请求对象
GetIndexRequest request = new GetIndexRequest("user");
// 发送请求,获取响应
GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
System.out.println("aliases:"+response.getAliases());        //
System.out.println("mappings:"+response.getMappings());        //
System.out.println("settings:"+response.getSettings());        //

(3)删除索引

// 删除索引 - 请求对象
DeleteIndexRequest request = new DeleteIndexRequest("user");
// 发送请求,获取响应
AcknowledgedResponse response = client.indices().delete(request,RequestOptions.DEFAULT);
// 操作结果
System.out.println("操作结果 : " + response.isAcknowledged());

4、文档操作

(1)新增文档

user 对象

@Data
public class User {
    private String name;
    private Integer age;
    private String sex;
}

新增文档

// 新增文档 - 请求对象
IndexRequest request = new IndexRequest();
// 设置索引及唯一性标识
request.index("user").id("1001");
// 创建数据对象
User user = new User();
user.setName("zhangsan");
user.setAge(30);
user.setSex("男");
ObjectMapper objectMapper = new ObjectMapper();
String productJson = objectMapper.writeValueAsString(user);
// 添加文档数据,数据格式为 JSON 格式
request.source(productJson, XContentType.JSON);
// 客户端发送请求,获取响应对象
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
//3.打印结果信息
System.out.println("_index:" + response.getIndex());
System.out.println("_id:" + response.getId());
System.out.println("_result:" + response.getResult());

(2)修改文档

// 修改文档 - 请求对象
UpdateRequest request = new UpdateRequest();
// 配置修改参数
request.index("user").id("1001");
// 设置请求体,对数据进行修改
request.doc(XContentType.JSON, "sex", "女");
// 客户端发送请求,获取响应对象
UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
System.out.println("_index:" + response.getIndex());
System.out.println("_id:" + response.getId());
System.out.println("_result:" + response.getResult());

(3)查询文档

//1.创建请求对象
GetRequest request = new GetRequest().index("user").id("1001");
//2.客户端发送请求,获取响应对象
GetResponse response = client.get(request, RequestOptions.DEFAULT);
////3.打印结果信息
System.out.println("_index:" + response.getIndex());
System.out.println("_type:" + response.getType());
System.out.println("_id:" + response.getId());
System.out.println("source:" + response.getSourceAsString());

(4)删除文档

//创建请求对象
DeleteRequest request = new DeleteRequest().index("user").id("1");
//客户端发送请求,获取响应对象
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
//打印信息
System.out.println(response.toString());

(5)批量操作

批量新增

//创建批量新增请求对象
BulkRequest request = new BulkRequest();
request.add(new IndexRequest().index("user").id("1001").source(XContentType.JSON, "name","zhangsan"));
request.add(new IndexRequest().index("user").id("1002").source(XContentType.JSON, "name","lisi"));
request.add(new IndexRequest().index("user").id("1003").source(XContentType.JSON, "name","wangwu"));
//客户端发送请求,获取响应对象
BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
//打印结果信息
System.out.println("took:" + responses.getTook());
System.out.println("items:" + responses.getItems());

批量删除

//创建批量删除请求对象
BulkRequest request = new BulkRequest();
request.add(new DeleteRequest().index("user").id("1001"));
request.add(new DeleteRequest().index("user").id("1002"));
request.add(new DeleteRequest().index("user").id("1003"));
//客户端发送请求,获取响应对象
BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
//打印结果信息
System.out.println("took:" + responses.getTook());
System.out.println("items:" + responses.getItems());

5、请求体查询–高级查询

(1)查询所有索引数据

// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 查询所有数据
sourceBuilder.query(QueryBuilders.matchAllQuery());
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits) {
    //输出每条查询的结果信息
    System.out.println(hit.getSourceAsString());
}
System.out.println("<<========");

(2)term 查询,查询条件为关键字

// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termQuery("age", "30"));
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits) {
    //输出每条查询的结果信息
    System.out.println(hit.getSourceAsString());
}
System.out.println("<<========");

(3)分页查询

// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchAllQuery());
// 分页查询
// 当前页其实索引(第一条数据的顺序号), from
sourceBuilder.from(0);
// 每页显示多少条 size
sourceBuilder.size(2);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits) {
    //输出每条查询的结果信息
    System.out.println(hit.getSourceAsString());
}
System.out.println("<<========");

(4)数据排序

// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchAllQuery());
// 排序
sourceBuilder.sort("age", SortOrder.ASC);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits) {
    //输出每条查询的结果信息
    System.out.println(hit.getSourceAsString());
}
System.out.println("<<========");

(5)过滤字段

// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchAllQuery());
//查询字段过滤
String[] excludes = {};
String[] includes = {"name", "age"};
sourceBuilder.fetchSource(includes, excludes);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits) {
    //输出每条查询的结果信息
    System.out.println(hit.getSourceAsString());
}
System.out.println("<<========");

(6)Bool查询

// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 必须包含
boolQueryBuilder.must(QueryBuilders.matchQuery("age", "30"));
// 一定不含
boolQueryBuilder.mustNot(QueryBuilders.matchQuery("name", "zhangsan"));
// 可能包含
boolQueryBuilder.should(QueryBuilders.matchQuery("sex", "男"));
sourceBuilder.query(boolQueryBuilder);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits) {
    //输出每条查询的结果信息
    System.out.println(hit.getSourceAsString());
}
System.out.println("<<========");

(7)范围查询

// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age");
// 大于等于
rangeQuery.gte("30");
// 小于等于
rangeQuery.lte("40");
sourceBuilder.query(rangeQuery);
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits) {
    //输出每条查询的结果信息
    System.out.println(hit.getSourceAsString());
}
System.out.println("<<========");

(8)模糊查询

// 创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("student");
// 构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.fuzzyQuery("name","zhangsan").fuzziness(Fu
zziness.ONE));
request.source(sourceBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 查询匹配
SearchHits hits = response.getHits();
System.out.println("took:" + response.getTook());
System.out.println("timeout:" + response.isTimedOut());
System.out.println("total:" + hits.getTotalHits());
System.out.println("MaxScore:" + hits.getMaxScore());
System.out.println("hits========>>");
for (SearchHit hit : hits) {
    //输出每条查询的结果信息
    System.out.println(hit.getSourceAsString());
}
System.out.println("<<========");

6、高亮查询–高级查询

// 高亮查询
SearchRequest request = new SearchRequest().indices("student");
//2.创建查询请求体构建器
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//构建查询方式:高亮查询
TermsQueryBuilder termsQueryBuilder =
QueryBuilders.termsQuery("name","zhangsan");
//设置查询方式
sourceBuilder.query(termsQueryBuilder);
//构建高亮字段
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<font color='red'>");//设置标签前缀
highlightBuilder.postTags("</font>");//设置标签后缀
highlightBuilder.field("name");//设置高亮字段
//设置高亮构建对象
sourceBuilder.highlighter(highlightBuilder);
//设置请求体
request.source(sourceBuilder);
//3.客户端发送请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.打印响应结果
SearchHits hits = response.getHits();
System.out.println("took::"+response.getTook());
System.out.println("time_out::"+response.isTimedOut());
System.out.println("total::"+hits.getTotalHits());
System.out.println("max_score::"+hits.getMaxScore());
System.out.println("hits::::>>");
for (SearchHit hit : hits) {
    String sourceAsString = hit.getSourceAsString();
    System.out.println(sourceAsString);
    //打印高亮结果
    Map<String, HighlightField> highlightFields = hit.getHighlightFields();
    System.out.println(highlightFields);
}
System.out.println("<<::::");

7、聚合查询–高级查询

(1)最大年龄

// 高亮查询
SearchRequest request = new SearchRequest().indices("student");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.aggregation(AggregationBuilders.max("maxAge").field("age"));
//设置请求体
request.source(sourceBuilder);
//3.客户端发送请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.打印响应结果
SearchHits hits = response.getHits();
System.out.println(response);

(2)分组统计

// 高亮查询
SearchRequest request = new SearchRequest().indices("student");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.aggregation(AggregationBuilders.terms("age_groupby").field("ag
e"));
//设置请求体
request.source(sourceBuilder);
//3.客户端发送请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4.打印响应结果
SearchHits hits = response.getHits();
System.out.println(response);

四、RestHighLevelClient

1、2、3、同上

4、实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private int age;
}

5、操作索引

@SpringBootTest
public class IndexTest {

    @Resource
    private RestHighLevelClient client;

    // 创建索引
    @Test
    public void createIndex() throws IOException {
        // 1、创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("rewind_index");
        // 2、客户端执行请求 ,请求后获得响应
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response);
    }

    // 判断索引是否存在
    @Test
    public void getIndex() throws IOException {
        GetIndexRequest request = new GetIndexRequest("rewind_index");
        boolean b = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(b);
    }

    // 删除索引
    @Test
    public void delIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("rewind_index");
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(response);
    }
}

6、操作文档

@SpringBootTest
public class DocumentTest {
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Resource
    private RestHighLevelClient client;

    // 创建文档
    @Test
    public void createDocumentTest() throws IOException {
        User user = new User(3L, "王三", 14);
        // 创建请求
        IndexRequest request = new IndexRequest("rewind_index");

        // 规则 localhost:9200/索引名称/文档类型/文档id
        request.id(user.getId()+"");

        // 设置超时时间,以下两行是等同的,都是设置超时时间1秒
        //request.timeout("1s");
        request.timeout(TimeValue.timeValueSeconds(100));

        // 将数据以json的形式放入请求中
        request.source(JSON.toJSONString(user), XContentType.JSON);

        // 发送请求
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        System.out.println(response.toString());
        System.out.println(response.status());  // 首次返回 CREATED  之后返回 OK
    }

    // 判断文档是否存在,获取文档信息
    @Test
    public void getDocumentTest() throws IOException {
        GetRequest request = new GetRequest("rewind_index", "2");

        // 不获取返回的 _source 上下文,提高效率,以下2行可不写
        //request.fetchSourceContext(new FetchSourceContext(false));
        //request.storedFields("_none_");

        // 判断文档是否存在
        boolean b = client.exists(request, RequestOptions.DEFAULT);
        System.out.println("文档是否存在:"+b);
        // 获取文档信息
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        System.out.println(response.getSourceAsString());   //{"age":12,"id":2,"name":"李四"}
    }

    // 更新文档信息
    @Test
    public void getDcumentInfoTest() throws IOException {
        UpdateRequest request = new UpdateRequest("rewind_index", "1");
        request.timeout("1s");
        User user = new User(1L, "张三三", 19);
        request.doc(JSON.toJSONString(user),XContentType.JSON);
        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
        System.out.println(response.status());  // 修改成功返回 OK
    }

    // 删除文档
    @Test
    public void delDocumentTest() throws IOException {
        DeleteRequest request = new DeleteRequest("rewind_index", "1");
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        System.out.println(response.status()); // 删除成功返回 OK
    }

    // 批量 操作
    @Test
    public void bulkTest() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");     // 批处理超时时间应该设置大一些

        ArrayList<User> list = new ArrayList<>();
        list.add(new User(4L,"小四",14));
        list.add(new User(5L,"小五",14));
        list.add(new User(6L,"小六",14));
        list.add(new User(7L,"小七",14));
        list.add(new User(8L,"小八",14));

        for (int i = 0; i < list.size(); i++) {
            IndexRequest indexRequest = new IndexRequest("rewind_index")
                    .id(i+10+"")    // 不设置,会生成随机id
                    .source(JSON.toJSONString(list.get(i)),XContentType.JSON);
            // 只需将不同的 request 放入 add 中,即可批量执行 增删改
            bulkRequest.add(indexRequest);
        }

        BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulkResponse.hasFailures());     // 是否失败,false执行成功
    }

    // 查询文档
    @Test
    public void searchDocumentTest() throws IOException {
        SearchRequest request = new SearchRequest("rewind_index");
        // 构建条件
        SearchSourceBuilder builder = new SearchSourceBuilder();

        // 查询条件,可用 QueryBuilders 工具类实现
        //MatchQueryBuilder query1 = QueryBuilders.matchQuery("name","小");  // 匹配查询,类似mysql模糊查询
        //MatchAllQueryBuilder query2 = QueryBuilders.matchAllQuery();  // 查询所有
        //精确匹配,必须全等, 中文查询必须在字段名后加 .keyword
        TermQueryBuilder query = QueryBuilders.termQuery("name.keyword", "小四");
        builder.query(query);
        builder.from(0);    // 分页开始索引
        builder.size(5);    // 每页数据条数
        builder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        request.source(builder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        System.out.println(JSON.toJSONString(response.getHits()));
        System.out.println("=====================================");
        for (SearchHit fields : response.getHits().getHits()) {
            System.out.println(fields.getSourceAsMap());    // {name=小四, id=4, age=14}
        }
    }
}

五、Spirng Data(不推荐)

Spring Data Elasticsearch 基于 spring data API 简化 Elasticsearch 操作,将原始操作Elasticsearch 的客户端 API 进行封装 。 Spring Data 为 Elasticsearch 项目提供集成搜索引擎。Spring Data Elasticsearch POJO 的关键功能区域为中心的模型与 Elastichsearch 交互文档和轻松地编写一个存储索引库数据访问层。

1、版本和依赖

Spring Data Elasticsearch 版本对比

image-20210531162926179

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

2、配置文件

# es 服务地址
elasticsearch.host=127.0.0.1
# es 服务端口
elasticsearch.port=9200
# 配置日志级别,开启 debug 日志
logging.level.com.atguigu.es=debug

3、配置类

  • ElasticsearchRestTemplate 是 spring-data-elasticsearch 项目中的一个类,和其他 spring 项目中的 template类似。

  • 在新版的 spring-data-elasticsearch 中, ElasticsearchRestTemplate 代替了原来的 ElasticsearchTemplate。

  • 原因是 ElasticsearchTemplate 基于 TransportClient, TransportClient 即将在 8.x 以后的版本中移除。所以,我们推荐使用 ElasticsearchRestTemplate。

  • ElasticsearchRestTemplate 基 于 RestHighLevelClient 客 户 端 的 。 需 要 自 定 义 配 置 类 , 继 承AbstractElasticsearchConfiguration,并实现 elasticsearchClient()抽象方法,创建 RestHighLevelClient 对象。

// 注入属性,值为 配置文件 中前缀为“elasticsearch”的参数,或者采用@Value注入
@ConfigurationProperties(prefix = "elasticsearch")
@Configuration
@Data
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
    // 配置文件中的elasticsearch.host
    private String host ;
    // elasticsearch.port
    private Integer port ;

    //重写父类方法
    @Override
    public RestHighLevelClient elasticsearchClient() {
        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
        return restHighLevelClient;
    }
}

4、实体类映射

用于关联 ES 的文档

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
// springboot运行时,自动关联@Document实体类,没有则创建索引
@Document(indexName = "shopping", shards = 3, replicas = 1)
public class Product {
    //必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"
    @Id
    private Long id;//商品唯一标识
    /**
    * type : 字段数据类型
    * analyzer : 分词器类型
    * index : 是否索引(默认:true)
    * Keyword : 短语,不进行分词
    */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;//商品名称
    @Field(type = FieldType.Keyword)
    private String category;//分类名称
    @Field(type = FieldType.Double)
    private Double price;//商品价格
    @Field(type = FieldType.Keyword, index = false)        
    private String images;//图片地址
}

@Document 注解

public @interface Document {
    String indexName(); //索引库的名称,个人建议以项目的名称命名
    String type() default ""; //类型,个人建议以实体的名称命名  
    short shards() default 5; //默认分区数  
    short replicas() default 1; //每个分区默认的备份数  
    String refreshInterval() default "1s"; //刷新间隔  
    String indexStoreType() default "fs"; //索引文件存储类型
}

@Field 注解

public @interface Field {

    FieldType type() default FieldType.Auto; //自动检测属性的类型,可以根据实际情况自己设置
    FieldIndex index() default FieldIndex.analyzed; //默认情况下分词,一般默认分词就好,除非这个字段你确定查询时不会用到

    DateFormat format() default DateFormat.none; //时间类型的格式化  
    String pattern() default "";  
    boolean store() default false; //默认情况下不存储原文  
    String searchAnalyzer() default ""; //指定字段搜索时使用的分词器  
    String indexAnalyzer() default ""; //指定字段建立索引时指定的分词器  
    String[] ignoreFields() default {}; //如果某个字段需要被忽略  
    boolean includeInParent() default false;
}

5、DAO数据访问对象

@Repository
public interface ProductDao extends ElasticsearchRepository<Product,Long> {
}

6、索引操作

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESIndexTest {
    //注入 ElasticsearchRestTemplate
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    //创建索引并增加映射配置
    @Test
    public void createIndex(){
        //创建索引,系统初始化会自动创建索引
        //springboot运行时,自动关联@Document实体类,没有则创建索引
        System.out.println("创建索引");
    }
    @Test
    public void deleteIndex(){
        //删除索引
        boolean flg = elasticsearchRestTemplate.deleteIndex(Product.class);
        System.out.println("删除索引 = " + flg);
    }
}

7、文档操作

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESProductDaoTest {
    @Autowired
    private ProductDao productDao;
    /**
    * 新增
    */
    @Test
    public void save(){
        Product product = new Product();
        product.setId(2L);
        product.setTitle("华为手机");
        product.setCategory("手机");
        product.setPrice(2999.0);
        product.setImages("http://www.atguigu/hw.jpg");
        productDao.save(product);
    }
    //修改
    @Test
    public void update(){
        Product product = new Product();
        product.setId(1L);
        product.setTitle("小米 2 手机");
        product.setCategory("手机");
        product.setPrice(9999.0);
        product.setImages("http://www.atguigu/xm.jpg");
        productDao.save(product);
    }
    //根据 id 查询
    @Test
    public void findById(){
        Product product = productDao.findById(1L).get();
        System.out.println(product);
    }
    //查询所有
    @Test
    public void findAll(){
        Iterable<Product> products = productDao.findAll();
        for (Product product : products) {
            System.out.println(product);
           }
    }
    //删除
    @Test
    public void delete(){
        Product product = new Product();
        product.setId(1L);
        productDao.delete(product);
    }
    //批量新增
    @Test
    public void saveAll(){
        List<Product> productList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Product product = new Product();
            product.setId(Long.valueOf(i));
            product.setTitle("["+i+"]小米手机");
            product.setCategory("手机");
            product.setPrice(1999.0+i);
            product.setImages("http://www.atguigu/xm.jpg");
            productList.add(product);
        }
        productDao.saveAll(productList);
    }
    //分页查询
    @Test
    public void findByPageable(){
        //设置排序(排序方式,正序还是倒序,排序的 id)
        Sort sort = Sort.by(Sort.Direction.DESC,"id");
        int currentPage=0;//当前页,第一页从 0 开始, 1 表示第二页
        int pageSize = 5;//每页显示多少条
        //设置查询分页
        PageRequest pageRequest = PageRequest.of(currentPage, pageSize,sort);
        //分页查询
        Page<Product> productPage = productDao.findAll(pageRequest);
        for (Product Product : productPage.getContent()) {
            System.out.println(Product);
        }
    }
}

8、文档搜索

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESSearchTest {
@Autowired
private ProductDao productDao;
    /**
    * term 查询
    * search(termQueryBuilder) 调用搜索方法,参数查询构建器对象
    */
    @Test
    public void termQuery(){
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");
        Iterable<Product> products = productDao.search(termQueryBuilder);
        for (Product product : products) {
            System.out.println(product);
        }
    }
    /**
    * term 查询加分页
    */
    @Test
    public void termQueryByPage(){
        int currentPage= 0 ;
        int pageSize = 5;
        //设置查询分页
        PageRequest pageRequest = PageRequest.of(currentPage, pageSize);
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");
        Iterable<Product> products = productDao.search(termQueryBuilder,pageRequest);
        for (Product product : products) {
            System.out.println(product);
        }
    }
}

  目录