elasticsearch(下载安装、基本操作、查询、聚合、SpringData-Elasticsearch)

文章目录

  • 1. 了解搜索技术
    • 1.1. 什么是搜索
    • 1.2. 新业务需求
    • 1.3. 搜索引擎
    • 1.4. 倒排索引(Inverted index)
    • 1.5. 认识lucene
    • 1.6. 什么是全文检索
  • 2.下载安装
    • 2.1. elastic
    • 2.2 下载
      • 2.2.1 elasticsearch
      • 2.2.2 kibana地址
      • 2.2.3 ik中文分词器地址
    • 2.3 安装elasticsearch
      • 2.3.1 安装elasticsearch
      • 2.3.2 elasticsearch配置jdk:
      • 2.3.3 elasticsearch配置:
      • 2.3.4 设置es访问配置文件的权限
      • 2.3.5. 启动
      • 2.3.6. 测试
    • 2.4 安装kibana
    • 2.5 IK分词器
      • 2.5.1. 中文问题
      • 2.5.2. 中文分词器
      • 2.5.3. IK分词器
      • 2.5.4. 自定义词库
  • 3. elasticsearch基本操作
    • 3.1. 基本概念
    • 3.2. 索引操作(indeces)
      • 3.2.1. 查询索引
      • 3.2.2. 创建索引
      • 3.2.3. 查看索引具体信息
      • 3.2.4. 删除索引
    • 3.3. 映射配置(_mapping)
      • 3.3.1. 创建映射字段
      • 3.3.2. 查看映射关系
    • 3.4. 新增文档(document)
      • 3.4.1. 基本玩法
      • 3.4.2. 智能判断
    • 3.5. 删除数据
    • 3.6 更新文档
  • 4. 查询
    • 4.1. 数据准备
    • 4.2. 匹配查询(match)
    • 4.3. 词条查询(term)
    • 4.4. 范围查询(range)
    • 4.5. 布尔组合(bool)
    • 4.6. 过滤(filter)
    • 4.7. 排序(sort)
    • 4.8. 分页(from/size)
    • 4.9. 高亮(highlight)
    • 4.10. 结果过滤(_source)
  • 5. 聚合(aggregations)
    • 5.1. 基本概念
    • 5.2. 聚合为桶
    • 5.3. 桶内度量
    • 5.4. 桶内嵌套桶
  • 6. SpringData-Elasticsearch
    • 6.1. 创建module
    • 6.2. 实体类
    • 6.3. ElasticsearchRestTemplate
      • 6.3.1 创建索引及映射
      • 6.3.2 查询文档
      • 6.3.3 复杂查询
    • 6.4. Repository文档操作
      • 6.4.1. 新增
      • 6.4.2. 删除
      • 6.4.3. 基本查询
      • 6.4.4. 使用@Query注解
    • 6.5. RestHighLevelClient

在这里插入图片描述

1. 了解搜索技术

1.1. 什么是搜索

什么是搜索, 计算机根据用户输入的关键词进行匹配,从已有的数据库中摘录出相关的记录反馈给用户。

在这里插入图片描述

线性匹配:

select * from item where title like ’%小米%‘

1.2. 新业务需求

比如,用户在百度文本框中输入,“吃饭睡觉写程序”,会出现的以下结果:

在这里插入图片描述

从结果可以看出,百度搜索具备以下明显特点:

1、即使在相关结果数量接近3600万时,也能快速得出结果。

2、搜索的结果不仅仅局限于完整的“吃饭睡觉写程序”这一短语,而是将此短语拆分成,“写程序”,“吃饭”,“睡觉”,“程序”等关键字。

3、对拆分后的搜索关键字进行标红显示。

4、即使只满足部分关键字也能查询出来

5、即使输错一两个字母也能查询出来,例如:搜索facebool,能查询到Facebook

问题:上述功能,使用大家以前学过的数据库搜索能够方便实现吗?

1.3. 搜索引擎

搜索引擎(search engine)是一种信息检索系统,旨在协助搜索存储在计算机系统中的信息。

搜索引擎按照功能通常分为垂直搜索和综合搜索。

​ 1、垂直搜索是指专门针对某一类信息进行搜索。例如:会搜网 主要做商务搜索的,并且提供商务信息。除此之外还有爱看图标网、职友集等。

​ 2、综合搜索是指对众多信息进行综合性的搜索。例如:百度、谷歌、搜狗、360搜索等。

​ 3、站内搜索是指对网站内的信息进行的搜索。例如:京东、招聘网站等

​ 4、软件内部搜索,例如word、eclipse、idea等

1.4. 倒排索引(Inverted index)

搜索引擎目前主流的实现原理:倒排索引技术

倒排索引又叫反向索引(如下图)以字或词为关键字进行索引,表中关键字所对应的记录表项,记录了出现这个字或词的所有文档,每一个表项记录该文档的编号和关键字在该文档中出现的位置情况。

一个未经处理的数据库中,一般是以文档ID作为索引,以文档内容作为记录。
而倒排索引指的是将单词或记录作为索引,将文档ID作为记录,这样便可以方便地通过单词或记录查找到其所在的文档。

在这里插入图片描述

文档保存时: 使用文档id和倒排列表id绑定存储

​ 例如文档编号为1的文档: [1,1],[1,2],[1,3],[1,4],[1,5]

​ 用户搜索时,es可以将用户搜索的内容按照单词id拆分

​ 例如用户搜索:谷歌 , 单词ID=1

​ es可以根据1去文档的倒排列表关系中查找哪些文档绑定id中包含1,将包含1的文件按照它的评分方式评分然后查询显示

在实际的运用中,我们可以对数据库中原始的数据结构(如:商品表),在业务空闲时,事先生成文档列表(左图)及倒排索引区域(右图)

用户有查询需求时,先访问倒排索引数据区域(右图),得出文档编号后,通过文档编号即可快速,准确的通过左图找到具体的文档内容

例如:用户输入“跳槽”关键字,先到右图的索引区查询,

找到[1,4];再根据id=1和id=4到左图找到两条记录。整个过程走的都是索引,比传统的链式匹配更加快速。

1.5. 认识lucene

倒排索引技术只是底层原理,我们可以自己写代码实现。也可以使用开源组织写好的方案:lucene。

官网:http://lucene.apache.org

  • Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供

  • Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具

  • Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品。例如:solr和elasticsearch

1.6. 什么是全文检索

在这里插入图片描述

倒排索引是全文检索技术的一种实现方式。

2.下载安装

Apache提供的Lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来的应用。

lucene是类似于servlet,而搜索引擎就是tomcat 。

目前市面上流行的搜索引擎软件,主流的就两款:elasticsearch和solr。这两款都是基于lucene的搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作,修改、添加、保存、查询等等都十分类似。就好像都是支持sql语言的两种数据库软件。只要学会其中一个另一个很容易上手。

从实际企业使用情况来看,elasticSearch的市场份额逐步在取代solr,国内百度、京东、新浪都是基于elasticSearch实现的搜索功能。国外就更多了 像维基百科、GitHub、Stack Overflow等等也都是基于ES的。

