石一歌的ElasticSearch笔记

Elasticsearch

概述

  • Doug Cutting
    • 开发Lucene
      • Lucene是一套用于全文检索搜寻开源程序库,由Apache软件基金会支持和提供
      • Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具
      • Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品
      • Lucene是一套信息检索工具包! 不包含搜索引擎系统!
    • 开发Nutch
      • Nutch是一个建立在Lucene核心之上的网页搜索应用程序,可以下载下来直接使用。它在Lucene的基础上加了网络爬虫和一些网页相关的功能,目的就是从一个简单的站内检索推广到全球网络的搜索上,就像Google一样。
    • 开发NDFS
      • 分布式文件存储系统,基于Google的GFS
    • 实现MapReduce
      • 大规模数据集的并行分析运算,基于Google的MapReduce
    • 开发Hadoop
      • 将NDFS和MapReduce进行了升级改造
    • 开发HBase
      • 分布式数据存储系统,用来处理海量数据的非关系型数据库,基于Google的BigTable,底层是HDFS
  • Elasticsearch
    • 简介
      • Elasticsearch是一个基于Apache Lucene(TM)实时分布式搜索和分析引擎。它让你以前所未有的速度处理大数据成为可能。它用于全文搜索、结构化搜索、分析以及将这三者混合使用。
    • 应用场景
      • 维基百科,类似百度百科,全文检索高亮搜索推荐
      • The Guardian (国外新闻网站) , 类似搜狐新闻,用户行为日志(点击,浏览,收藏,评论) +社交网络数据(对某某新闻的相关看法) ,数据分析,给到每篇新闻文章的作者,让他知道他的文章的公众反馈
      • Stack Overflow (国外的程序异常讨论论坛) , IT问题,程序的报错, 提交上去,有人会跟你讨论和回答,全文检索,搜索相关问题和答案,程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应的答案
      • GitHub (开源代码管理)
      • 电商网站,检索商品
      • 日志数据分析, logstash采集日志, ES进行复杂的数据分析, ELK技术, elasticsearch(搜索)+logstash(过滤)+kibana(可视化分析)
      • 商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户,比如说订阅牙膏的监控:如果高露洁牙膏的家庭套装低于50块钱,就通知我,我就去买
      • BI系统,商业智能, Business Intelligence
      • 国内:站内搜索(电商,招聘,门户),IT系统搜索(OA,CRM,ERP,等等),数据分析(ES热门的一个使用场景)
  • ELK
    • ELK是Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称。市面上也被成为Elastic Stack。
    • Elasticsearch是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。
    • Logstash是ELK的中央数据流引擎,用于从不同目标(文件/数据存储/MQ )收集的不同格式数据,经过过滤后支持输出以到不同目的地(文件/MQ/redis/elasticsearch/kafka等)。
    • Kibana可以将es的数据通过友好的页面展示出来 ,提供实时分析的功能。
  • Solr
    • Solr是Apache下的一个顶级开源项目,采用java开发,是基于Lucene的全文搜索服务器。Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展、并对索引、搜索性能进行了优化。可以独立运行,是一个独立的企业及搜索应用服务器,它对外提供类似于web-service的API接口。用户可以通过http请求,像搜索引擎服务器提交一定格式的文件,生成索引;也可以通过提出查找请求,并得到返回结果

安装

目录

  • bin 可执行文件文件

  • config 配置文件

    • log4j2.properties 日志配置文件

    • jvm.options java虚拟机配置文件

    • elasticsearch.yml ES配置文件

  • data 索引目录

  • jdk 自带jdk

  • lib 依赖jar包

  • logs 日志

  • modules 功能模块

  • plugins 插件

地址(7.17)

直接用Kibana代替elasticsearch-head

JDK(可选)

为了节约硬盘空间😝,删掉了自带的jdk,选择指定自带的jdk11

  • 查看文件\elasticsearch-7.17.0\elasticsearch-env\elasticsearch-7.17.0\elasticsearch-env.bat,直接配置环境变量ES_JAVA_HOME为本机的jdk11路径(7.17)

