全文检索-ElasticSearch入门

文章目录

1. 基于Lucene的全文检索

1.1 全文检索概念

    非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行检索,从而达到搜索相对较快的目的。从非结构化数据中提取出来并重新组织的信息,我们称之为索引。

例如:字典中的拼音表、偏旁部首表就相当于字典的索引。

    这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

注意:索引创建过程十分耗时,但是一旦创建有利于复用。全文检索主要处理的是查询,所以虽然创建索引耗时,但却值得。否则使用顺序扫描的方式去查询非结构化数据是非常慢的。

因此,在大数据量的情况下,如果你需要用到搜索,就可以用到全文检索引擎。


    1998年9月4日,谷歌公司在美国成立,正如大家所说的,它是一家做搜索引擎起家的公司。无独有偶,有个叫 Doug Cutting 的美国工程师,也迷上了搜索引擎,他做了一个用于文本搜索的函数库(可以理解成一个软件的功能组件),名为Lucene(ElasticSearch和Solr都是基于它封装的)。

注:故事来自鲜枣课堂公众号。此外,hadoop的作者也是 Doug Cutting。

    Lucene 是用 Java 编写的信息检索工具包(不包含搜索引擎系统),目的是为各种中小型应用软件增添全文检索功能,因为好用且开源得到了程序员的欢迎。2001年,Lucene 成为了Apache软件基金会jakarta项目的一个子项目

    Nutch 是一个建立在Lucene核心之上的网页搜索应用程序,可以直接download使用。它在Lucene的基础上增加了网络爬虫和一些网页相关的功能,目的就是像谷歌一样从一个简单的站内检索推广到全球网络的搜索上。

注:这里是本人看了B站狂神说相关视频了解到的一个前菜,个人记住了3个词:大数据 = 海量存储 + 计算,除此之外还有一些技术的关联历史,推荐大家去了解下,有利于对技术的发展追溯和认知,因为这是一个大数据的背景,包括我们之后学的ES也是处于这种背景之下。



1.2 全文检索过程

在这里插入图片描述



1.3 全文检索相关概念

  1. 索引库

    即将索引存储在磁盘上的一系列文件,里面保存了建立好的索引信息以及文档对象

    一个索引库可类比于数据库中的一张表。

    在这里插入图片描述

  2. 文档对象

    获取原始内容的目的是为了索引,在创建索引前需要将原始内容创建成文档,文档中包括很多个域,域中存储内容,且每个文档都有唯一的编号,即文档id。

    一个文档可类比于表里的一条记录。

    在这里插入图片描述

  3. 域对象

    域(Field)是索引库中存储数据的最小单位,其数据类型可以分成数值类型和文本类型两种。一般查询的字段都是文本类型的,域还有如下属性:

    域可以类比成记录的字段。

    • 是否分词

      是否对域的内容进行分词处理,前提是我们要对域的内容进行查询。如果是类似商品编号、商品价格可以不用分词。

      例如,标题——中国人民银行,可以分词为:

      • 标题:中国;
      • 标题:人民;
      • 标题:银行;
      • 标题:中国人民。
    • 是否索引

      将Field分析后的词或整个Field进行索引,只有索引才能搜索到

      例如:商品名称、商品简介分析后进行索引,订单号、身份证号可以不用分词但是也要索引,它们将来都可以作为查询的条件。

    • 是否存储

      是否将Field值存储在文档中,存储在文档中的Field才能从文档中获取到。

      例如商品名、订单号,凡是将来要从文档中获取的域都要存储。

    • term对象

      从文档对象中拆分出来的每个词都是一个term,term中包含两部分:文档域名和单词的内容。

      注意:不同域中拆分出来的统一单词是不同的term,term是创建索引的关键词对象。



1.4 全文检索的使用场景

  1. 搜索的数据对象是大量的非结构化的文本数据。
  2. 文件记录量达到数十万或数百万个甚至更多。
  3. 支持大量基于交互式文本的查询。
  4. 需求非常灵活的全文搜索查询。
  5. 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
  6. 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。



2. ElasticSearch

2.1 ElasticSearch概念

    ElasticSearch(ES)是一个开源的、高扩展的、分布式的全文检索引擎,它可以==近乎实时地存储、检索数据==,并且由于本身扩展性很好,可以扩展到上百台服务器,所以能够处理PB级别的数据。ES 也可以使用 Java 开发,并使用Lucene作为其核心,从而实现所有索引和检索的功能。ES 的目的是通过简单的Restful API 来隐藏Lucene的复杂性,使得全文搜索变得十分简单

注:2016年,ES已经成为了排名第一的搜索引擎类应用。



2.2 ElasticSearch应用案例

  • GitHub:2013年初抛弃了Solr,采用 ElasticSearch 来做PB级别的搜索,“GitHub使用 ElasticSearch 搜索20TB的数据,包括13 亿文件和1300亿行代码”。
  • 百度:目前广泛使用 ElasticSearch 作为文本数据分析,采集百度所有服务器其上的各类指标数据和用户自定义数据,通过对各种数据进行多维分析战术,辅助定位分析实例异常或业务层面异常。单集群最大100台机器,200ES节点,每天导入30TB+数据。
  • 新浪:使用 ElasticSearch 分析处理32亿实时日志。
  • 阿里巴巴:使用 ElasticSearch 构建自己的日志采集和分析体系。
  • ……