2.1. elastic

Elastic有一条完整的产品线及解决方案:Elasticsearch、Kibana、Logstash等,前面说的三个就是大家常说的ELK技术栈。实现企业海量日志的处理分析的解决方案。大数据领域的重要一份子。

Elastic官网:https://www.elastic.co/cn/

在这里插入图片描述

文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.6/index.html

2.2 下载

我们只需要用到elasticsearch和kibana,es还需要中文的处理插件ik分词器,我们选择讲解的版本是7.6.2的,kibana、elasticsearch和IK分词器的版本号要一致,否则可能带来兼容性问题

另外,es需要jdk1.8以上的环境(虚拟机中必须安装jdk并配置了环境变量)

下载地址如下:

2.2.1 elasticsearch

https://www.elastic.co/cn/downloads/past-releases#elasticsearch

在这里插入图片描述

2.2.2 kibana地址

https://www.elastic.co/cn/downloads/past-releases#kibana

在这里插入图片描述

2.2.3 ik中文分词器地址

https://github.com/medcl/elasticsearch-analysis-ik/tags

2.3 安装elasticsearch

把课前资料中的elasticsearch、kibana拷贝到/opt目录下

在这里插入图片描述

2.3.1 安装elasticsearch

cd /opt/elasticsearch/
rpm -ivh elasticsearch-7.6.2-x86_64.rpm 

2.3.2 elasticsearch配置jdk:

elasticsearch7.X版本需要jdk11以上的版本,安装过的es目录下自带jdk11:

ll /usr/share/elasticsearch

在这里插入图片描述

配置jdk路径:

vim /etc/sysconfig/elasticsearch
# 修改javahome为(/usr/share/elasticsearch/jdk)

在这里插入图片描述

2.3.3 elasticsearch配置:

切换到/etc/elasticsearch目录下,看到两个配置文件jvm.options和elasticsearch.yml

在这里插入图片描述

1、首先修改jvm.options。 elasticsearch默认占用所有内存,导致虚拟机很慢,可以改的小一点。

vim /etc/elasticsearch/jvm.options

在这里插入图片描述

2、再修改elasticsearch.yml

修改yml配置的注意事项:每行必须顶格,不能有空格":"后面必须有一个空格**

  • 集群名称,同一集群名称必须相同(集群配置)

在这里插入图片描述

  • 单个节点名称 (集群配置)

在这里插入图片描述

  • 如果配置集群了,一定要设置当前节点为主节点

在这里插入图片描述

  • 保存es数据和日志文件的目录(不修改,使用默认的)

在这里插入图片描述

  • 把bootstrap自检程序关掉(提高启动速度)
# 修改原配置
bootstrap.memory_lock: false
# 新增配置
bootstrap.system_call_filter: false

在这里插入图片描述

  • 默认只允许本机访问,修改为0.0.0.0后则可以远程访问;端口使用默认:9200

在这里插入图片描述

  • 配置集群列表,这里只有一个。可以配置计算机名,也可以配置ip,推荐ip,主机名是你计算机名,一定不可以写错!!

在这里插入图片描述

  • elasticsearch服务器硬盘剩余空间不足5%,会抛出cluster_block_exception异常,在elasticsearch.yml最后配置取消硬盘检查:(新增)
cluster.routing.allocation.disk.threshold_enabled: false

在这里插入图片描述

2.3.4 设置es访问配置文件的权限

必须设置!!!!!!!!!!!!!!!!

chmod -R 777 /etc/elasticsearch/

在这里插入图片描述

2.3.5. 启动

启动elasticsearch:systemctl start elasticsearch

如果启动未成功,请去查看相关日志

cat  /var/log/elasticsearch/{cluster-name}.log

例如:这里cluster-name配置的是my-es,那么就是指:

cat  /var/log/elasticsearch/my-es.log文件

tail -n行数 -f 日志文件名

一般是配置文件的错误,按照上面的配置检查一遍

2.3.6. 测试

浏览器访问:http://192.168.1.179:9200

在这里插入图片描述

如果访问失败:

​ 原因1:防火墙可能没有关闭

​ 原因2:配置文件 network.hosts修改错误或者 集群节点主机名配置错误

2.4 安装kibana

Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。

解压缩进入kibana的config目录:

cd /opt/elasticsearch/
# 解压缩
tar -zxvf kibana-7.6.2-linux-x86_64.tar.gz
# 切换进config目录
cd kibana-7.6.2-linux-x86_64/config/
  • 默认端口号:5601

在这里插入图片描述

  • 修改配置文件,允许访问的ip地址

​ 修改为虚拟机IP地址

在这里插入图片描述

  • 配置elasticsearch服务器列表:

在这里插入图片描述

  • 启动:切换到kibana的bin目录下
cd /opt/elasticsearch/kibana-7.6.2-linux-x86_64/bin
# 会输出日志,并独占当前窗口(--allow-root表示允许使用root用户启动)
./kibana --allow-root  
# 后台启动
nohup  ./kibana --allow-root  &

  • 注意:不要使用ps -ef | grep kibana查询kibana进程,因为kibana的进程不叫kibana

在这里插入图片描述

必须先启动elasticSearch 启动kibana后才可以访问

  • 测试:通过浏览器访问kibana

    http://192.168.1.170:5601

在这里插入图片描述

点击左边菜单DevTools

在这里插入图片描述

在Console中,执行默认的DSL语句得到结果

在这里插入图片描述

表示es启动正常,并且与kibana连接正常。

2.5 IK分词器

2.5.1. 中文问题

大家先不管语法,我们先测试一波。

在kibana控制台输入下面的请求:

GET _analyze
{
  "text": "我是中国人"
}

运行得到结果:

{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "中",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "国",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "人",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    }
  ]
}

2.5.2. 中文分词器

elasticsearch本身自带的中文分词,就是单纯把中文一个字一个字的分开,根本没有词汇的概念。但是实际应用中,用户都是以词汇为条件,进行查询匹配的,如果能够把文章以词汇为单位切分开,那么与用户的查询条件能够更贴切的匹配上,查询速度也更加快速。

因此一般我们会用第三方提供的分词器:

在这里插入图片描述

IK分词器使用最多。

2.5.3. IK分词器

Lucene的IK分词器早在2012年已经没有维护了,现在我们要使用的是在其基础上维护升级的版本,并且开发为ElasticSearch的集成插件了,与Elasticsearch一起维护升级,版本也保持一致:7.6.2。

上传课前资料中的elasticsearch-analysis-ik-7.6.2.tar.gz包到/usr/share/elasticsearch/plugins目录下,

在这里插入图片描述

并解压到:/usr/share/elasticsearch/plugins/

# 切换到目录下
cd /usr/share/elasticsearch/plugins/
# 解压缩到指定目录下
unzip elasticsearch-analysis-ik-7.6.2.zip -d ik-analyzer
# 一定要删除压缩包
rm -rvf elasticsearch-analysis-ik-7.6.2.zip