GC(可选)

  • 修改文件\elasticsearch-7.17.0\config\jvm.options如下

    ## GC configuration
    #8-13:-XX:+UseConcMarkSweepGC
    #8-13:-XX:CMSInitiatingOccupancyFraction=75
    #8-13:-XX:+UseCMSInitiatingOccupancyOnly
    
    ## G1GC Configuration
    # NOTE: G1 GC is only supported on JDK version 10 or later
    # to use G1GC, uncomment the next two lines and update the version on the
    # following three lines to your version of the JDK
    #10-13:-XX:-UseConcMarkSweepGC
    #10-13:-XX:-UseCMSInitiatingOccupancyOnly
    14-:-XX:+UseG1GC
    

跨域

  • \elasticsearch-7.17.0\config\elasticsearch.yml文件,添加配置如下

    http.cors.enabled: true
    http.cors.allow-origin: "*"
    

汉化(可选)

  • \kibana-7.17.0\config\kibana.yml文件,添加国际化配置如下

    i18n.locale: "zh-CN"
    

上面的操作做完后,双击elasticsearch.batkibana.bat,访问localhost:5601,启动成功

概念

Elasticsearch是面向文档的一种数据库,这意味着其不再需要行列式的表格字段约束。

ES会存储整个构造好的数据或文档,然而不仅仅是储存数据,这使得文档中每个数据可以被标识,进而可以被检索。在ES中,执行index,search,sort或过滤文档等操作都不是传统意义上的行列式的数据。

ES从根本上对数据的不同思考方式也正是他能应对复杂数据结构的全文检索的原因之一。

对比关系型数据库

Relational DB Elasticsearch
数据库(database) 索引(index)
表(tables) 类型(types,新版本中逐步弃用)
行(rows) 文档(documents)
字段(columns) 字段(file)

Elasticsearch(一般为集群)中可以包含多个索引(对应数据库) ,每个索引中可以包含多个类型(对应表) ,每个类型下又包含多个文档(对应行),每个文档中又包含多个字段(对应列)。

  • 物理设计

    • Elasticsearch在后台把每个索引划分成多个分片,每分分片可以在集群中的不同服务器间迁移(方便集群的搭建)

      实际上只建立一个索引它自己也是一个集群,默认名称就是elasticsearch

  • 逻辑设计

    • 一个索引类型中,包含多个文档

文档

Elasticsearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档, Elasticsearch中,文档有几个重要属性:

  • 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value
  • 可以是层次型的,一个文档中包含着文档,复杂的逻辑实体就是这么来的(即文档就是JSON格式的对象,可用fastjson进行自动转换自动)
  • 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在Elasticsearch中,对于字段是非常灵活的,有时候我们可以忽略该字段,或者动态的添加一个新的字段。

我们可以随意的新增或者忽略某个字段,但每个字段的类型非常重要。比如一个年龄字段类型,可以是字符串也可以是整形。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型

文档就是一条条打好标签的数据

类型

类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器。

Document 可以分组,比如weather这个 Index 里面,可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。

不同的 Type 应该有相似的结构(schema),举例来说,id字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如productslogs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。

索引

索引是映射类型的容器, elasticsearch中的索引是一个非常大的文档集合(即数据库)。索引存储了映射类型的字段和其他设置。 然后它们被存储到了各个分片上了。我们来研究下分片是如何工作的。

  • 物理设计

    • 存在数据库的数据可以通过不同的分片放在不同的集群上

    • 一个集群至少有一个节点,而一个节点就是一个elasricsearch进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有个5个分片( primary shard ,又称主分片)构成,每一个主分片会有一个副本( replica shard ,又称复制分片)

      上图是一个有3个节点的集群,可以看到主分片和对应的复制分片都不会在同一个节点内(比如主分片P0和主分片的复制分片P1分别在节点1/3,同样的分片Px在每个至少有一个),当某个节点挂掉了,数据也不至于丢失。

      实际上,一个分片是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字。

倒排索引

文件ID对应到关键词的映射转换为关键词到文件ID的映射

elasticsearch的索引和Lucene的索引对比

在elasticsearch中,索引(数据库)这个词被频繁使用。在elasticsearch中 ,索引被分为多个分片,每份分片是一个Lucene的索引。所以一个elasticsearch索引是由多个Lucene倒排索引组成的