2.3 ElasticSearch和Solr对比

  • 都是基于 Lucene 而改造、封装的全文检索引擎;

  • Solr 利用Zookeeper进行分布式管理;ElasticSearch 自身带有分布式协调管理功能

  • Solr 支持更多格式的数据(json、xml、csv…);ElasticSearch 仅支持json数据格式

  • Solr 官方提供更多的功能;ElasticSearch 本身更注重核心功能,高级功能多有第三方插件提供;

    例如:IK分词器、图形化界面需要Kibana友好支持。

  • Solr 在传统的搜索应用中表现优于 ElasticSearch,在处理实时搜索应用时效率低于ElasticSearch。

  • 单纯地对已有的数据进行搜索的时候,Solr更快;当实时建立索引的时候,Solr会产生IO阻塞,查询性能相对较差,ES有明显的优势。

  • 随着数据量的增加,Solr的搜索效率会变得更低,而ES没有明显变化。

  • Solr查询快,但更新索引时慢,适合电商等查询多的应用;ES建立索引快(查询慢)实时性查询快,适合facebook、新浪等搜索。



2.4 ElasticSearch 术语

2.4.1概述

    ElasticSearch 是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在 ElasticSearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。

    借用了一位仁兄的图

在这里插入图片描述

注意:和1.3小节有细微差别。一切都是json。在这里提一嘴,不要一上来就记各种名词,容易绕懵(我一开始上来就懵逼了),通过类比进行记忆并理解各自对应的内容、作用即可。


2.4.2 Index 索引

一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。


2.4.3 Type 类型

    在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。

注意:Elasticsearch 6.X中,一个 index 下已经只能包含一个Type,Elasticsearch 7.X中, Type的概念已经被删除了。因为这里的版本用的是6.7.1,所以提一嘴,之后学完高版本之后再回过头来更新。

为什么要删除映射类型?


2.4.4 Document 文档

    一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(JavaScript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。在一个Index/Type里面,你可以存储任意多的文档。注意,尽管一个文档物理上存在于一个索引之中,但是文档必须被索引/赋予一个索引的Type(看版本)。


2.4.5 Field 字段

    相当于是数据表的字段,对文档数据根据不同属性进行的分类标识

2.4.6 Mapping 映射

    Mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理ES里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。

注意:映射可类比于数据库表格中对字段的定义。


2.4.7 NRT 接近实时

    ElasticSearch 是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)。

2.4.8 Cluster集群

    一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识(一个人就是一个集群!戏精一点的说法就是一人一城既视感hhhh,只不过默认这个集群里只有一个节点而已)。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。

注意:集群名一般默认就是“elasticsearch”,但是在2.5小节中发现默认是”docker-cluster“,这与ES的版本和构建类型有关。后面先统一称之为“elasticsearch”。

2.4.9 Node 节点

    一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。

    一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
    在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何 ElasticSearch 节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。


2.4.10 Shards&Replicas 分片和复制

    一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,ElasticSearch提供了将索引划分成多份的能力,这就叫做分片

    当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。分片很重要,主要有两方面的原因:

  1. 允许你水平分割/扩展你的内容容量。
  2. 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。

    至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由ElasticSearch管理,对于作为用户的你来说,这些都是透明的。

注意:实际上,一个分片是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得ES在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字。那么像我这种菜鸡第一次看到肯定在想啥又是倒排索引??持续懵逼…

倒排索引(有人说正确的翻译应该叫反转索引):

    ES使用的是一种倒排索引的结构(Lucene底层就用了倒排),这种结构使用于快速的全文搜索。一个索引由文档中所有不重复的列表构成,对于每个词都有一个包含它的文档列表。

​    为了创建倒排索引,我们首先将每个文档拆分成独立的词,然后创建一个包含所有不重复词条的索引列表,然后列出每个词条在哪个文档有出现

    这样的话,我们在搜索某些词的时候,会根据这个列表去查找满足条件的文档,例如,词abc在文档1和文档2都出现了,词def只在文档1出现了,那么我们在搜索abc def 的时候就会搜索到这两个表格,而且文档1的权重(score)更高,因为它更符合条件。

​ 因此,查找倒排索引之后的数据会比直接查原始数据快得多。所以抓住重点,什么是倒排索引:文档key—>词value,查文档的时候就反过来根据词再对文档排一次(权重高低)

总之,一个ES索引(库概念)是由多个Lucene索引组成的(因为多个分片)。一般情况下,说到的索引指的是ES索引而不是Lucene索引。

    在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,ElasticSearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制

    复制之所以重要,有两个主要原因:

  1. 在分片/节点失败的情况下,提供了高可用性。

    因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。

  2. 扩展你的搜索量/吞吐量。

    因为搜索可以在所有的复制上并行运行。总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量

    默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。

注意:需要考虑版本的不同,7.0以上不会默认帮你创建分片了,需要自己创建索引的时候设置。


2.5 ElasticSearch安装

这里采用Docker进行安装ES 6.7.1。

  1. Docker拉取ES镜像;

    docker pull elasticsearch:6.7.1
    
  2. 创建ES数据的数据卷目录,并设置权限;

    这里挂载数据卷一定要给权限。

    mkdir -p /data/elasticsearch/data
    
    sudo chmod 777 /data/elasticsearch/
    sudo chmod 777 /data/elasticsearch/data/
    

    在这里插入图片描述

  3. 后台运行ES容器;

    docker run -d --name elasticsearch \
    -p 9200:9200 \
    -p 9300:9300 \
    -v /data/elasticsearch/data:/usr/share/elasticsearch/data \
    elasticsearch:6.7.1
    
  4. 遇到ES闪退,查看日志发现最大虚拟内存区域的数量太低了;

    在这里插入图片描述

    docker logs elasticsearch #查看日志
    
    vim /etc/sysctl.conf #追加以下内容 (限制一个进程可以拥有的VMA(虚拟内存区域)的数量 )
    vm.max_map_count=655360
    
    sysctl -p #修改内核参数马上生效
    
  5. 访问9200端口;

    9200端口是ES的Web管理平台端口,9300是ES的服务默认端口。

    注意:这里使用的构建类型是docker,默认集群名是”docker-cluster“。

    在这里插入图片描述

  6. 修改elasticsearch.yml文件

    docker exec -it elasticsearch /bin/bash #进入容器
    dir # 查看文件目录
    cd config #进入config目录
    ls #查看文件
    vi elasticsearch.yml #修改elasticsearch.yml文件
    
    node.name: es0 #节点名称
    network.host: 192.168.31.35 #本节点ip地址
    http.host: 0.0.0.0
    transport.host: 0.0.0.0
    
    cluster.name: my-elasticsearch #修改集群名
    node.master: true #是否作为集群的主节点
    node.data: true #是否存储数据
    
    discovery.zen.ping.unicast.hosts: ["192.168.31.35:9300"] #配置集群中节点的位置,节点之间可以相互通信
    
    #跨域设置
    http.cors.enabled: true #默认false
    http.cors.allow-origin: "*"
    
    exit
    docker restart elasticsearch
    

    注:ES中的config/elasticsearch.ymal配置项说明

    在这里插入图片描述

  7. 设置容器开启自动重启。

    docker update --restart=always elasticsearch
    
  8. 修改JVM内存。

    vi jvm.options
    
    -Xms512m #设置JVM初始可用内存为512M,默认1G
    -Xmx512m #设置JVM最大可用内存为512M,默认1G
    
    exec
    docker restart elasticsearch
    

    注意:也可以在创建容器的时候就指定,即加上:-e ES_JAVA_POTS="-Xms512m -Xmx512m"