在这里插入图片描述

重启elasticsearch :

systemctl restart elasticsearch.service

再次测试:

ik_smart: 最粗粒度的拆分

ik_max_word:最细粒度的拆

GET _analyze
{
  "analyzer": "ik_smart", 
  "text": "我是中国人"
}

在这里插入图片描述

ik分词器还提供了更详尽的分词方式:ik_max_word

GET _analyze
{
  "analyzer": "ik_max_word", 
  "text": "我是中国人"
}

分词效果:

{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "中国人",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "中国",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "国人",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 4
    }
  ]
}

分词更完美!!

2.5.4. 自定义词库

中文语言在不断发展,新的词汇不断涌现,怎么不断完善分词需求?

ik分词器提供了重要的扩展能力:

扩展词典(新创建词功能): 有些词IK分词器不识别 例如:“尚硅谷”,“夺笋哪”,“针不戳”

停用词典(停用某些词功能): 有些词不需要建立索引 例如:“哦”,“啊”,“的”

elasticsearch加载ik分词器插件时,ik会读取一个配置文件,这个配置文件在ik分词器根目录的config目录下:

在这里插入图片描述

打开该配置文件:

在这里插入图片描述

这里有两种方式配置扩展词典和停用词典:

  1. 本地方式:ext_dict配置扩展词典,ext_stopwords配置停用词典。
  2. 远程方式:tomcat或者nginx

第一种方式每次修改词典都要重启搜索服务,不推荐。推荐使用nginx的方式,并发量大,修改内容不需要重启。

第一步:利用nginx搭建远程词库。

vim /usr/local/nginx/conf/nginx.conf

在这里插入图片描述

需要在nginx根目录下创建对应的elasticsearch目录

mkdir -p /usr/local/nginx/elasticsearch/ext
# 创建扩展字典文件
vim /usr/local/nginx/elasticsearch/ext/ext_dict.txt

在这里插入图片描述

添加扩展词典,每行一个关键词

在这里插入图片描述

测试:

在这里插入图片描述

第二步:在ik分词器中引用远程词库

进入ik分词器的conf目录:cd /usr/share/elasticsearch/plugins/ik-analyzer/config/

vim /usr/share/elasticsearch/plugins/ik-analyzer/config/IKAnalyzer.cfg.xml

在这里插入图片描述

重启elasticsearch服务,再次测试

在这里插入图片描述

分词效果就是好!​

添加新词条后,es只会对新增的数据用新词分词。历史数据是不会重新分词的。如果想要历史数据重新分词。需要执行:

POST {index}/_update_by_query?conflicts=proceed

3. elasticsearch基本操作

3.1. 基本概念

Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。

对比关系:

索引(indices)----------------------Databases 数据库

  类型(type)--------------------------Table 数据表 [7.x废弃]

     文档(Document)----------------------Row 行

	    字段(Field)-------------------------Columns 列 

要注意的是:Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对你的数据进行分片和副本操作,当你向集群添加新数据时,数据也会在新加入的节点中进行平衡。

3.2. 索引操作(indeces)

3.2.1. 查询索引

查看es中有哪些索引库:

GET /_cat/indices?v

在这里插入图片描述

es 中会默认存在一个名为.kibana和.kibana_task_manager的索引

表头的含义

字段名含义说明
healthgreen(集群完整) yellow(单点正常、集群不完整) red(单点不正常)
status是否能使用
index索引名
uuid索引统一编号
pri主节点几个
rep从节点几个
docs.count文档数
docs.deleted文档被删了多少
store.size整体占空间大小
pri.store.size主节点占

3.2.2. 创建索引

PUT /索引名

参数可选:指定分片及副本,默认分片为3,副本为2。

{
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 2
      }
}

演示:

PUT /atguigu
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 2
  }
}
# 创建成功后如下图所示

在这里插入图片描述

再次查询,可以看到刚刚创建的索引:

在这里插入图片描述

3.2.3. 查看索引具体信息

GET /索引名

在这里插入图片描述

或者,我们可以使用*来查询所有索引具体信息

3.2.4. 删除索引

DELETE /索引库名

演示:

在这里插入图片描述

查看atguigu:

在这里插入图片描述

3.3. 映射配置(_mapping)

索引有了,接下来肯定是添加数据。但是,在添加数据之前必须定义映射。

什么是映射?

映射是定义文档的过程,文档包含哪些字段,这些字段是否保存,是否索引,是否分词等

只有配置清楚,Elasticsearch才会帮我们进行索引库的创建(不一定)

3.3.1. 创建映射字段

PUT /索引库名/_mapping
{
  "properties": {
    "字段名": {
      "type": "类型",
      "index": true,
      "store": true,
      "analyzer": "分词器"
    }
  }
}

字段名:类似于列名,properties下可以指定许多字段。

每个字段可以有很多属性。例如:

  • type:类型,String(text keyword) Numeric(long integer float double) date boolean
  • index:是否索引,默认为true
  • store:是否存储,默认为false,即使为false也会存储到_source中,如果为true则会额外存储一份
  • analyzer:分词器,这里使用ik分词器:ik_max_word或者ik_smart

示例

发起请求:

PUT atguigu/_mapping
{
  "properties": {
    "title": {
      "type": "text",
      "analyzer": "ik_max_word"
    },
    "images": {
      "type": "keyword",
      "index": "false"
    },
    "price": {
      "type": "long"
    }
  }
}

响应结果:

{
  "acknowledged": true
}

3.3.2. 查看映射关系

语法:

GET /索引库名/_mapping

示例:

GET /atguigu/_mapping

响应:

{
  "atguigu" : {
    "mappings" : {
      "properties" : {
        "images" : {
          "type" : "keyword",
          "index" : false
        },
        "price" : {
          "type" : "double"
        },
        "title" : {
          "type" : "text",
          "analyzer" : "ik_max_word"
        }
      }
    }
  }
}

3.4. 新增文档(document)

有了索引、类型和映射,就可以对文档做增删改查操作了。

3.4.1. 基本玩法

如果我们想要自己新增的时候指定id,可以这么做:

POST /索引库名/_doc/id值
{
    ...
}

演示:

在这里插入图片描述

查询:小米手机的id是我们指定的id

在这里插入图片描述

  • _source:源文档信息,所有的数据都在里面。
  • _id:这条文档的唯一标识,与文档自己的id字段没有关联

3.4.2. 智能判断

事实上Elasticsearch非常智能,你不需要给索引库设置任何mapping映射,它也可以根据你输入的数据来判断类型,动态添加数据映射。

测试一下:

POST /atguigu/_doc/2
{
    "title":"小米手机",
    "images":"http://image.jd.com/12479122.jpg",
    "price":2899,
    "stock": 200,
    "saleable":true,
    "attr": {
        "category": "手机",
        "brand": "小米"
    }
}

我们额外添加了stock库存,saleable是否上架,attr其他属性几个字段。