IK分词器

  • 分词算法

    • ik_smart
      • 最少切分
    • ik _max _word
      • 最细粒度切分
  • 自定义词典:

    • \elasticsearch-7.17.0\plugins\ik\config\IKAnalyzer.cfg.xml,相关配置如下

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
      <properties>
      	<!-- 使用key来指定引用的字典的类型   -->
      	<!-- ext_dict表示配置本地扩展字典   -->
      	<!-- ext_stopwords表示配置扩展的停止词字典   -->
      	<!-- remote_ext_dict表示配置 远程扩展字典  -->
      	<!-- remote_ext_stopwords表示配置 远程扩展停止词字典  -->
      	<comment>IK Analyzer 扩展配置</comment>
      	<!--用户可以在这里配置自己的扩展字典 -->
      	<entry key="ext_dict"></entry>
      	 <!--用户可以在这里配置自己的扩展停止词字典-->
      	<entry key="ext_stopwords"></entry>
      	<!--用户可以在这里配置远程扩展字典 -->
      	<!-- <entry key="remote_ext_dict">words_location</entry> -->
      	<!--用户可以在这里配置远程扩展停止词字典-->
      	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
      </properties>
      

Restful风格

一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

通过不同的命令实现不同的操作

查看节点信息

GET _cat/count
GET _cat/health
GET _cat/indices
GET _cat/master
GET _cat/nodes

索引操作

创建索引

PUT {index}

查询索引

GET {index}

删除索引

DELETE {index}

映射操作

创建 mapping

PUT {index}
{
  "mappings": {
    "properties": {
      "{field}": {
      	"type": "text",# 指定字段属性 会建立分词倒排索引,用于全文检索。
      	"fields": {# 多字段属性
      		"keyword": {
      			"type": "keyword",# 不会建立分词倒排索引,用于排序和聚合。
      			"ignore_above": 256 # 索引最大长度
      		}
      	}
      }
    }
  }
}

给 mapping 添加字段映射

PUT {index}/_mapping
{
  "properties":{
    "{new-field}":{
      "type": "long",
      "index": false # 表明新增的字段不能被检索,只是一个冗余字段。
    }
  }
}

修改索引字段是不可以的,会影响历史数据,只能通过数据迁移的方式
旧版本的索引有 type,所以迁移时需要加上 type

PUT {newbank}
{
  "mappings": {
    "properties" : {
        "{field}" : {
          "type" : "text"
        },
        ...
      }
  }
}

POST _reindex
{
  "source": {
    "index": "{bank}"
  }, "dest": {
    "index": "{newbank}"
  }
}

查询 mapping

GET {index}/_mapping

文档操作

插入和修改

# 幂等性,必须带id,插入或修改
PUT {index}/_doc/{id}
{
  "filed": "value",
  ...
}
#PUT指明创建
PUT {index}/_doc/{id}/_create  
{
  "filed": "value",
  ...
}
#POST指明创建
POST {index}/_doc/{id}/_create  
{
  "filed": "value",
  ...
}
# 不带id时,id自动生成,相当于create;
# 带id时,首次为create,其余为update
# 修改会覆盖原值
POST {index}/_doc/{id}
{
  "filed": "value",
  ...
}
# 指明更新  不会覆盖原值
POST {index}/_doc/{id}/_update
{
  "doc":{
    "filed": "value",
    ...
  }
}

乐观锁

# 旧版本使用version实现乐观锁
POST {index}/_doc/1?version=4
{
  "filed": "value",
  ...
}
# 新版本使用if_seq_no和if_primary_term实现乐观锁
POST {index}/_doc/1?if_seq_no=4&if_primary_term=1
{
  "filed": "value",
  ...
}

删除

DELETE {index}/_doc/{id}

查询

# 查询指定id的文档
GET {index}/_doc/{id}
# 查询所有文档
GET {index}/_doc/_search
# 简单条件查询文档
GET {index}/_doc/_search?q={condition}: {value}

复杂查询操作

Query DSL 语法

# 全文搜索,相当于select *
GET _search
{
  "query": {
    "match_all": {}
  }
}

排序

GET {index}/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "{field}": "asc"
    } ,
    {
      "{field}": "desc"
    }
  ]
}

返回指定字段