2.6 ElasticSearch的客户端操作

    实际开发中,主要有三种方式可以作为 ElasticSearch 服务的客户端:

  • 第一种,elasticsearch-head插件;

    注意:ES需要设置跨域才行。

  • 第二种,使用 ElasticSearch 提供的Restful接口直接访问;

    使用postman进行客户端操作,需要注意的是:

    1. 6.x版本的是否分词设置成 “index”:true或false。
    2. 测试一下标椎分词器的效果需要使用post请求。
  • 第三种,使用 ElasticSearch 提供的API进行访问。


2.7 IK分词器

2.7.1 IK分词器概念

    IKAnalyzer是一个开源的、基于 Java 语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出 了3个大版本。最初,它是以开源项目 Lucene 为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为面向 Java 的公用分词组件,独立于 Lucene 项目,同时提供了对 Lucene 的默认优化实现。


2.7.2 IK分词器特性

  1. 采用了特有的“正向迭代最细粒度切分算法“,具有60万字/秒的高速处理能力。
  2. 采用了多子处理器分析模式,支持:英文字母(IP地址、Email、URL)、数字(日期,常用中文数量词,罗马数字,科学计数法),中文词汇(姓名、地名处理)等分词处理。
  3. 对中英联合支持不是很好,在这方面的处理比较麻烦。需再做一次查询,同时是支持个人词条的优化的词典存储,更小的内存占用。
  4. 支持用户词典扩展定义。
  5. 针对Lucene全文检索优化的查询分析器IKQueryParser;采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高Lucene检索的命中率。

2.7.3 IK分词器安装

  1. 下载 IK 分词器压缩包;

    注意:IK分词器下载地址ES和IK的版本对应

  2. 将IK分词器解压后上传至服务器,并改名字为ik;

    在这里插入图片描述

  3. 将ik目录拷贝到Docker容器的plugins目录下。

    docker cp ./ik elasticsearch:/usr/share/elasticsearch/plugins
    

    在这里插入图片描述


2.7.4 IK分词器测试

POST http://192.168.31.35:9200/_analyze

注意:这里使用的是6.7.1版本的,不同版本之间的方式不一样。

  • ik_smart:会做最粗粒度的拆分;

    携带参数如下:

    {
        "analyzer": "ik_smart",
        "text": "我是程序员"
    }
    

    在这里插入图片描述

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

    携带参数如下:

    {
        "analyzer": "ik_max_word",
        "text": "我是程序员"
    }
    

    在这里插入图片描述



2.8 Kibana

2.8.1 Kibana概念

    Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 ElasticSearch 协作。您可以使用 Kibana 对 ElasticSearch 索引中的数据进行搜索、查看、交互操作。您可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现。

    Kibana 可以使大数据通俗易懂。它很简单,基于浏览器的界面便于您快速创建和分享动态数据仪表板来追踪 ElasticSearch 的实时数据变化。搭建 Kibana 非常简单。您可以分分钟完成 Kibana 的安装并开始探索 Elasticsearch 的索引数据 — 没有代码、不需要额外的基础设施。


2.8.2 Kibana安装

这里以Docker创建Kibana容器为例。

  1. 拉取Kibana镜像;

    docker pull docker.io/kibana:6.7.1
    

    注意:Kibana的版本。

  2. 创建并运行Kibana容器。

    docker run -d -e ELASTICSEARCH_URL=http://192.168.31.35:9200 --name kibana --restart=always -p 5601:5601 kibana:6.7.1
    
  3. 如果启动失败,检查kibana.yml文件。

    docker exec -it kibana /bin/bash
    cd config
    vi kivana.yml
    
    elasticsearch.hosts: [ "http://192.168.31.35:9200" ]#修改elasticsearch.hosts的值为自己的es的ip
    

在这里插入图片描述



2.8.3 Kibana使用

2.8.3.1 配置索引

        要使用Kibana,您必须至少配置一个索引。索引用于标识 Elasticearch 索引以运行搜索和分析。它们还用于配置字段。

在这里插入图片描述

在这里插入图片描述


2.8.3.2数据搜索

        Discover为数据搜索部分,可以对日志信息进行搜索操作。可以使用Discover实现数据搜索过滤和搜索条件显示以及关键词搜索,如下图:

在这里插入图片描述


2.8.3.3 数据可视化

在这里插入图片描述


2.8.3.4 控制台操作数据

在这里插入图片描述



2.8.4 使用DSL语句