来看结果:GET /atguigu/_search

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "atguigu",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.jd.com/12479122.jpg",
          "price" : 2899,
          "stock" : 200,
          "saleable" : true,
          "attr" : {
            "category" : "手机",
            "brand" : "小米"
          }
        }
      },
      {
        "_index" : "atguigu",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://xiaomi.com/xiaomi.jpg",
          "price" : 2999
        }
      }
    ]
  }
}

再看下索引库的映射关系: GET /atguigu/_mapping

{
  "atguigu" : {
    "mappings" : {
      "properties" : {
        "attr" : {
          "properties" : {
            "brand" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "category" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        },
        "images" : {
          "type" : "keyword",
          "index" : false
        },
        "price" : {
          "type" : "double"
        },
        "saleable" : {
          "type" : "boolean"
        },
        "stock" : {
          "type" : "long"
        },
        "title" : {
          "type" : "text",
          "analyzer" : "ik_max_word"
        }
      }
    }
  }
}

stock,saleable,attr都被成功映射了。

如果是字符串类型的数据,会添加两种类型:text + keyword。如上例中的category 和 brand

3.5. 删除数据

删除使用DELETE请求,同样,需要根据id进行删除:

语法

DELETE /索引库名/_doc/id值

示例:

DELETE /atguigu/_doc/2

结果:

{
  "_index" : "atguigu",
  "_type" : "_doc",
  "_id" : "2",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 3,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 3,
  "_primary_term" : 1
}

3.6 更新文档

语法:

POST /索引库名/_update/id值
{
	doc:{
		"属性":"值"
	}
}
POST /atguigu/_update/1
{
  "doc": {
    "title":"aaa"
  }
}

结果:

{
  "_index" : "atguigu",
  "_type" : "_doc",
  "_id" : "2",
  "_version" : 3,
  "result" : "updated",
  "_shards" : {
    "total" : 3,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 5,
  "_primary_term" : 1
}

4. 查询

之前已经见识了查询功能

查询所有:

GET /{index}/_search

根据id查询:

GET /{index}/_doc/{id}

除了上述简单查询之外。elasticsearch作为搜索引擎,最复杂最强大的功能就是搜索查询功能。包括:匹配查询、词条查询、模糊查询、组合查询、范围查询、高亮、排序、分页等等查询功能。

基本查询语法如下:

GET /索引库名/_search
{
    "query":{
        "查询类型":{
            "查询条件":"查询条件值"
        }
    }
}

这里的query代表一个查询对象,里面可以有不同的查询属性

  • 查询类型:
    • 例如:match_allmatchtermrange 等等
  • 查询条件:查询条件会根据类型的不同,写法也有差异

查询结果:

  • took:查询花费时间,单位是毫秒
  • time_out:是否超时
  • _shards:分片信息
  • hits:搜索结果总览对象
    • total:搜索到的总条数
    • max_score:所有结果中文档得分的最高分
    • hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
      • _index:索引库
      • _type:文档类型
      • _id:文档id
      • _score:文档得分
      • _source:文档的源数据

4.1. 数据准备

POST /atguigu/_bulk
{"index":{"_id":1}}
{ "title":"小米手机", "images":"http://image.jd.com/12479122.jpg", "price":1999, "stock": 200, "attr": { "category": "手机", "brand": "小米" } }
{"index":{"_id":2}}
{"title":"超米手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "小米" } }
{"index":{"_id":3}}
{ "title":"小米电视", "images":"http://image.jd.com/12479122.jpg", "price":3999, "stock": 400, "attr": { "category": "电视", "brand": "小米" } }
{"index":{"_id":4}}
{ "title":"小米笔记本", "images":"http://image.jd.com/12479122.jpg", "price":4999, "stock": 200, "attr": { "category": "笔记本", "brand": "小米" } }
{"index":{"_id":5}}
{ "title":"华为手机", "images":"http://image.jd.com/12479122.jpg", "price":3999, "stock": 400, "attr": { "category": "手机", "brand": "华为" } }
{"index":{"_id":6}}
{ "title":"华为笔记本", "images":"http://image.jd.com/12479122.jpg", "price":5999, "stock": 200, "attr": { "category": "笔记本", "brand": "华为" } }
{"index":{"_id":7}}
{ "title":"荣耀手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "华为" } }
{"index":{"_id":8}}
{ "title":"oppo手机", "images":"http://image.jd.com/12479122.jpg", "price":2799, "stock": 400, "attr": { "category": "手机", "brand": "oppo" } }
{"index":{"_id":9}}
{ "title":"vivo手机", "images":"http://image.jd.com/12479122.jpg", "price":2699, "stock": 300, "attr": { "category": "手机", "brand": "vivo" } }
{"index":{"_id":10}}
{ "title":"华为nova手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "华为" } }

4.2. 匹配查询(match)

条件匹配

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  }
}

查询出很多数据,不仅包括小米手机,而且与小米或者手机相关的都会查询到,说明多个词之间是or的关系。

某些情况下,我们需要更精确查找,我们希望这个关系变成and,可以这样做:

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": {
        "query": "小米手机",
        "operator": "and"
      }
    }
  }
}

查询结果:

{
  "took" : 19,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.7494967,
    "hits" : [
      {
        "_index" : "atguigu",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.7494967,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.jd.com/12479122.jpg",
          "price" : 1999,
          "stock" : 200,
          "attr" : {
            "category" : "手机",
            "brand" : "小米"
          }
        }
      }
    ]
  }
}

4.3. 词条查询(term)

term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串。

GET /atguigu/_search
{
    "query":{
        "term":{
            "price": 4999
        }
    }
}

4.4. 范围查询(range)

range 查询找出那些落在指定区间内的数字或者时间

GET /atguigu/_search
{
    "query":{
        "range": {
            "price": {
                "gte":  1000,
                "lt":   3000
            }
    	}
    }
}

range查询允许以下字符:

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

4.5. 布尔组合(bool)

布尔查询又叫组合查询

bool把各种其它查询通过must(与)、must_not(非)、should(或)的方式进行组合

GET /atguigu/_search
{
    "query":{
        "bool":{
        	"must": [
        	  {
        	    "range": {
        	      "price": {
        	        "gte": 1000,
        	        "lte": 3000
        	      }
        	    }
        	  },
        	  {
        	    "range": {
        	      "price": {
        	        "gte": 2000,
        	        "lte": 4000
        	      }
        	    }
        	  }
        	]
        }
    }
}

注意:一个组合查询里面只能出现一种组合,不能混用

4.6. 过滤(filter)

所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter方式:

GET /atguigu/_search
{
  "query": {
    "bool": {
      "must": {
        "match": { "title": "小米手机" }
      },
      "filter": {
        "range": {
          "price": { "gt": 2000, "lt": 3000 }
        }
      }
    }
  }
}

注意:filter中还可以再次进行bool组合条件过滤。

4.7. 排序(sort)

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

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  },
  "sort": [
    {
      "price": { "order": "desc" }
    },
    {
      "_score": { "order": "desc"}
    }
  ]
}