GET {index}/_search
{
  "query": {
    "match_all": {}
  },
  "_source": [
    "{field}",
    ...
  ]
}
GET {index}/_search
{
  "query": {
    "match_all": {}
  },
  "_source": false,
  "fields": [
	"{field}",
    ...
  ]
}

分页

GET {index}/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 2
}

match 匹配 必须加字段条件,满足条件分更好,一般用于全文检索

# 条件为非字符时 精确查询
GET {index}/_search
{
  "query": {
    "match": {
       "{field}": "{1}"
    }
  } 
}
# 条件为字符时 倒排索引 全文检索 会对条件进行分词 
# 最终按_score由高到低排序
GET {index}/_search
{
  "query": {
    "match": {
      "{field}": "{condition value}"
    }
  }
}
# 短语匹配 当成一个词,匹配包含这个词的字段值
GET {index}/_search
{
  "query": {
    "match_phrase": {
      "{field}": "{value}"
    }
  }
}
# 前缀
GET {index}/_search
{
  "query": {
    "match_phrase_prefix": {
      "{field}": "{value}"
    }
  }
}
# 多字段匹配
GET {index}/_search
{
  "query": {
    "multi_match": {
      "query": "{value}",
      "fields": ["{field}", "{field}"]
    }
  }
}

布尔 多条件查询

# 满足should的条件,评分更高
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "{field}": "{value}"
          }
        }
      ],
      "should": [
        {
          "match": {
            "{field}": "{value}"
          }
        }
      ]
    }
  }
}

范围查询

GET {index}/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "{field}": {
              "gte": 10,
              "lte": 20
            }
          }
        }
      ]
    }
  }
}

filter(must_not) 不会参与计算相关性得分,只过滤条件,速度相比 match 更快

GET {index}/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "age": {
              "gte": 10,
              "lte": 20
            }
          }
        }
      ]
    }
  }
}

term 用于非 text 类型,例如数值类型和keyword类型,一般用于精准匹配,效率高

# term 存在数据分析问题,也就是分词,检索text字段的完整值是困难的
GET {index}/_search
{
  "query": {
    "term": {
 	  "{field}": "{value}"
    }
  }
}
#精确查询 field.keyword
GET {index}/_search
{
  "query": {
    "match": {
      "{field}.keyword": "{value}"
    }
  }
}
  • 总结

    trem和match是对查询条件是否分词的区分,keyword和text是对存储数据是否分词的区分

    • 除了text类型都不分词
    • 默认分词器,中文单字分词,英文单词分词

    看到个比较形象的解释,贴一下

    简单解释一下 text 、keyword、term、match
    text和keyword是数据类型,表示在创建的时候是否会分词建立索引。
    term和match是指查询时是否启用分词查询。
    
    “my cat”是text类型。
    使用term查询“my cat”时,失败,因为“my cat”在被创建时分词器将其索引建立为“my”和“cat”。
    使用term查询“my”或“cat”时,则会成功,因为索引又能够精确匹配的数据。
    使用match查询“my cat”也能成功,因为match是模糊查询,查询语句“my cat”会被分词成为“my”和“cat”和“my cat”,只要有任意一个满足就会返回数据。
    
    “my cat”是keyword类型。
    那么建立时,只会有一个索引“my cat”,此时不管是term还是match都只有查询“my cat”时才会返回数据,其余的都查询不到。
    

聚合查询