2.8.4.1 Query DSL结构化查询

    Query DSL是一个 Java 开源框架用于构建类型安全的 SQL 查询语句。采用 API 代替传统的拼接字符串来构造查询语句。目前,Querydsl支持的平台包括JPA、JDO、SQL、Java Collections、RDF、Lucene、Hibernate Search。ElasticSearch 提供了一整套基于JSON 的 DSL 语言来定义查询。


2.8.4.2 常用快捷键
ctrl+i #自动缩进
ctrl+enter #提交请求
down #打开自动补全菜单
enter或tab #选中项自动补全
esc #关闭补全菜单

2.8.4.3 索引操作
  1. 查询所有索引;

    GET /_cat/indices?v
    

    在这里插入图片描述

  2. 新增索引;

    PUT /user
    

    在这里插入图片描述

    注意:从7.0.0版本开始,默认只有一个分片,而不是5个了,所以如果想要多个分片需要在创建索引的时候指定。

  3. 删除某个索引;

    DELETE /user
    

    在这里插入图片描述

  4. 创建映射;

    PUT /user/userinfo/_mapping
    {
      "properties": {
        "name": {
          "type": "text",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        },
        "city": {
          "type": "text",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        },
        "age": {
          "type": "long"
        },
        "description": {
          "type": "text",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        }
      }
    }
    

    在这里插入图片描述

    为什么要删除映射类型?各版本之间语句的使用有什么区别?


2.8.4.4 数据操作
  1. 新增文档数据;

    PUT /user/userinfo/1
    {
      "name": "李四",
      "age": 22,
      "city": "深圳",
      "description": "李四来自湖北武汉!"
    }
    
    PUT /user/userinfo/2
    {
      "name": "王五",
      "age": 35,
      "city": "深圳",
      "description": "王五家住在深圳!"
    }
    
    PUT /user/userinfo/3
    {
      "name": "张三",
      "age": 19,
      "city": "深圳",
      "description": "在深圳打工,来自湖北武汉"
    } 
    
    PUT /user/userinfo/4
    {
      "name": "张三丰",
      "age": 66,
      "city": "武汉",
      "description": "在武汉读书,家在武汉!"
    } 
    
    PUT /user/userinfo/5
    {
      "name": "赵子龙",
      "age": 77,
      "city": "广州",
      "description": "赵子龙来自深圳宝安,但是在广州工作!",
      "address": "广东省茂名市"
    } 
    
    PUT /user/userinfo/6
    {
      "name": "赵毅",
      "age": 55,
      "city": "广州",
      "description": "赵毅来自广州白云区,从事电子商务8年!"
    } 
    
    PUT /user/userinfo/7
    {
      "name": "赵哈哈",
      "age": 57,
      "city": "武汉",
      "description": "武汉赵哈哈,在深圳打工已有半年了,月薪7500!"
    }
    

    在这里插入图片描述

    注意:更新数据也可以使用新增操作,这种操作会将整个数据替换掉。

  2. 修改文档数据;

    使用POST更新某个列的数据。

    POST /user/userinfo/4/_update
    {
      "doc": {
        "name": "张三丰",
        "description": "在武汉读书,家在武汉!在深圳工作!"
      }
    }
    

    在这里插入图片描述

  3. 根据id查询,获取文档数据;

    GET /user/userinfo/4
    

    在这里插入图片描述

  4. 查询索引下的全部文档数据;

    GET /user/_search
    

    查询全部索引的全部文档数据:GET _search

    在这里插入图片描述

  5. 排序查询文档数据;

    GET /user/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": {
        "age": {
          "order": "desc"
        }
      }
    }
    

    在这里插入图片描述

  6. 分页查询文档数据;

    GET /user/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": {
        "age": {
          "order": "desc"
        }
      },
      "from": 0,
      "size": 2
    }
    
    • from:从下N的记录开始查询;
    • size:每页显示条数。

    在这里插入图片描述

  7. 删除文档数据;

    DELETE user/userinfo/7
    

    在这里插入图片描述

2.8.4.5 查询模式
  1. term查询;

    term主要用于精确匹配(不分词),如字符串、数值、日期等。

    不适合情况:1.列中除英文字符外有其它值 ;2.字符串值中有冒号或中文 ;3.系统自带属性如_version。

    GET _search
    {
      "query": {
        "term": {
          "city": "武汉"
        }
      }
    }
    

    在这里插入图片描述

  2. terms查询;

    terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配。

    GET _search
    {
      "query": {
        "terms": {
          "city": [
            "武汉",
            "广州"
          ]
        }
      }
    }
    

    在这里插入图片描述

  3. match查询;

    与term查询不同的是,match查询会使用映射中设置的分词器,对搜索值进行分词后再匹配查询

    GET _search
    {
      "query": {
        "match": {
          "city": "广州武汉"
        }
      }
    }
    

    在这里插入图片描述

  4. query_string查询;

    默认使用空格拆分成多个子项,并且每个子项都会去分词查询。可以通过 default_operator 指定子项之间的关系,默认是或 。match 查询只是分词以后查询,相当于 query_string 用空格隔开的一个个子项查询,query_string查询相当于match查询的增强版。

    GET _search
    {
        "query": {
            "query_string": {
                "default_field": "city",
                "query": "广州武汉"
            }
        }
    }
    

    query_string 和 match有什么区别?

  5. range 查询;

    range过滤允许我们按照指定范围查找一批数据,例如查询年龄范围。

    GET _search
    {
      "query": {
        "range": {
          "age": {
            "gte": 30,
            "lte": 57
          }
        }
      }
    }
    

    在这里插入图片描述

  6. exists查询;

    exists 过滤可以用于查找拥有某个域的数据

    GET _search
    {
      "query": {
        "exists": {
          "field": "address"
        }
      }
    }
    

    在这里插入图片描述

  7. bool 查询;

    bool 查询可以用来合并多个条件查询结果的布尔逻辑,它包含一下操作符:

    • must : 多个查询条件的完全匹配,相当于 and。
    • must_not : 多个查询条件的相反匹配,相当于 not。
    • should : 至少有一个查询条件匹配, 相当于 or。
    GET _search
    {
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "city": {
                  "value": "深圳"
                }
              }
            },
            {
              "range": {
                "age": {
                  "gte": 20,
                  "lte": 99
                }
              }
            }
          ]
        }
      }
    }
    

    在这里插入图片描述

  8. match_all 查询;

    可以查询到所有文档,是没有查询条件下的默认语句。

    前面已经用了很多次了,这里就不介绍了。

  9. prefix 查询;

    以什么字符开头的,可以更简单地用 prefix ,例如查询所有以张开始的用户描述。

    GET _search
    {
      "query": {
        "prefix": {
          "name": {
            "value": "张"
          }
        }
      }
    }
    

    在这里插入图片描述

  10. multi_match 查询。

    multi_match查询允许你做match查询的基础上同时搜索多个字段,在多个字段中同时查一个

    GET _search
    {
      "query": {
        "multi_match": {
          "query": "深圳",
          "fields": [
            "city",
            "description"
          ]
        }
      }
    }
    

    在这里插入图片描述

  11. filter查询。

    在查询前添加过滤条件后再进行查询。尽量使用过滤代替查询,因为多次过滤可以使用缓存,而且过滤不需要计算分数,从性能上来说过滤更好

    注意:一般使用查询(query)来进行==全文搜索或其它任何需要影响相关性得分==的搜索。除此以外的情况都使用过滤(filters) 。

    GET _search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match_all":{}
            }
          ], 
          "filter": {
            "range": {
                "age": {
                  "gte": 30,
                  "lte": 57
                }
              }
          }
        }
      }
    }
    