4.8. 分页(from/size)

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  },
  "from": 2,
  "size": 2
}

from:从那一条开始

size:取多少条

4.9. 高亮(highlight)

查看百度高亮的原理:

在这里插入图片描述

发现:高亮的本质是给关键字添加了标签,在前端再给该标签添加样式即可。

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米"
    }
  },
  "highlight": {
    "fields": {"title": {}}, 
    "pre_tags": "<em>",
    "post_tags": "</em>"
  }
}

fields:高亮字段

pre_tags:前置标签

post_tags:后置标签

查询结果如下:

在这里插入图片描述

4.10. 结果过滤(_source)

默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source的所有字段都返回。

如果我们只想获取其中的部分字段,可以添加_source的过滤

GET /atguigu/_search
{
  "_source": ["title","price"],
  "query": {
    "term": {
      "price": 2699
    }
  }
}

返回结果,只有两个字段:

{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "atguigu",
        "_type" : "goods",
        "_id" : "9",
        "_score" : 1.0,
        "_source" : {
          "price" : 2699,
          "title" : "vivo手机"
        }
      }
    ]
  }
}

5. 聚合(aggregations)

聚合可以让我们极其方便的实现对数据的统计、分析。例如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现实时搜索效果。

5.1. 基本概念

Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫,一个叫度量

桶(bucket)

桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个,例如我们根据国籍对人划分,可以得到中国桶英国桶日本桶……或者我们按照年龄段对人进行划分:010,1020,2030,3040等。

Elasticsearch中提供的划分桶的方式有很多:

  • Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
  • Histogram Aggregation:根据数值阶梯分组,与日期类似
  • Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
  • Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
  • ……

bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量

度量(metrics)

分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量

比较常用的一些度量聚合方式:

  • Avg Aggregation:求平均值
  • Max Aggregation:求最大值
  • Min Aggregation:求最小值
  • Percentiles Aggregation:求百分比
  • Stats Aggregation:同时返回avg、max、min、sum、count等
  • Sum Aggregation:求和
  • Top hits Aggregation:求前几
  • Value Count Aggregation:求总数
  • ……

5.2. 聚合为桶

首先,我们按照手机的品牌attr.brand.keyword来划分

GET /atguigu/_search
{
    "size" : 0,
    "aggs" : { 
        "brands" : { 
            "terms" : { 
              "field" : "attr.brand.keyword"
            }
        }
    }
}
  • size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
  • aggs:声明这是一个聚合查询,是aggregations的缩写
    • brands:给这次聚合起一个名字,任意。
      • terms:划分桶的方式,这里是根据词条划分
        • field:划分桶的字段

结果:

{
  "took" : 23,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brands" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "华为",
          "doc_count" : 4
        },
        {
          "key" : "小米",
          "doc_count" : 4
        },
        {
          "key" : "oppo",
          "doc_count" : 1
        },
        {
          "key" : "vivo",
          "doc_count" : 1
        }
      ]
    }
  }
}
  • hits:查询结果为空,因为我们设置了size为0
  • aggregations:聚合的结果
  • brands:我们定义的聚合名称
  • buckets:查找到的桶,每个不同的品牌字段值都会形成一个桶
    • key:这个桶对应的品牌字段的值
    • doc_count:这个桶中的文档数量

5.3. 桶内度量

前面的例子告诉我们每个桶里面的文档数量,这很有用。 但通常,我们的应用需要提供更复杂的文档度量。 例如,每种品牌手机的平均价格是多少?

因此,我们需要告诉Elasticsearch使用哪个字段使用何种度量方式进行运算,这些信息要嵌套在内,度量的运算会基于内的文档进行

现在,我们为刚刚的聚合结果添加 求价格平均值的度量:

GET /atguigu/_search
{
    "size" : 0,
    "aggs" : { 
        "brands" : { 
            "terms" : { 
              "field" : "attr.brand.keyword"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                }
            }
        }
    }
}
  • aggs:我们在上一个aggs(brands)中添加新的aggs。可见度量也是一个聚合
  • avg_price:聚合的名称
  • avg:度量的类型,这里是求平均值
  • field:度量运算的字段

结果:

{
  "took" : 82,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brands" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "华为",
          "doc_count" : 4,
          "avg_price" : {
            "value" : 3999.0
          }
        },
        {
          "key" : "小米",
          "doc_count" : 4,
          "avg_price" : {
            "value" : 3499.0
          }
        },
        {
          "key" : "oppo",
          "doc_count" : 1,
          "avg_price" : {
            "value" : 2799.0
          }
        },
        {
          "key" : "vivo",
          "doc_count" : 1,
          "avg_price" : {
            "value" : 2699.0
          }
        }
      ]
    }
  }
}

可以看到每个桶中都有自己的avg_price字段,这是度量聚合的结果

5.4. 桶内嵌套桶

刚刚的案例中,我们在桶内嵌套度量运算。事实上桶不仅可以嵌套运算, 还可以再嵌套其它桶。也就是说在每个分组中,再分更多组。

比如:我们想统计每个品牌都生产了那些产品,按照attr.category.keyword字段再进行分桶

GET /atguigu/_search
{
    "size" : 0,
    "aggs" : { 
        "brands" : { 
            "terms" : { 
              "field" : "attr.brand.keyword"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                },
                "categorys": {
                  "terms": {
                    "field": "attr.category.keyword"
                  }
                }
            }
        }
    }
}

部分结果:

{
  "took" : 27,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brands" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "华为",
          "doc_count" : 4,
          "categorys" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "手机",
                "doc_count" : 3
              },
              {
                "key" : "笔记本",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 3999.0
          }
        },
        {
          "key" : "小米",
          "doc_count" : 4,
          "categorys" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "手机",
                "doc_count" : 2
              },
              {
                "key" : "电视",
                "doc_count" : 1
              },
              {
                "key" : "笔记本",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 3499.0
          }
        },
        {
          "key" : "oppo",
          "doc_count" : 1,
          "categorys" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "手机",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 2799.0
          }
        },
        {
          "key" : "vivo",
          "doc_count" : 1,
          "categorys" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "手机",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 2699.0
          }
        }
      ]
    }
  }
}
  • 我们可以看到,新的聚合categorys被嵌套在原来每一个brands的桶中。
  • 每个品牌下面都根据 attr.category.keyword字段进行了分组
  • 我们能读取到的信息:
    • 华为有4中产品
    • 华为产品的平均售价是 3999.0美元。
    • 其中3种手机产品,1种笔记本产品

**综合练习:**查询有货的小米手机前五条记录,标题关键字高亮显示,按照价格排序,只需要标题价格图片属性。并查询品牌分类桶。

6. SpringData-Elasticsearch

目前市面上有两类客户端

一类是TransportClient 为代表的ES原生客户端,不能执行原生dsl语句必须使用它的Java api方法。

另外一种是以Rest Api为主的missing client,最典型的就是jest。 这种客户端可以直接使用dsl语句拼成的字符串,直接传给服务端,然后返回json字符串再解析。