# 查询address为mill的用户及其年龄的分布和薪资平均值
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "aggAgg": {
      "terms": {
        "field": "age",
        "size": 10
      }
    },
    "aggAvg": {
      "avg": {
        "field": "balance"
      }
    }
  }
}
# 按照年龄聚合,并请求这些年龄段的这些人的平均薪资
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0, 
  "aggs": {
    "aggAgg": {
      "terms": {
        "field": "age",
        "size": 1000
      },
      "aggs": {
        "aggAvg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}
# 查询所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资,以及这个年龄段的总体平均薪资和所有年龄段的总体平均薪资
# aggs套娃,基于上一次计算结果
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "aggAgg": {
      "terms": {
        "field": "age",
        "size": 10
      },
      "aggs": {
        "aggGender": {
          "terms": {
            "field": "gender.keyword",
            "size": 10
          },
          "aggs": {
            "aggBalance2": {
            "avg": {
              "field": "balance"
            }
          }
          }
        },
        "aggBalance1": {
          "avg": {
            "field": "balance"
          }
        }
      }
    } ,
    "aggBalance": {
      "avg": {
        "field": "balance"
      }
    }
  }
}

高亮操作

GET {index}/{type}/_search
{
  "query": {
    "match": {
      "{field}": "{value}"
    }
  },
  "highlight": {
    "pre_tags": "<p class=key style='color:red'>", # 自定义高亮前缀
    "post_tags": "</p>", # 自定义高亮后缀
    "fields": {
      "name":{}
    }
  }
}

分词器操作

# 标准分词器
POST _analyze
{
  "analyzer": "standard",
  "text": ["wo ai zhong guo", "我爱中国"]
}
# ik分词器(包含中文分词)
POST _analyze
{
  "analyzer": "ik_smart",
  "text": ["wo ai zhong guo", "我爱中国"]
}
POST _analyze
{
  "analyzer": "ik_max_word",
  "text": ["wo ai zhong guo", "我爱中国"]
}

SpringBoot整合

官方文档

整合方式比较多,推荐spring-boot-starter-data-elasticsearchr或者spring-data-elasticsearch

  • Java Client(最新版的客户端,资料很少)
  • Java Transport Client (deprecated)
  • Java REST Client (deprecated)
    • Java Low Level REST Client
    • Java High Level REST Client
  • spring-data-elasticsearch
  • spring-boot-starter-data-elasticsearch

查找maven结构可知,除了新型clientJava ClientJava Transport Client,剩下的几种方法,本质上都是对上一级API的包装。

Java Client

很新的一个client,资料很少,初步尝试使用。

  • pom

    	<dependency>
          <groupId>co.elastic.clients</groupId>
          <artifactId>elasticsearch-java</artifactId>
          <version>7.16.3</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.12.3</version>
        </dependency>
    
  • 配置

    注入bean失败,查找原因没有jakarta/json/JsonException,奇怪,暂时没找到解决方法。简单看一下官网的使用介绍

    @Configuration
    public class ElasticsearchJavaClientConfig {
        @Bean
        public ElasticsearchClient elasticsearchClient() {
            return new ElasticsearchClient(
                    new RestClientTransport(
                            RestClient.builder(
                                    new HttpHost("localhost", 9200)).build(), new JacksonJsonpMapper()));
        }
    }
    
  • 使用

    原有的实例化请求的构建器被lambda表达式所替代,查询部分也大量使用了lambda表达式(别的看不懂啦)

spring-boot-starter-data-elasticsearch

spring-boot-starter-data-elasticsearch底层是spring-data-elasticsearch,spring-data-elasticsearch底层是Java High Level REST Client,Java High Level REST Client底层是Java Low Level REST Client

我们介绍Java High Level REST Client和spring-boot-starter-data-elasticsearch两种方式

Java High Level REST Client

  • 配置

    @Configuration
    public class ElasticsearchConfig {
        @Bean
        public RestHighLevelClient restHighLevelClient(){
            RestHighLevelClient client=new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("127.0.0.1",9200,"http")
                    )
            );
            return client;
        }
    }
    
  • 索引

    • 创建索引

      @Test
      public void testCreateIndex() throws IOException {
          CreateIndexRequest request = new CreateIndexRequest("shiyige_index");
          CreateIndexResponse response = restHighLevelClient.indices().create(request,RequestOptions.DEFAULT);
          System.out.println(response.isAcknowledged());// 查看是否创建成功
          System.out.println(response);// 查看返回对象
          restHighLevelClient.close();
      }
      
    • 获取索引

      @Test
      public void testIndexIsExists() throws IOException {
           GetIndexRequest request = new GetIndexRequest("shiyige_index");
           boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
           System.out.println(exists);// 索引是否存在
           restHighLevelClient.close();
      }
      
    • 删除索引

      // 测试索引删除
      @Test
      public void testDeleteIndex() throws IOException {
          DeleteIndexRequest request = new DeleteIndexRequest("shiyige_index");
          AcknowledgedResponse response = restHighLevelClient.indices().delete(request,RequestOptions.DEFAULT);
          System.out.println(response.isAcknowledged());// 是否删除成功
          restHighLevelClient.close();
      }
      
  • 文档

    • 创建文档

       	@Test
          public void testAddDocument() throws IOException {
              IndexRequest request = new IndexRequest("shiyige_index");
              request.id("1");
              request.timeout(TimeValue.timeValueMillis(1000));
      
              //==============================提供文档源========================================
              //方式1:以Json形式提供
              User user = new User("石一歌", 18);
              request.source(JSON.toJSONString(user), XContentType.JSON);
              //方式2:以字符串形式提供
              String jsonString = "{" +
                      "\"user\":\"kimchy\"," +
                      "\"postDate\":\"2013-01-30\"," +
                      "\"message\":\"trying out Elasticsearch\"" +
                      "}";
              request.source(jsonString, XContentType.JSON);
              IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
              System.out.println(response.status());// 获取建立索引的状态信息 CREATED
              System.out.println(response);// 查看返回内容
          }
      
    • 获取文档

      	@Test
          public void testGetDocument() throws IOException {
              GetRequest request = new GetRequest("shiyige_index","1");
              GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
              System.out.println(response.getSourceAsString());// 打印文档内容
              System.out.println(response);
              restHighLevelClient.close();
          }
      
    • 更新文档

          @Test
          public void testUpdateDocument() throws IOException {
              UpdateRequest request = new UpdateRequest("shiyige_index", "1");
              User user = new User("石一歌",22);
              request.timeout(TimeValue.timeValueMillis(1000));
              request.doc(JSON.toJSONString(user),XContentType.JSON);
              UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
              System.out.println(response.status()); // OK
              restHighLevelClient.close();
          }
      
    • 删除文档

          @Test
          public void testDeleteDocument() throws IOException {
              DeleteRequest request = new DeleteRequest("shiyige_index", "1");
              request.timeout(TimeValue.timeValueMillis(1000));
              DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
              System.out.println(response.status());// OK
          }
      
    • 查询文档

      // 查询
          // SearchRequest 搜索请求
          // SearchSourceBuilder 条件构造
          // HighlightBuilder 高亮
          // TermQueryBuilder 精确查询
          // MatchAllQueryBuilder
          // xxxQueryBuilder ...
          @Test
          public void testSearch() throws IOException {
              // 1.创建查询请求对象
              SearchRequest searchRequest = new SearchRequest();
              // 2.构建搜索条件
              SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
              // (1)查询条件 使用QueryBuilders工具类创建
              // 精确查询
              TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "liuyou");
              // 匹配查询
              // MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
              // (2)其他<可有可无>:(可以参考 SearchSourceBuilder 的字段部分)
              // 设置高亮
              searchSourceBuilder.highlighter(new HighlightBuilder());
              // 分页
              // searchSourceBuilder.from();
              // searchSourceBuilder.size();
              searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
              // (3)条件投入
              searchSourceBuilder.query(termQueryBuilder);
              // 3.添加条件到请求
              searchRequest.source(searchSourceBuilder);
              // 4.客户端查询请求
              SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
              // 5.查看返回结果
              SearchHits hits = search.getHits();
              System.out.println(JSON.toJSONString(hits));
              for (SearchHit documentFields : hits.getHits()) {
                  System.out.println(documentFields.getSourceAsMap());
              }
          }
      
    • 批量操作

          @Test
          public void testBulk() throws IOException {
              BulkRequest bulkRequest = new BulkRequest();
              bulkRequest.timeout("10s");
              ArrayList<User> users = new ArrayList<>();
              users.add(new User("shiyige-1",1));
              users.add(new User("shiyige-2",2));
              users.add(new User("shiyige-3",3));
              users.add(new User("shiyige-4",4));
              users.add(new User("shiyige-5",5));
              users.add(new User("shiyige-6",6));
              // 批量请求处理
              for (int i = 0; i < users.size(); i++) {
                  bulkRequest.add(
                  // 这里是数据信息
                          new IndexRequest("bulk")
                                  .id(""+(i + 1)) // 没有设置id 会自定生成一个随机id
                                  .source(JSON.toJSONString(users.get(i)),XContentType.JSON)
                  );
              }
              BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
              System.out.println(bulk.status());// ok
          }
      