2.8.5 ElasticSearch原生API编程

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

    <groupId>com.lijinghua</groupId>
    <artifactId>es-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>6.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>6.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.24</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

2.8.5.2 创建客户端连接对象
private TransportClient client;

@Before
public void init() throws UnknownHostException {
    //(1)配置
    Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build();

    //(2)创建客户端连接对象
    client = new PreBuiltTransportClient(settings).addTransportAddress(
        new TransportAddress(InetAddress.getByName("192.168.31.35"), 9300));
}

2.8.5.3 创建索引Index
@Test
public void createIndex() throws UnknownHostException {

    //(3)使用api创建索引
    client.admin().indices().prepareCreate("index_test").get();

    //(4)资源释放
    client.close();
}

在这里插入图片描述


2.8.5.3 创建映射Mapping
@Test
public void createMapping() throws Exception {

    //(3)创建映射
    /**
         * {
         *     "mappings":{
         *         "article":{
         *             "properties": {
         *                 "id":{
         *                     "type":"long",
         *                     "store":true,
         *                 },
         *                 "title":{
         *                     "type":"text",
         *                     "store":true,
         *                     "index":"analyzed",
         *                     "analyzer":"ik_smart"
         *                 },
         *                 "content":{
         *                     "type":"text",
         *                     "store":true,
         *                     "index":"analyzed",
         *                     "analyzer":"ik_smart"
         *                 }
         *             }
         *         }
         *     }
         * }
         */
    XContentBuilder builder = XContentFactory.jsonBuilder()
        .startObject()
        .startObject("article")
        .startObject("properties")
        .startObject("id")
        .field("type", "long").field("store", true)
        .endObject()
        .startObject("title")
        .field("type", "text").field("store", true).field("index",true).field("analyzer",
                                                                              "ik_smart")
        .endObject()
        .startObject("content")
        .field("type", "text").field("store", true).field("index",true).field("analyzer",
                                                                              "ik_smart")
        .endObject()
        .endObject()
        .endObject()
        .endObject();

    //http://192.168.31.35:9200/index_test/article/_mapping
    client.admin().indices().preparePutMapping("index_test").setType("article").setSource(builder).get();
    //(4)资源释放
    client.close();
}

在这里插入图片描述



2.8.5.4 创建文档Document
  1. 方法一:使用 XContentBuilder 构建Document对象;

    /**
    * 使用 XContentBuilder 构建Document对象
    *
    * @Author: lijinghua
    * @Date: 2021/9/21
    */
    @Test
    public void createDocument() throws Exception {
        //(3)创建文档
        XContentBuilder builder = XContentFactory.jsonBuilder()
            .startObject()
            .field("id", 2l)
            .field("title", "北方入秋速度明显加快 多地降温幅度最多可达10度")
            .field("content", "阿联酋一架客机在纽约机场被隔离 10名乘客病倒")
            .endObject();
    
        //(4)把文档对象添加到索引库
        //http://192.168.31.35:9200/index_test/article/2
        client.prepareIndex().setIndex("index_test").setType("article").setId("2").setSource(builder).get();
    
        //(5)关闭客户端
        client.close();
    }
    

    在这里插入图片描述

  2. 方法二:使用实体转Json。

    1. 创建Article实体类;

      public class Article {
          private Long id;
          private String title;
          private String content;
          //getter/setter...
      }
      
    2. 添加 Jackson 依赖;

      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
          <version>2.8.1</version>
      </dependency>
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.8.1</version>
      </dependency>
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <version>2.8.1</version>
      </dependency>
      
    3. 创建文档对象。

      /**
      * 使用 实体类转json 构建Document对象
      *
      * @Author: lijinghua
      * @Date: 2021/9/21
      */
      @Test
      public void createDocument2() throws Exception {
          //(3)创建文档实体类
          Article article = new Article();
          article.setId(3l);
          article.setTitle("MH370坠毁在柬埔寨密林?中国一公司调十颗卫星去拍摄");
          article.setContent("警惕荒唐的死亡游戏!俄15岁少年输掉游戏后用电锯自杀");
      
          //(4)把实体类对象转换成json格式的字符串
          ObjectMapper objectMapper = new ObjectMapper();
          String jsonDocument = objectMapper.writeValueAsString(article);
          System.out.println(jsonDocument);
          
          //(5)使用client对象把文档写入索引库
          client.prepareIndex("index_test", "article", "3")
              .setSource(jsonDocument, XContentType.JSON).get();
          
          //(6)资源释放
          client.close();
      }
      

      在这里插入图片描述