两种方式各有优劣,但是最近elasticsearch官网,宣布计划在7.0以后的版本中废除TransportClient。以RestClient为主。

​ RestClient: jest, java low level client,java high level client

​ springboot又提供了 EsRestTemplate 简化了es操作,底层使用了java high level client实现

​ 还提供了EsRepostitory

在这里插入图片描述

由于原生的Elasticsearch客户端API非常麻烦(https://www.elastic.co/guide/en/elasticsearch/client/index.html)。所以这里直接学习Spring提供的套件:Spring Data Elasticsearch。

https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#repositories

spring-data-Elasticsearch 使用之前,必须先确定版本,elasticsearch 对版本的要求比较高。

每个springboot场景启动器(引入了一组依赖并且配置了版本号的maven项目)

项目开发时会引入多个场景启动器,如果多个场景器引入了 同一个依赖但是版本不同,由于maven自动解决依赖冲突会按照先声明者路径短者优先的原则 挑选一个版本使用。没有被用到的版本的依赖 如果项目代码中使用了它的新特性会导致代码运行错误

后面的谷粒学院项目:springboot2.3.6.RELEASE springcloud Hoxton.SR9 springcloudalibaba:2.2.6.RELEASE

springboot和elasticsearch的版本对照:

在这里插入图片描述

6.1. 创建module

在gmall工程下创建一个模块:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

引入依赖:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atguigu</groupId>
    <artifactId>es-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>es-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 如果不手动添加如下依赖,可能报错: Failed to resolve org.junit.platform:junit-platform-launcher:1.6.3 -->
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

在application.properties中添加配置

# 单机情况下
spring.elasticsearch.rest.uris=http://192.168.1.170:9200
# 集群情况下
#spring.elasticsearch.rest.uris[0]=http://192.168.1.170:9200
#spring.elasticsearch.rest.uris[1]=http://192.168.1.171:9200

6.2. 实体类

在这里插入图片描述

@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
@Document(indexName = "user", shards = 3, replicas = 2)
public class User {

    @Id
    private Long id;
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String name;
    @Field(type = FieldType.Integer)
    private Integer age;
    @Field(type = FieldType.Keyword, index = false)
    private String password;
}

Spring Data通过注解来声明字段的映射属性,有下面的三个注解:

  • @Document 作用在类,标记实体类为文档对象,一般有四个属性
    • indexName:对应索引库名称
    • shards:分片数量,默认1
    • replicas:副本数量,默认1
  • @Id 作用在成员变量,标记一个字段作为id主键
  • @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
    • type:字段类型,取值是枚举:FieldType
    • index:是否索引,布尔类型,默认是true
    • store:是否存储,布尔类型,默认是false
    • analyzer:分词器名称:ik_max_word

6.3. ElasticsearchRestTemplate

6.3.1 创建索引及映射

@SpringBootTest
class EsDemoApplicationTests {

    // ElasticsearchTemplate是TransportClient客户端 已过期
    // ElasticsearchRestTemplate是RestHighLevel客户端
    @Autowired
    ElasticsearchRestTemplate restTemplate;

    @Test
    void contextLoads() {
        // 索引库操作对象
        IndexOperations ops = this.restTemplate.indexOps(User.class);
        ops.create(); // 初始化索引库
        ops.putMapping(indexOps.createMapping()); // 声明创建映射
    }
    //新增文档
    @Test
    void save(){
        restTemplate.save(new User(1L,"zhangsan",20,"zhangsan@126.com"));
    }
    //根据id查询文档
    @Test
    void get(){//根据id查找
        User user = restTemplate.queryForObject(GetQuery.getById("1"), User.class);
        System.out.println(user);
    }

}

6.3.2 查询文档

//查询所有
@Test
void testMatchAll(){
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
    builder.withQuery(QueryBuilders.matchAllQuery());
    SearchHits<User> search = elasticsearchRestTemplate.search(builder.build(), User.class);
    List<SearchHit<User>> searchHits = search.getSearchHits();
    searchHits.forEach(s->{
        User user = s.getContent();
        String id = s.getId();
        System.out.println(user);
        System.out.println(id);
    });
}
//匹配查询
@Test
void testMatch() {
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
    builder.withQuery(QueryBuilders.matchQuery("username", "cuicui"));
    SearchHits<User> searchHits = elasticsearchRestTemplate.search(builder.build(), User.class);
    List<SearchHit<User>> hitList = searchHits.getSearchHits();
    hitList.forEach(hit->{
        User user = hit.getContent();
        System.out.println(user);
    });
}
//布尔查询、过滤查询
@Test
void testBoolFilter(){
    NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
    builder.withQuery(QueryBuilders.boolQuery()
                      .must(QueryBuilders.matchQuery("username","cuicui"))//布尔组合
                      .filter(QueryBuilders.rangeQuery("age").gte(22)));//过滤查询
    SearchHits<User> searchHits = elasticsearchRestTemplate.search(builder.build(), User.class);
    System.out.println(searchHits.getTotalHits());
    List<SearchHit<User>> hits = searchHits.getSearchHits();
    hits.forEach(h->{
        System.out.println(h.getContent());
    });
}

6.3.3 复杂查询

匹配、过滤、分页、高亮、聚合度量查询

@Autowired
ElasticsearchRestTemplate restTemplate;
@Test
void testSearch(){
    // 自定义搜索查询构建器
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    // 放入搜索条件
    queryBuilder.withQuery(QueryBuilders.matchQuery("name", "冰冰").operator(Operator.AND));
    // 放入年龄的过滤条件
    queryBuilder.withFilter(QueryBuilders.rangeQuery("age").gte(19).lte(23));
    // 放入排序条件
    queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC));
    // 分页
    queryBuilder.withPageable(PageRequest.of(0, 3));
    // 高亮
    queryBuilder.withHighlightBuilder(new HighlightBuilder().field("name").preTags("<em>").postTags("</em>"));

    // 添加聚合条件
    queryBuilder.addAggregation(AggregationBuilders.terms("pwdAgg").field("password"));
    //打印设置的Filter的DSL语句
    System.out.println(queryBuilder.build().getFilter().toString());
    //打印设置的查询的DSL语句
    System.out.println(queryBuilder.build().getQuery().toString());
    // 执行查询操作
    SearchHits<User> hits = this.restTemplate.search(queryBuilder.build(), User.class);

    // 获取搜索结果集
    List<SearchHit<User>> searchHits = hits.getSearchHits();
    System.out.println("总共命中:" + hits.getTotalHits());
    // 遍历打印
    searchHits.forEach(searchHit -> {
        System.out.println("===========================================");
        User user = searchHit.getContent();
        System.out.println("高亮前:" + user);
        List<String> names = searchHit.getHighlightField("name");
        user.setName(names.get(0));
        System.out.println("高亮后:" + user);
    });

    // 解析聚合结果集
    Aggregations aggregations = hits.getAggregations();
    ParsedStringTerms pwdAgg = aggregations.get("pwdAgg");
    pwdAgg.getBuckets().forEach(bucket -> {
        System.out.println("--------------------------------------------");
        System.out.println("桶的名称:" + bucket.getKeyAsString());
        System.out.println("桶中的文档数量:" + bucket.getDocCount());
    });
}

NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体

Page<item>:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:

  • totalElements:总条数
  • totalPages:总页数
  • Iterator:迭代器,本身实现了Iterator接口,因此可直接迭代得到当前页的数据

6.4. Repository文档操作

官方文档:https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.repositories

Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。

在这里插入图片描述

其中ElasticsearchRepository接口功能最强大。该接口的方法包括:

在这里插入图片描述

定义UserRepository:

public interface UserRepository extends ElasticsearchRepository<User, Long> {

}

6.4.1. 新增

@Autowired
UserRepository userRepository;

@Test
void testAdd(){
    this.userRepository.save(new User(1l, "zhang3", 20, "123456"));
}

修改和新增是同一个接口,区分的依据就是id,这一点跟我们在页面发起PUT请求是类似的。

执行成功之后查看数据:

在这里插入图片描述

7.x版本会自动添加一个_class字段,但是不影响相关操作。

6.4.2. 删除

@Test
void testDelete(){
    this.userRepository.deleteById(1l);
}

6.4.3. 基本查询

在这里插入图片描述

查询一个:

@Test
void testFind(){
    System.out.println(this.userRepository.findById(1l).get());
}

基本查询扩展:

在这里插入图片描述

KeywordSampleElasticsearch Query String
AndfindByNameAndPrice{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }}
OrfindByNameOrPrice{ "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }}
IsfindByName{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }}
NotfindByNameNot{ "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }}
BetweenfindByPriceBetween{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}
LessThanfindByPriceLessThan{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }}
LessThanEqualfindByPriceLessThanEqual{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}
GreaterThanfindByPriceGreaterThan{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }}
GreaterThanEqualfindByPriceGreaterThan{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }}
BeforefindByPriceBefore{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}
AfterfindByPriceAfter{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }}
LikefindByNameLike{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}
StartingWithfindByNameStartingWith{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}
EndingWithfindByNameEndingWith{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}
Contains/ContainingfindByNameContaining{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}
In (when annotated as FieldType.Keyword)findByNameIn(Collection<String>names){ "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }}
InfindByNameIn(Collection<String>names){ "query": {"bool": {"must": [{"query_string":{"query": "\"?\" \"?\"", "fields": ["name"]}}]}}}
NotIn (when annotated as FieldType.Keyword)findByNameNotIn(Collection<String>names){ "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }}
NotInfindByNameNotIn(Collection<String>names){"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}
NearfindByStoreNearNot Supported Yet !
TruefindByAvailableTrue{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }}
FalsefindByAvailableFalse{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }}
OrderByfindByAvailableTrueOrderByNameDesc{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] }

6.4.4. 使用@Query注解

在这里插入图片描述

@Query("{\n" +
        "    \"range\": {\n" +
        "      \"age\": {\n" +
        "        \"gte\": \"?0\",\n" +
        "        \"lte\": \"?1\"\n" +
        "      }\n" +
        "    }\n" +
        "  }")
List<User> findByAgeBetween2(Integer age1 , Integer age2);

6.5. RestHighLevelClient

如果是6.8.x推荐使用原生RestHighLevelClient客户端

在这里插入图片描述

@SpringBootTest
class EsDemoApplicationTests2 {

    // springboot-elasticsearch 对应原生客户端提供了支持,直接注入即可使用
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    // jackson工具类
    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Test
    void testRestHighLevelClient() throws IOException {
        // 搜索条件构建器
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 构建搜索请求体,设置索引库 并设置搜索条件构建器
        SearchRequest searchRequest = new SearchRequest(new String[]{"user"}, sourceBuilder);
        // 向构建器中放入搜索条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        sourceBuilder.query(boolQueryBuilder);
        boolQueryBuilder.must(QueryBuilders.matchQuery("name", "冰冰").operator(Operator.AND));
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("age").gte(19).lte(23));

        // 排序
        sourceBuilder.sort("age", SortOrder.DESC);

        // 分页
        sourceBuilder.from(2);
        sourceBuilder.size(2);

        // 高亮
        sourceBuilder.highlighter(new HighlightBuilder().field("name").preTags("<em>").postTags("</em>"));

        // 聚合
        sourceBuilder.aggregation(AggregationBuilders.terms("pwdAgg").field("password"));

        System.out.println(sourceBuilder);
        // 执行搜索功能,返回响应对象
        SearchResponse response = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println(response);

        // 查询结果集
        SearchHits hits = response.getHits();
        System.out.println("一共命中:" + hits.getTotalHits());
        SearchHit[] hitsHits = hits.getHits();
        for (SearchHit hitsHit : hitsHits) {
            String json = hitsHit.getSourceAsString();
            User user = MAPPER.readValue(json, User.class);
            System.out.println("高亮前:" + user);

            // 高亮结果集
            Map<String, HighlightField> highlightFields = hitsHit.getHighlightFields();
            HighlightField highlightField = highlightFields.get("name");
            user.setName(highlightField.fragments()[0].string());
            System.out.println("高亮后:" + user);
        }

        // 聚合结果集
        Aggregations aggregations = response.getAggregations();
        ParsedStringTerms pwdAgg = aggregations.get("pwdAgg");
        List<? extends Terms.Bucket> buckets = pwdAgg.getBuckets();
        buckets.forEach(bucket -> {
            System.out.println("桶的key:" + bucket.getKeyAsString());
            System.out.println("桶中的元素数量:" + bucket.getDocCount());
        });
    }

}

上述程序在7.x中会报错:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "_class" (class com.atguigu.es.demo.pojo.User), not marked as ignorable (4 known properties: "id", "age", "name", "password"])
 at [Source: (String)"{"_class":"com.atguigu.es.demo.pojo.User","id":3,"name":"李冰冰","age":20,"password":"123456"}"; line: 1, column: 12] (through reference chain: com.atguigu.es.demo.pojo.User["_class"])

在这里插入图片描述

意思:User实体类中没有_class字段,反序列化时就会出错。

解决方法如下:

在这里插入图片描述

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

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

相关文章

AI Agent LangChain使用方法记录

B站教程OpenAI官网获取密钥&#xff1a; OPENAI官网获取KEY 报错“Did not find openai_api_key, please add an environment variable OPENAI_API_KEY”

智能网红主播直播手机:助您轻松卖货、卖团购卷、拓客利器!

在当下快速发展的电商行业中&#xff0c;直播销售已经成为无可忽视的一大趋势。智能网红主播直播手机的出现&#xff0c;让人们无需拥有专业设备和经验&#xff0c;便可轻松参与直播销售&#xff0c;享受销售乐趣。本文将介绍智能网红主播直播手机的操作简单、易上手以及其在卖…