ElasticsearchRepository

  • application.yml配置

    server: 
    	port: 8080
    	servlet:
    		context-path: /ems
    
    spring:
    	elasticsearch:
    		connection-timeout: 1s
    		uris: http://localhost:9200
    		username: elastic
    		password: 123456
    
  • 启动注解

    @EnableElasticsearchRepositories
    
  • 实现

    ElasticsearchRepository是一个持久层接口,直接传入实体继承即可,不在赘述,这里展示一下源码,下列接口中的方法,继承后都可以使用

    @NoRepositoryBean
    public interface ElasticsearchRepository<T, ID> extends PagingAndSortingRepository<T, ID> {
        Page<T> searchSimilar(T entity, @Nullable String[] fields, Pageable pageable);
    }
    
    @NoRepositoryBean
    public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
        Iterable<T> findAll(Sort sort);
        Page<T> findAll(Pageable pageable);
    }
    
    @NoRepositoryBean
    public interface CrudRepository<T, ID> extends Repository<T, ID> {
        <S extends T> S save(S entity);
        <S extends T> Iterable<S> saveAll(Iterable<S> entities);
        Optional<T> findById(ID id);
        boolean existsById(ID id);
        Iterable<T> findAll();
        Iterable<T> findAllById(Iterable<ID> ids);
        long count();
        void deleteById(ID id);
        void delete(T entity);
        void deleteAllById(Iterable<? extends ID> ids);
        void deleteAll(Iterable<? extends T> entities);
        void deleteAll();
    }
    