2.8.5.5 查询文档
2.8.5.5.1 公共查询部分
private void search(QueryBuilder queryBuilder) {
    //(4)执行查询
    SearchResponse searchResponse = client.prepareSearch("index_test")
        .setTypes("article").setQuery(queryBuilder).get();

    //(5)获取查询结果
    SearchHits searchHits = searchResponse.getHits();
    System.out.println("查询结果总记录数:" + searchHits.getTotalHits());//取查询结果的总记录数
    //查询结果列表
    Iterator<SearchHit> iterator = searchHits.iterator();
    while (iterator.hasNext()) {
        SearchHit searchHit = iterator.next();
        //打印文档对象,以json格式输出
        System.out.println(searchHit.getSourceAsString());
        System.out.println("-----------文档的属性");
        Map<String, Object> document = searchHit.getSourceAsMap();
        System.out.println(document.get("id"));
        System.out.println(document.get("title"));
        System.out.println(document.get("content"));
    }

    //(6)资源释放
    client.close();
}

注意:可以发现,查询默认查询前十条。


2.8.5.5.2 term 查询
@Test
public void termQuery() {
    //(3)创建一个QueryBuilder对象:要搜索的字段、要搜索的关键词
    QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "女护士");

    search(queryBuilder);
}

2.8.5.5.3 queryString 查询
@Test
public void queryStringQuery() {
    //(3)创建一个QueryBuilder对象:要搜索的字段、要搜索的关键词
    QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("美丽的女护士").defaultField("title");

    search(queryBuilder);
}

2.8.5.5.4 match 查询
@Test
public void matchQuery() {
    //(3)创建一个QueryBuilder对象:要搜索的字段、要搜索的关键词
    QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "美丽的女护士");
    
    search(queryBuilder);
}

在这里插入图片描述


2.8.5.5.5 根据文档id查询
@Test
public void idsQuery() throws Exception {
    //(3)创建一个QueryBuilder对象:要搜索的id范围
    QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds("1", "2");
    search(queryBuilder);
}

在这里插入图片描述


2.8.5.5.6 分页查询
private void searchByPage(QueryBuilder queryBuilder, int pageNum, int pageSize) {
    //(4)执行查询
    SearchResponse searchResponse = client.prepareSearch("index_test")
        .setTypes("article").setQuery(queryBuilder).setFrom((pageNum-1)*pageSize).setSize(pageSize).get();

    //(5)获取查询结果
    SearchHits searchHits = searchResponse.getHits();
    System.out.println("查询结果总记录数:" + searchHits.getTotalHits());//取查询结果的总记录数
    //查询结果列表
    Iterator<SearchHit> iterator = searchHits.iterator();
    while (iterator.hasNext()) {
        SearchHit searchHit = iterator.next();
        //打印文档对象,以json格式输出
        System.out.println(searchHit.getSourceAsString());
        System.out.println("-----------文档的属性");
        Map<String, Object> document = searchHit.getSourceAsMap();
        System.out.println(document.get("id"));
        System.out.println(document.get("title"));
        System.out.println(document.get("content"));
    }

    //(6)资源释放
    client.close();
}

2.8.5.6 查询高亮显示

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

    ElasticSearch可以对查询出的内容中关键字部分进行标签和样式的设置,但是你需要告诉ElasticSearch使用什么标签对高亮关键字进行包裹。

在这里插入图片描述

在这里插入图片描述

注意:通过开发者模式查看发现,关键词使用了em标签。

@Test
public void multiMatchQuery() {
    //(3)创建一个QueryBuilder对象:要搜索的关键词、要搜索的字段(可变参数)
    QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery( "美丽的女护士","title","content");
    //search(queryBuilder);

    searchByHighLight(queryBuilder, 1, 5);
}
private void searchByHighLight(QueryBuilder queryBuilder, int pageNum, int pageSize) {
    //(4)高亮显示设置
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("title").field("content");//设置多个域高亮
    highlightBuilder.preTags("<em>");//设置标签样式
    highlightBuilder.postTags("</em>");

    //(5)执行查询
    SearchResponse searchResponse = client.prepareSearch("index_test")
        .setTypes("article").setQuery(queryBuilder)
        .setFrom((pageNum-1)*pageSize).setSize(pageSize)
        .highlighter(highlightBuilder).get();


    //(6)获取查询结果
    SearchHits searchHits = searchResponse.getHits();
    System.out.println("查询结果总记录数:" + searchHits.getTotalHits());//取查询结果的总记录数
    //查询结果列表
    Iterator<SearchHit> iterator = searchHits.iterator();
    while (iterator.hasNext()) {
        SearchHit searchHit = iterator.next();
        //打印文档对象,以json格式输出
        System.out.println("-----------文档-----------");
        System.out.println(searchHit.getSourceAsString());

        System.out.println("-----------高亮-----------");
        Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
        for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
            System.out.println(entry.getKey() + ":\t" +
                               Arrays.toString(entry.getValue().getFragments()));
        }

    }

    //(7)资源释放
    client.close();
}

在这里插入图片描述

在实际应用时,我们只需要判断这个字段存不存在高亮片段,例如title的高亮片段,如果存在就返回高亮的title,而不返回原来的title即可。



2.8.6 Spring Data ElasticSearch

参考网址

2.8.6.1 Spring Data概念

Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务。 Spring Data可以极大的简化 JPA 的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。

Spring Data的官网

在这里插入图片描述

在这里插入图片描述



2.8.6.2 Spring Data ElasticSearch概念

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

在作用上,可以类比于Myabtis-Plus对Mybatis的简化封装。