JSON格式化输出html——数组+对象+JSON字符串+汉字——基础积累——@pgrabovets/json-view

昨天写了一篇关于JSON格式化输出到页面上——数组对象JSON字符串汉字——基础积累的文章&#xff0c;效果是可以实现的 但是如果要实现右侧部分的展开/折叠&#xff0c;则可以使用到下面的插件了pgrabovets/json-view github链接&#xff1a;https://github.com/pgrabovets/j…

【C语言】水仙花数

问题 水仙花数&#xff08;Narcissistic number&#xff09;也被称为超完全数字不变数&#xff08;pluperfect digital invariant, PPDI&#xff09;、自恋数、自幂数或阿姆斯壮数数&#xff08;Armstrong number&#xff09;。 它是指一个n位数&#xff08;n≥3&#xff09;…

什么样的开放式耳机好用舒服?五款高人气质量绝佳产品力荐!

​随着人们越来越注重个人的身体健康问题&#xff0c;掀起了一股运动浪潮&#xff0c;现在大家都会喜欢跑跑步&#xff0c;运动一下使自己的身体更好&#xff0c;那么在运动时候如果能有音乐听的话&#xff0c;人们的运动状态就能达到更好的水平。鉴于传统入耳式耳机给用户带来…

特征模态分解(FMD):一种小众而又新颖的分解方法

​ 声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 今天为大家介绍一个小众而又新颖的信号分…

【全开源】Java同城信息付费系统家政服务房屋租赁房屋买卖房屋装修信息发布平台小程序APP公众号源码

同城信息付费系统&#xff1a;家政服务的新篇章 在快节奏的现代生活中&#xff0c;家政服务已成为许多家庭不可或缺的一部分。然而&#xff0c;如何快速、准确地找到合适、可靠的家政服务人员&#xff0c;一直是困扰着许多家庭的问题。为了解决这一难题&#xff0c;我们推出了…

JVS物联网、无忧企业文档、规则引擎5.14功能新增说明

项目介绍 JVS是企业级数字化服务构建的基础脚手架&#xff0c;主要解决企业信息化项目交付难、实施效率低、开发成本高的问题&#xff0c;采用微服务配置化的方式&#xff0c;提供了 低代码数据分析物联网的核心能力产品&#xff0c;并构建了协同办公、企业常用的管理工具等&am…

谷歌 I/O 2024大会全面硬钢OpenAI;腾讯宣布旗下的混元文生图大模型;阿里巴巴技术下的AI自动视频剪辑工具

✨ 1: 谷歌 I/O 2024 谷歌 I/O 2024 发布了众多新技术&#xff0c;包括 Gemini AI、大语言模型和通用 AI 智能体等&#xff0c;全面颠覆搜索体验。 谷歌 I/O 2024发布会带来许多令人兴奋的新功能和技术创新&#xff1a; Gemini 1.5 Pro&#xff1a;一个极其强大的语言模型&am…

消息队列选型

一、要解决的问题 1.1 异步 分析&#xff1a; 需要根据场景来判断。若整体链路的逻辑中&#xff0c;某些逻辑是不需要强实时的&#xff0c;滞后一段时间是允许的&#xff0c;同时又不会对用户带来不好的体验&#xff0c;那么可以使用MQ完成异步操作。 例如&#xff1a;秒杀场…

x264 帧类型代价计算原理:slicetype_frame_cost 函数分析

slicetype_frame_cost 函数 函数功能 这个函数的核心是计算编码一系列帧(从 p0 到p1,以 b 为当前帧)的代价 cost,并根据这个代价 cost来辅助帧类型决策。它考虑了运动搜索的结果、帧间和帧内预测的成本,并且可以并行处理以提高效率。该函数在帧类型决策、MBtree 分析、场…

消防物资存储|基于SSM+vue的消防物资存储系统的设计与实现(源码+数据库+文档)

消防物资存储系统 目录 基于SSM&#xff0b;vue的消防物资存储系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1用户功能模块 2 管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介…

天锐绿盾 | 如何防止电脑内文件遭到泄露?

天锐绿盾是一款专为企业设计的数据防泄漏软件系统&#xff0c;它通过一系列综合性的安全措施来有效防止电脑内文件遭到泄露。 PC地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是天锐绿盾防止文件泄露的主要功能和方法&a…

酒店刷脸设备遭批量扔进「垃圾桶」,为啥所有人都叫好……

最近&#xff0c;不知道柴油们有没有关注到这么一个热点&#xff0c;就是大量酒店曾经动辄几千上万买来的刷脸设备&#xff0c;大批量的被挂在二手平台1折甩卖了…… 昔日花大几千&#xff0c;甚至上万买来的设备&#xff0c;如今年低至三四百&#xff1f;打折打到脚底板&#…

8.2 GOOGLE(SAML应用)登录联携AWS用户池(Amazon Cognito)

GOOGLE&#xff08;SAML应用&#xff09;登录联携AWS用户池&#xff08;Amazon Cognito&#xff09; 目录一、参考资料二、创建和配置AWS用户池1. 创建用户池2. 在用户池中创建联合身份提供商3. 在用户池中创建应用程序集成 三、创建和配置GOOGLE SAML应用1. 创建GOOGLE SAML应…

nodemon运行ts文件

https://juejin.cn/post/7035637086451400734 nodemon经常用来调试js文件&#xff0c;大家都是知道的&#xff0c;但是用nodemon来调试ts文件&#xff0c;大家试过吗&#xff1f; 如果直接运行nodemon index.ts是会报错的。 ts 复制代码 //index.ts console.log(1) 需要全局…

Find My OBD|苹果Find My技术与OBD结合,智能防丢,全球定位

OBD是英文On-Board Diagnostics的缩写&#xff0c;中文翻译为“车载自动诊断系统”。这个系统将从发动机的运行状况随时监控汽车是否尾气超标&#xff0c;一旦超标&#xff0c;会马上发出警示。当系统出现故障时&#xff0c;故障(MIL)灯或检查发动机(Check Engine)警告灯亮&…

JAVA面试库

1、基础 1.1、面向对象编程有哪些特性 1、抽象 抽象就是对同一个目标的共有的属性、特征、方法、功能、行为等进行抽取并归纳总结&#xff0c;它是一种将复杂现实简单化为模型的过程&#xff0c;它关注的是对象行为&#xff0c;而不用关注具体的实现细节。 在面向对象编程中…

【千帆AppBuidler】零代码构建AI人工智能应用,全网都在喊话歌手谁能应战,一键AI制作歌手信息查询应用

欢迎来到《小5讲堂》 这是《千帆平台》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景创建应用平台地址随机生成快速创建应用头像应用名称应用描述…

S32K3的中断向量表

S32K312的中断向量表所在文件名称是S32K312_COMMON.h 中断向量内容是&#xff1a; /*!* addtogroup Interrupt_vector_numbers Interrupt vector numbers* {*//** Interrupt Number Definitions */ #define NUMBER_OF_INT_VECTORS 229 /**< Number of inte…