ElasticSearchRestTemplate

ElasticSearchTemplate已被淘汰,使用ElasticSearchRestTemplate代替,本质上是对ElasticsearchRepository的补充

  • 配置

    @Configuration
    public class ElasticsearchConfig {
        @Bean
        public ElasticsearchRestTemplate elasticsearchTemplate(RestHighLevelClient client, ElasticsearchConverter converter) {
            return new ElasticsearchRestTemplate(client, converter);
        }
    }
    
  • 实现

    初学,查看源码,仅凭方法名,我们就能得知基本的使用方法

    public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTransportTemplate {
        private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
        private final RestHighLevelClient client;
        private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
    	//构造
        public ElasticsearchRestTemplate(RestHighLevelClient client) {}
    	//构造
        public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {}
    	//索引操作	
        public IndexOperations indexOps(Class<?> clazz) {}
    	//索引操作
        public IndexOperations indexOps(IndexCoordinates index) {}
        public ClusterOperations cluster() {}
        //索引查询
        public String doIndex(IndexQuery query, IndexCoordinates index) {}
    	//获取
        @Nullable
        public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {}
    	//获取多个
        public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {}
    	//是否存在
        protected boolean doExists(String id, IndexCoordinates index) {}
    	//批量更新
        public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {}
    	//删除
        protected String doDelete(String id, @Nullable String routing, IndexCoordinates index) {}
        public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {}
    	//更新
        public UpdateResponse update(UpdateQuery query, IndexCoordinates index) {}
    	//查询更新
        public ByQueryResponse updateByQuery(UpdateQuery query, IndexCoordinates index) {}
    	//批量操作
        public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {}
    	//搜索
        public <T> org.springframework.data.elasticsearch.core.SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index) {}
    	//搜索Scroll开始时间
        public <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz, IndexCoordinates index) {}
    	//搜索Scroll持续时间
        public <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz, IndexCoordinates index) {}
    	//搜索清除
        public void searchScrollClear(List<String> scrollIds) {}
        public SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index) {}
    	//获取多条件查询的结果
        protected Item[] getMultiSearchResult(MultiSearchRequest request) {}
        public <T> T execute(ElasticsearchRestTemplate.ClientCallback<T> callback) {}
        private RuntimeException translateException(Exception exception) {}
    	//获取节点版本
        protected String getClusterVersion() {}
        @FunctionalInterface
        public interface ClientCallback<T> {}
    }
    

参考链接

posted @ 2022-02-14 19:06  Faetbwac  阅读(250)  评论(0编辑  收藏  举报