2.8.6.3 入门案例
2.8.6.3.1 POM文件
<?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.2.5.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.lijinghua</groupId>
	<artifactId>es-demo2</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>es-demo2</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.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
        
        <dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
        <dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
		</dependency>
	</dependencies>

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

2.8.6.3.2 创建ES配置类
# ip+端口
elasticSearch:
  host.port: 192.168.31.35:9200
  # Socket连接超时时间
  socketTimeout: 60
@Configuration
public class ElasticSearchClientConfig extends AbstractElasticsearchConfiguration {
    @Value("${elasticSearch.host.port}")
    private String hostAndPort;
    @Value("${elasticSearch.socketTimeout}")
    private long socketTimeout;

    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {

        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo(hostAndPort)
                .withSocketTimeout(Duration.ofSeconds(socketTimeout))
                .build();

        return RestClients.create(clientConfiguration).rest();
    }

    @Bean
    public ElasticsearchRestTemplate restTemplate() {
        return new ElasticsearchRestTemplate(elasticsearchClient());
    }
}

2.8.6.3.3 创建文档实体类
@Data
@Document(indexName = "index_springdata", type = "article")
public class Article {
    @Id
    @Field(type = FieldType.Long, store = true)
    private Long id;
    @Field(type = FieldType.Text, store = true, analyzer = "ik_smart")
    private String title;
    @Field(type = FieldType.Text, store = true, analyzer = "ik_smart")
    private String content;
}

2.8.6.3.4 编写Dao层

方法命名规则查询的基本语法findBy + 属性 + 关键词 + 连接符

关键字命名规则解释示例
andfindByField1AndField2根据Field1和Field2 获得数据findByTitleAndContent
orfindByField1OrField2根据Field1或Field2 获得数据findByTitleOrContent
isfindByField根据Field获得数据findByTitle
notfindByFieldNot根据Field获得补集数据findByTitleNot
betweenfindByFieldBetween获得指定范围的数据findByPriceBetween
lessThanEqualfindByFieldLessThan获得小于等于指定值的数据findByPriceLessThan
public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {

    List<Article> findByTitle(String title);

    List<Article> findByTitleOrContent(String title, String content);

    List<Article> findByTitleOrContent(String title, String content, Pageable pageable);
}

    除此之外,ElasticSearchRepository内置了其他CRUD方法,此外我们也可以利用ElasticSearchRestTemplate去使用。例如:

@Autowired
private ElasticsearchRestTemplate restTemplate;

@Test
public void createIndex(){
    // 创建索引
    restTemplate.createIndex(Article.class);//我们在实体类中已经声明了映射
}

注意: ElasticsearchTemplate使用的是 transport ,而ElasticsearchRestTemplate使用的是 HighLevelRestClient 。ElasticsearchTemplate当然可以使用了。但是,ES 官方从 v6.* 开始就deprecated了这个接口,且宣称从 v8.* 开始,将不再支持基于 transport 的接口。所以我们暂时用用还行,新项目还是不要再用了,改为使用基于 rest 接口的 ElasticsearchRestTemplate 比较好。参考文章

在这里插入图片描述



2.8.6.3.5 测试
@RunWith(SpringRunner.class)
@SpringBootTest
class EsDemo2ApplicationTests {
    @Autowired
    private ElasticsearchRestTemplate restTemplate;

    @Autowired
    private ArticleRepository articleRepository;

    @Autowired

    @Test
    public void createIndex() {
        // 创建索引
        restTemplate.createIndex(Article.class);
    }

    @Test
    public void addDocument() {
        for (int i = 10; i <= 20; i++) {
            //创建一个Article对象
            Article article = new Article();
            article.setId(i + 0l);
            article.setTitle("女护士路遇昏迷男子跪地抢救:救人是职责更是本能" + i);
            article.setContent("这是一个美丽的女护士妹妹" + i);
            //把文档写入索引库
            articleRepository.save(article);
        }
    }

    @Test
    public void deleteDocumentById() {
         articleRepository.deleteById(11l);
        //全部删除
        //articleRepository.deleteAll();
    }

    @Test
    public void findAll() {
        Iterable<Article> articles = articleRepository.findAll();
        articles.forEach(a -> System.out.println(a));
    }

    @Test
    public void testFindById() {
        Optional<Article> optional = articleRepository.findById(10l);
        Article article = optional.get();
        System.out.println(article);
    }

    @Test
    public void testFindByTitle() {
        //List<Article> list = articleRepository.findByTitle("美丽的女护士");
        List<Article> list = articleRepository.findByTitleLike("美丽的女护士");
        list.stream().forEach(a -> System.out.println(a));
    }

    @Test
    public void testFindByTitleOrContent() {
        Pageable pageable = PageRequest.of(1, 5);
        articleRepository.findByTitleOrContent("美丽的女护士", "女护士", pageable)
                .forEach(a -> System.out.println(a));
    }

    @Test
    public void testNativeSearchQuery() {
        //创建一个查询对象
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.queryStringQuery("女护士").defaultField(" title"))
                .withPageable(PageRequest.of(0, 15))
                .build();
        //执行查询
        List<Article> articleList = restTemplate.queryForList(query, Article.class);
        articleList.forEach(a -> System.out.println(a));
    }
}

2.8.6.4 聚合查询
2.8.6.4.1 实体类
@Data
@Document(indexName = "car_index", type = "car")
public class Car {
    @Id
    @Field(type = FieldType.Long, store = true)
    private Long id;
    @Field(type = FieldType.Text, store = true, analyzer = "ik_smart")
    private String name;
    @Field(type = FieldType.Text, store = true, analyzer = "ik_smart", fielddata = true)
    private String color;
    @Field(type = FieldType.Text, store = true, analyzer = "ik_smart", fielddata = true)
    private String brand;
    @Field(type = FieldType.Double, store = true)
    private Double price;
}

2.8.6.4.2 编写Dao层
public interface CarRepository extends ElasticsearchRepository<Car, Long> {
}

2.8.6.4.3 测试
@Autowired
private CarRepository carRepository;


@Test
public void initIndex() {
    // 创建索引
    carRepository.save(new Car(1l, "比亚迪A1", "红色", "比亚迪", 50000d));
    carRepository.save(new Car(2l, "比亚迪A2", "白色", "比亚迪", 70000d));
    carRepository.save(new Car(3l, "比亚迪A3", "白色", "比亚迪", 80000d));
    carRepository.save(new Car(4l, "比亚迪A4", "红色", "比亚迪", 60000d));
    carRepository.save(new Car(5l, "比亚迪A5", "红色", "比亚迪", 90000d));

    carRepository.save(new Car(6l, "宝马A1", "红色", "宝马", 10000d));
    carRepository.save(new Car(7l, "宝马A2", "黑色", "宝马", 20000d));
    carRepository.save(new Car(8l, "宝马A3", "黑色", "宝马", 30000d));
    carRepository.save(new Car(9l, "宝马A4", "红色", "宝马", 40000d));
    carRepository.save(new Car(10l, "宝马A5", "红色", "宝马", 50000d));
    carRepository.save(new Car(11l, "奔驰A1", "红色", "奔驰", 10000d));
    carRepository.save(new Car(12l, "奔驰A2", "黑色", "奔驰", 20000d));
    carRepository.save(new Car(13l, "奔驰A3", "黑色", "奔驰", 30000d));
    carRepository.save(new Car(14l, "奔驰A4", "红色", "奔驰", 40000d));
    carRepository.save(new Car(15l, "奔驰A5", "红色", "奔驰", 50000d));
}

在这里插入图片描述


2.8.6.4.4 划分桶
GET /car_index/car/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_all": {}
        }
      ]
    }
  },
  "aggs": {
    "group_by_bland": {
      "terms": {
        "field": "color"
      }
    }
  }
}

在这里插入图片描述

    Spring Data ElasticSearch代码实现如下:

@Test
public void testQuerySelfAggs() {
    //查询条件的构建器
    NativeSearchQueryBuilder queryBuilder = new
        NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery());

    //资源过滤:1.查询要包含的字段、2.要排除的字段
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, new String[]{"brand"}));

    //添加聚合条件,聚合名为group_by_color,按照分词:color进行聚合
    queryBuilder.addAggregation(AggregationBuilders.terms("group_by_color").field("color"));

    //执行查询,把查询结果直接转为聚合page
    AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carRepository.search(queryBuilder.build());

    //从所有的聚合中获取对应名称的聚合
    Terms agg = (Terms) aggPage.getAggregation("group_by_color");

    //从聚合的结果中获取所有的桶信息
    for (Terms.Bucket bucket : agg.getBuckets()) {
        System.out.println(bucket.getKeyAsString()+"  "+bucket.getDocCount());
    }
}

在这里插入图片描述

在这里插入图片描述


2.8.6.4.5 桶内度量
GET /car_index/car/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_all": {}
        }
      ]
    }
  },
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

其实就是嵌套的聚合

在这里插入图片描述

    Spring Data ElasticSearch代码实现如下:

@Test
public void testQuerySubAggs() {
    //查询条件的构建器
    NativeSearchQueryBuilder queryBuilder = new
        NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery());

    //资源过滤:1.查询要包含的字段、2.要排除的字段
    queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, new String[]{"brand"}));

    //添加聚合条件,聚合名为group_by_color,按照分词:color进行聚合
    queryBuilder.addAggregation(
        AggregationBuilders.terms("group_by_color").field("color")
        .subAggregation(AggregationBuilders.avg("avg_price").field("price"))//嵌套子聚合
    );

    //执行查询,把查询结果直接转为聚合page
    AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carRepository.search(queryBuilder.build());

    //从所有的聚合中获取对应名称的聚合
    Terms agg = (Terms) aggPage.getAggregation("group_by_color");

    //从聚合的结果中获取所有的桶信息
    for (Terms.Bucket bucket : agg.getBuckets()) {
        System.out.println(bucket.getKeyAsString()+"  "+bucket.getDocCount());
        Avg avg_price = (Avg) bucket.getAggregations().asMap().get("avg_price");
        System.out.println(avg_price.getValue());
        System.out.println("---------------");
    }
}

在这里插入图片描述

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GitHub是一个全球最大的开源代码托管平台,宝安街道的Geo表示地理信息,因此"GitHub宝安街道Geo"可以理解为宝安街道的地理信息在GitHub上的呈现和分享。 在GitHub上,宝安街道的地理信息可以通过各种方式展示和共享。首先,宝安街道的地理信息可以通过地图的形式展示在GitHub上。开发者可以创建地理信息的仓库,将街道的边界、建筑物、道路、公共设施等地理数据通过地图库展示出来,让其他开发者和用户可以查看和使用。 其次,宝安街道的地理信息还可以通过开源的GIS工具进行分析和处理。GitHub上有许多开源的GIS工具和库,如Leaflet、Mapbox等,开发者可以利用这些工具对宝安街道的地理信息进行地图制作、空间分析、地理编码等操作。这些工具具有灵活、易用和高度可定制的特点,可以帮助开发者更好地处理和展示地理信息。 此外,在GitHub上还存在许多与宝安街道地理信息相关的开源项目。开发者可以在GitHub上共享和发布与宝安街道地理信息相关的应用、算法、数据集等。其他人可以通过这些项目了解宝安街道的地理环境、发现潜在的问题或挑战,并共同参与解决或改进。 总而言之,"GitHub宝安街道Geo"表示在GitHub上展示和共享宝安街道地理信息的各种方式和工具。通过GitHub,我们可以更好地利用和共享地理信息,促进地理科学、地理应用和地理社区的发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值