基础理论和DSL语法

准备工作

什么是ElasticSearch?它和Lucene以及solr的关系是什么?

这些是自己的知识获取能力,自行百度百科

下载ElasticSearch的window版

linux版后续说明

自行百度Elastic,然后进到官网进行下载,我的版本是:7.8.0

下载postman

自行百度进行下载,也可以用其他的。

ElasticSearch中的目录解读

会tomcat,看到这些目录就不陌生

进到bin目录下,点击 elasticsearch.bat 文件即可启动 ES 服务

ELK技术是什么意思?

就图中这三个

注意事项

保证自己的JDK是1.8或以上。

ES非关系型和关系型数据库对应关系

注意:ES 7.x之后,type已经被淘汰了,其他的没变

只要玩ES,那么这个图就要牢牢地记在自己脑海里,后续的名词解释不再过多说明,就是操作这幅图中的东西

基础理论

正向索引和倒排索引

elasticsearch中使用的就是倒排索引

倒排索引中又有3个小东西:

  1. 词条是指索引中的最小存储或查询单元。这个其实很好理解,白话文来讲就是:字或者词组,英文就是一个单词,中文就是字或词组嘛,比如:你要查询的内容中具备含义的某一个字或词组,这就是词条呗,如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条。但是数据千千万万,一般的数据结构能够存的下吗?不可能的,所以这里做了文章,采用的是B+树和hash存储(如:hashmap)

  2. 词典:就是词条的集合嘛。字或者词组组成的内容呗

  3. 倒排表就是指 关键字 / 关键词 在索引中的位置。 有点类似于数组,你查询数组中某个元素的位置,但是区别很大啊,我只是为了好理解,所以才这么举例子的

type 类型

这玩意儿就相当于关系型数据库中的表,注意啊:关系型中表是在数据库下,那么ES中也相应的 类型是在索引之下建立的

表是个什么玩意呢?行和列嘛,这行和列有多少?N多行和N多列嘛,所以:ES中的类型也一样,可以定义N种类型。

同时:每张表要存储的数据都不一样吧,所以表是用来干嘛的?分类 / 分区嘛,所以ES中的类型的作用也来了:就是为了分类嘛。

另外:关系型中可以定义N张表,那么在ES中,也可以定义N种类型

因此:ES中的类型类似于关系型中的表,作用:为了分类 / 分区,同时:可以定义N种类型,但是:类型必须是在索引之下建立的( 是索引的逻辑体现嘛 )

但是:不同版本的ES,类型也发生了变化,上面的解读不是全通用的

field 字段

这也就类似于关系型中的列。 对文档数据根据不同属性(列字段)进行的分类标识

字段常见的简单类型:注意:id的类型在ES中id是字符串,这点需要注意

  • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)。text和keyword的区别如下;

    • text类型支持全文检索和完全查询,即:我搜索时只用字符串中的一个字符照样得到结果。原理:text使用了分词,就是把字符串拆分为单个字符串了
    • keyword类型支持完全查询,即:精确查询,前提:index不是false原理:keyword不支持分词,所以:查询时必须是完全查询( 所有字符匹配上才可以 )
  • 数值:long、integer、short、byte、double、float、

  • 布尔:boolean

  • 日期:date

  • 对象:object

  • 地图类型:geo_point 和 geo_shape

    • geo_point:有纬度(latitude) 和经度(longitude)确定的一个点,如:“32.54325453, 120.453254”
    • geo_shape:有多个geo_point组成的复杂集合图形,如一条直线 “LINESTRING (-77.03653 38.897676, -77.009051 38.889939)”
  • 自动补全类型:completion

注意:没有数组类型,但是可以实现出数组,因为每种类型可以有“多个值”,即可实现出类似于数组类型,例如下面的格式:

{
    "age": 21, // Integer类型
    "weight": 52.1, // float类型
    "isMarried": false, // boolean类型
    "info": "这就是一个屌丝女", // 字符串类型 可能为test,也可能为keyword 需要看mapping定义时对文档的约束时什么
"email": "zixq8@slafjkl.com", // 字符串类型 可能为test,也可能为keyword 需要看mapping定义时对文档的约束时什么
"score": [99.1, 99.5, 98.9], // 类似数组 就是利用了一个类型可以有多个值
    "name": { // object对象类型
        "firstName": "紫",
        "lastName": "邪情"
    }
}

还有一个字段的拷贝: 可以使用copy_to属性将当前字段拷贝到指定字段

使用场景: 多个字段放在一起搜索的时候

注意: 定义的要拷贝的那个字段在ES中看不到,但是确实是存在的,就像个虚拟的一样

// 定义了一个字段
"all": {
"type": "text",
"analyzer": "ik_max_word"
} "name": {
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all" // 将当前字段 name 拷贝到 all字段中去
}

document 文档

这玩意儿类似于关系型中的行。 一个文档是一个可被索引的基础信息单元,也就是一条数据嘛

即:用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息

新增文档:

// 这是kibana中进行的操作,要是使用如postman风格的东西发请求,则在 /索引库名/_doc/文档id 前加上es主机地址即可
POST /索引库名/_doc/文档id // 指定了文档id,若不指定则es自动创建
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
// ...
}

查看指定文档id的文档:

GET /{索引库名称}/_doc/{id}

删除指定文档id的文档:

DELETE /{索引库名}/_doc/id值

修改文档:有两种方式

  • 全量修改:直接覆盖原来的文档。其本质是:

    • 根据指定的id删除文档
    • 新增一个相同id的文档
    • 注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了
// 语法格式
PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
// ... 略
}
  • 增量/局部修改:是只修改指定id匹配的文档中的部分字段
// 语法格式
POST /{索引库名}/_update/文档id
{
    "doc": {
"字段名": "新的值",
}
}

mapping 映射

指的就是:结构信息 / 限制条件

还是对照关系型来看,在关系型中表有哪些字段、该字段是否为null、默认值是什么........诸如此的限制条件,所以ES中的映射就是:数据的使用规则设置

mapping是对索引库中文档的约束,常见的mapping属性包括:

  • index:是否创建索引,默认为true
  • analyzer:使用哪种分词器
  • properties:该字段的子字段

更多类型去官网查看:https://www.elastic.co/guide/en/elasticsearch/reference/8.8/mapping-params.html

创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:

  • 字段名
  • 字段数据类型
  • 是否参与搜索
  • 是否需要分词
  • 如果分词,分词器是什么?

其中:

  • 字段名、字段数据类型,可以参考数据表结构的名称和类型
  • 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
  • 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
  • 分词器,我们可以统一使用ik_max_word
{
  "mappings": {
    "properties": { // 子字段
      "字段名1":{ // 定义字段名
        "type": "text", // 该字段的类型
        "analyzer": "ik_smart" // 该字段采用的分词器类型 这是ik分词器中的,一种为ik_smart 一种为ik_max_word,具体看一开始给的系列知识链接
      },
      "字段名2":{
        "type": "keyword",
        "index": "false" // 该字段是否可以被索引,默认值为trus,即:不想被搜索的字段就可以显示声明为false
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
// ...略
    }
  }
}

创建索引库的同时,创建数据结构约束:

// 格式
PUT /索引库名称 // 创建索引库
{ // 同时创建数据结构约束信息
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
// ...略
    }
  }
} // 示例
PUT /user
{
  "mappings": {
    "properties": {
      "info":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type": "keyword",
        "index": "falsae"
      },
      "name":{
        "properties": {
          "firstName": {
            "type": "keyword"
          },
"lastName": {
"type": "keyword"
          }
        }
      },
// ... 略
    }
  }
}

index 索引库

所谓索引:类似于关系型数据库中的数据库

但是索引这个东西在ES中又有点东西,它的作用和关系型数据库中的索引是一样的,相当于门牌号,一个标识,旨在:提高查询效率,当然,不是说只针对查询,CRUD都可以弄索引,所以这么一说ES中的索引和关系型数据库中的索引是一样的,就不太类似于关系型中的数据库了,此言差矣!在关系型中有了数据库,才有表结构( 行、列、类型...... )

而在ES中就是有了索引,才有doc、field.....,因此:这就类似于关系型中的数据库,只是作用和关系型中的索引一样罢了

因此:ES中索引类似于关系型中的数据库,作用:类似于关系型中的索引,旨在:提高查询效率,当然:在一个集群中可以定义N多个索引,同时:索引名字必须采用全小写字母

当然:也别忘了有一个倒排索引

  • 关系型数据库通过增加一个B+树索引到指定的列上,以便提升数据检索速度。而ElasticSearch 使用了一个叫做 倒排索引 的结构来达到相同的目的

创建索引: 相当于在创建数据库

# 在kibana中进行的操作
PUT /索引库名称 # 在postman之类的地方创建
http://ip:port/indexName 如:http://127.0.0.1:9200/createIndex 请求方式:put

注:put请求具有幂等性,幂等性指的是: 不管进行多少次重复操作,都是实现相同的结果。可以采用把下面的请求多执行几次,然后:观察返回的结果

具有幂等性的有:put、delete、get

查看索引库:

# 查看指定的索引库
GET /索引库名 # 查看所有的索引库
GET /_cat/indices?v

修改索引库:

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

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

语法说明

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

删除索引库:

DELETE /索引库名

文档_doc

使用post创建doc

这种方式:是采用ES随机生成id时使用的请求方式

注:需要先创建索引,因为:这就类似于关系型数据库中在数据库的表中 创建数据

语法:

http://ip:port/indexName/_doc     如: http://ip:9200/createIndex/_doc    请求方式:post

使用put创建doc-转幂等性-自定义id

在路径后面加一个要创建的id值即可

查询文档_doc - 重点

id查询单条_doc

语法:

http://ip:port/indexName/_doc/id      如: http://ip:9200/createIndex/_doc/100001     请求方式:get

查询ES中索引下的全部_doc

语法:

http://ip:port/indexName/_search    如: http://ip:9200/createIndex/_search     请求方式:get

注意:别再body中携带数据了,不然就会报:

Unknown key for a VALUE_STRING in [title]

返回的结果:

{
"took": 69, // 查询花费的时间 毫秒值
"timed_out": false, // 是否超时
"_shards": { // 分片 还没学,先不看
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 1.0,
"hits": [ // 查询出来的 当前索引下的所有_doc文档
// .............................
]
}
}

文档_doc的修改

全量修改

原理:利用内容覆盖,重新发一份文档罢了

语法:

http://ip:port/indexName/_doc/id      如: http://ip:9200/createIndex/_doc/100001     请求方式:post

局部修改

语法:

http://ip:port/indexName/_update/id   如: http://ip:9200/createIndex/_update/100001    请求方式:post

文档_doc的删除

使用delete请求即可

文档DSL查询

elasticsearch的查询依然是基于JSON风格的DSL来实现的

DSL查询分类

ElasticSearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all

  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

    • match_query
    • multi_match_query
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,所以不会对搜索条件分词。例如:

    • ids
    • range
    • term
  • 地理(geo)查询:根据经纬度查询。例如:

    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

    • bool
    • function_score
  • 聚合(aggregations)查询: 可以让我们极其方便的实现对数据的统计、分析、运算,例如:

    • 桶(Bucket)聚合:用来对文档做分组
    • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
    • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

查询的语法基本一致:除了聚合查询

GET /indexName/_search
{
  "query": {
    "查询类型": {
      "查询条件": "条件值"
    }
  }
} // 例如:查询所有
GET /indexName/_search
{
  "query": {
    "match_all": { // 查询类型为match_all
} // 没有查询条件
  }
}

其它查询无非就是查询类型查询条件的变化

全文检索查询

定义: 利用分词器对用户输入内容分词,然后去倒排索引库中匹配

全文检索查询的基本流程如下:

  1. 对用户搜索的内容做分词,得到词条
  2. 根据词条去倒排索引库中匹配,得到文档id
  3. 根据文档id找到文档,返回给用户

使用场景: 搜索框搜索内容,如百度输入框搜索、google搜索框搜索……….

注意: 因为是拿着词条去匹配,因此参与搜索的字段必须是可分词的text类型的字段

常见的全文检索查询包括:

  • match查询:单字段查询
  • multi_match查询:多字段查询,任意一个字段符合条件就算符合查询条件

match查询语法如下:

GET /indexName/_search
{
  "query": {
    "match": {
      "field": "搜索的文本内容text"
    }
  }
} // 例如:
GET /indexName/_search
{
  "query": {
    "match": {
      "name": "紫邪情"
    }
  }
}

mulit_match语法如下:

GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query": "搜索的文本内容text",
      "fields": ["field1", "field2"]
    }
  }
} // 例如:
GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query": "Java",
      "fields": ["username","title", "context"]
    }
  }
}

注意: 搜索字段越多,对查询性能影响越大,因此建议采用copy_to,然后使用单字段查询的方式(即:match查询)

精准查询

定义: 根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,所以不会对搜索条件分词

常见的精准查询有:

  • term:根据词条精确值查询
  • range:根据值的范围查询
term查询/精确查询

因为精确查询的字段搜是不分词的字段,因此查询的条件也必须是不分词的词条。查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。如果用户输入的内容过多,反而搜索不到数据

语法说明:

// term查询
GET /indexName/_search
{
  "query": {
    "term": {
      "field": {
        "value": "要精确查询的内容"
      }
    }
  }
} // 例如:
GET /indexName/_search
{
  "query": {
    "term": {
      "field": {
        "value": "遥远的救世主"
      }
    }
  }
}
range查询/范围查询

范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤

基本语法:

// range查询
GET /indexName/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 10, // gte代表大于等于,gt则代表大于
        "lte": 20 // lte代表小于等于,lt则代表小于
      }
    }
  }
} // 例如:
GET /indexName/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 10000,
        "lte": 20000
      }
    }
  }
}

地理坐标查询

所谓的地理坐标查询,其实就是根据经纬度查询,官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-queries.html

常见的使用场景包括:

  • 携程:搜索我附近的酒店
  • 滴滴:搜索我附近的出租车
  • 微信:搜索我附近的人
矩形范围查询

矩形范围查询,也就是geo_bounding_box查询,查询坐标落在某个矩形范围的所有文档

查询时,需要指定矩形的左上右下两个点的坐标,然后画出一个矩形(就是对两个点画“十”字,中间交汇的部分就是要的矩形),落在该矩形内的都是符合条件的点,比如下图

语法如下:

// geo_bounding_box查询
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": { // 左上点
          "lat": 31.1, // 这个点的经度
          "lon": 121.5 // 这个点的纬度
        },
        "bottom_right": { // 右下点
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}
附近查询/距离查询

附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档

换句话来说,在地图上找一个点作为圆心,以指定距离为半径,画一个圆,落在圆内的坐标都算符合条件,如下

语法说明:

// geo_distance 查询
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "距离", // 半径
      "field": "经度,纬度" // 圆心
    }
  }
} // 例如:在经纬度为 31.21,121.5 的方圆15km的附近
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "location": "31.21,121.5" // 圆心
    }
  }
}

复合查询

复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑

常见的复合查询有两种:

  • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
  • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索
相关性算分算法

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列

在elasticsearch中,早期使用的打分算法是TF-IDF算法,公式如下:

在后来的5.1版本升级中,elasticsearch将算法改进为BM25算法,公式如下:

TF-IDF算法有一各缺陷,就是词条频率越高,文档得分也会越高,单个词条对文档影响较大。而BM25则会让单个词条的算分有一个上限,曲线更加平滑:

function_score 算分函数查询

算分函数查询可以控制文档相关性算分,控制文档排名

以百度为例,你搜索的结果中,并不是相关度越高排名越靠前,而是谁掏的钱多排名就越靠前

要想人为控制相关性算分,就需要利用elasticsearch中的function score 查询了

语法格式说明:

function score 查询中包含四部分内容:

  • 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
  • 过滤条件:filter部分,符合该条件的文档才会重新算分
  • 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
    1. weight:函数结果是常量
    2. field_value_factor:以文档中的某个字段值作为函数结果
    3. random_score:以随机数作为函数结果
    4. script_score:自定义算分函数算法
  • 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
    1. multiply:相乘
    2. replace:用function score替换query score
    3. 其它,例如:sum、avg、max、min

function score的运行流程如下:

  1. 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
  2. 根据过滤条件,过滤文档
  3. 符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
  4. 原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。

因此,其中的关键点是:

  • 过滤条件:决定哪些文档的算分被修改
  • 算分函数:决定函数算分的算法
  • 运算模式:决定最终算分结果
bool 布尔查询

布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分

注意: 搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:

  • 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
  • 其它过滤条件,采用filter查询。不参与算分

示例:

GET /indexName/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {"city": "上海" }}
      ],
      "should": [
        {"term": {"brand": "皇冠假日" }},
{"term": {"brand": "华美达" }}
      ],
      "must_not": [
        { "range": { "price": { "lte": 500 } }}
      ],
      "filter": [
        { "range": {"score": { "gte": 45 } }}
      ]
    }
  }
}

排序查询

elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等

keyword、数值、日期类型排序的语法基本一致

语法

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "FIELD": "desc"  // 排序字段、排序方式ASC、DESC
    }
// 多个字段排序就继续写
  ]
}

排序条件是一个数组,也就是可以写多个排序条件。按照声明的顺序,当第一个条件相等时,再按照第二个条件排序,以此类推

地理坐标排序略有不同

提示:获取你的位置的经纬度的方式:https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-lnglat/

语法说明

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance" : {
          "FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点
          "order" : "asc", // 排序方式
          "unit" : "km" // 排序的距离单位
      }
    }
  ]
}

这个查询的含义是:

  • 指定一个坐标,作为目标点
  • 计算每一个文档中,指定字段(必须是geo_point类型)的坐标 到目标点的距离是多少
  • 根据距离排序

分页查询

elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:

  • from:从第几个文档开始
  • size:总共查询几个文档

类似于mysql中的limit ?, ?

基本分页

分页的基本语法如下:

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}
  • 优点:支持随机翻页
  • 缺点:深度分页问题,默认查询上限(from + size)是10000
  • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
深度分页问题

现在,我要查询990~1000的数据,查询逻辑要这么写:

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "from": 990, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}

这里是查询990开始的数据,也就是 第990~第1000条 数据

不过,elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条:

查询TOP1000,如果es是单点模式,这并无太大影响

但是elasticsearch将来一定是集群,例如我集群有5个节点,我要查询TOP1000的数据,并不是每个节点查询200条就可以了

因为节点A的TOP200,在另一个节点可能排到10000名以外了

因此要想获取整个集群的TOP1000,必须先查询出每个节点的TOP1000,汇总结果后,重新排名,重新截取TOP1000

那如果我要查询9900~10000的数据呢?是不是要先查询TOP10000呢?那每个节点都要查询10000条?汇总到内存中?

当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求

针对深度分页,ES提供了两种解决方案,官方文档

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式

    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:只能向后逐页查询,不支持随机翻页
    • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
  • scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用
    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:会有额外内存消耗,并且搜索结果是非实时的
    • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 search after方案

高亮查询

高亮显示的实现分为两步:

  1. 给文档中的所有关键字都添加一个标签,例如<em>标签
  2. 页面给<em>标签编写CSS样式

高亮的语法

GET /indexName/_search
{
  "query": {
    "match": {
      "field": "TEXT" // 查询条件,高亮一定要使用全文检索查询
    }
  },
  "highlight": {
    "fields": {
      "FIELD": { // 指定要高亮的字段
        "pre_tags": "<em>",  // 用来标记高亮字段的前置标签,es默认添加的标签就是em
        "post_tags": "</em>" // 用来标记高亮字段的后置标签
      }
    }
  }
}

注意:

  • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
  • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
  • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false,可以解决的场景:要高亮的字段和搜索指定字段不一致。如:
GET /indexName/_search
{
  "query": {
    "match": {
      "name": "紫邪情" // 查询条件,高亮一定要使用全文检索查询
    }
  },
  "highlight": {
    "fields": {
      "all": { // 假如这里的all字段是利用copy_to将其他很多字段copy进来的,就造成上面搜索字段name与这里要高亮得到字段不一致
        "pre_tags": "<em>",
        "post_tags": "</em>",
"require_field_match": "false" // 是否要求字段匹配,即:要高亮字段和搜索字段是否匹配,默认是true
      }
    }
  }
}

聚合查询/数据聚合

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

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

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

聚合的分类

聚合常见的有三类:

  • 桶(Bucket)聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

注意:参加聚合的字段必须是keyword、日期、数值、布尔类型,即:只要不是 text 类型即可,因为text类型会进行分词,而聚合不能进行分词

Bucket 桶聚合

桶(Bucket)聚合:用来对文档做分组

  • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
  • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

语法如下:

GET hhtp://ip:port/indexName/_search
{
  "query": { // 加入基础查询,从而限定聚合范围,不然默认是将es中的文档全部查出来再聚合
    "查询类型": {
      "查询条件": "条件值"
    }
  },
  "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果 即:去掉结果hits中的hits数组的数据
  "aggs": { // 定义聚合
    "AggName": { //给聚合起个名字
      "aggType": { // 聚合的类型,跟多类型去官网
        "field": "value", // 参与聚合的字段
        "size": 20, // 希望获取的聚合结果数量 默认是10
"order": { // 改变聚合的排序规则,默认是 desc 降序
"_key": "asc" // 按照什么关键字以什么类型排列
        }
      }
    }
  }
}

例如:

// 数据聚合
GET /indexName/_search
{
"query": {
"range": {
"price": {
"lte": 200
}
}
},
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 15,
"order": {
"_count": "asc"
}
}
}
}
}

Metric 度量聚合

度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

  • Avg:求平均值
  • Max:求最大值
  • Min:求最小值
  • Stats:同时求max、min、avg、sum等

语法如下:

GET /indexName/_search
{
  "size": 0, 
  "aggs": {
    "aggName": { 
      "aggType": { 
        "field": "value", 
        "size": 20,
"order": {
"_key": "orderType"
}
      },
      "aggs": { // brands聚合的子聚合,也就是分组后对每组分别计算
        "aggName": { // 聚合名称
          "aggType": { // 聚合类型,这里stats可以计算min、max、avg等
            "field": "value" // 聚合字段
          }
        }
      }
    }
  }
} // 例如:
GET /indexName/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": { 
      "terms": { 
        "field": "brand", 
        "size": 20,
"order": {
"scoreAgg.avg": "asc" // 注意:若是要使用子聚合来对每个桶进行排序,则这里的写法有点区别
}
      },
      "aggs": {
        "scoreAgg": {
          "stats": {
            "field": "score"
          }
        }
      }
    }
  }
}

自动补全查询 completion

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型

  • 字段的内容一般是用来补全的多个词条形成的数组

场景: 搜索框输入关键字,搜索框下面就会弹出很多相应的内容出来

比如,一个这样的索引库:

// 创建索引库
PUT test
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion" // 指定字段类型为 completion
      }
    }
  }
}

然后插入下面的数据:

// 示例数据
POST test/_doc
{
  "title": ["Sony", "WH-1000XM3"] // 字段内容为多个词条组成的数组
}
POST test/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test/_doc
{
  "title": ["Nintendo", "switch"]
}

查询的DSL语句如下:

// 自动补全查询
GET /test/_search
{
  "suggest": {
    "title_suggest": { // 起个名字
      "text": "s", // 关键字
      "completion": {
        "field": "title", // 补全查询的字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

Java操作ES篇 - 重点

摸索Java链接ES的流程

自行创建一个maven项目

父项目依赖管理

<properties>
<ES-version>7.8.0</ES-version>
<log4j-version>1.2.17</log4j-version>
<junit-version>4.13.2</junit-version>
<jackson-version>2.13.0</jackson-version>
<fastjson.version>1.2.83</fastjson.version>
</properties> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<!-- 注意:这里的版本问题,要和下载的window的ES版本一致,甚至后续用linux搭建也是一样的
到时用linux时,ES、kibana的版本都有这样的限定
-->
<version>${ES-version}</version>
</dependency> <dependency>
<groupId>org.elasticsearch.client</groupId>
<!-- 注意:这里别搞成了elasticsearch-client
这个东西在7.x已经不推荐使用了,而到了8.0之后,这个elasticsearch-client已经完全被废弃了
-->
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<!-- 同样的,注意版本问题 -->
<version>${ES-version}</version>
</dependency> <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j-version}</version>
</dependency> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
</dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

摸索链接流程

获取父项目中的依赖

<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</dependency> <dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>

代码编写:

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.Test; import Java.io.IOException; public class ConnectionTest {
/**
* 倒着看逻辑即可
*/
@Test
public void test() throws IOException { // 3、创建HttpHost
HttpHost host = new HttpHost("127.0.0.1", 9200); // 需要:String hostname, int port
// 当然:这个方法重载中有一个参数scheme 这个是:访问方式 根据需求用http / https都可以 这里想传的话用:http就可以了 // 2、创建RestClientBuilder
RestClientBuilder clientBuilder = RestClient.builder(host);
// 发现1、有重载;2、重载之中有几个参数,而HttpHost... hosts 这个参数貌似贴近我们想要的东西了,所以建一个HttpHost // 1、要链接client,那肯定需要一个client咯,正好:导入得有high-level-client
RestHighLevelClient esClient = new RestHighLevelClient(clientBuilder);
// 发现需要RestClientBuilder restClientBuilder,那就建 // 4、释放资源
esClient.close();
}
}

Java中操作ES索引

向父项目获取自己要的依赖

<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</dependency> <dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>

封装链接对象

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient; /**
* @ClassName ESClientUtil
* @Author ZiXieQing
* @Date 2021/12/14
* Version 1.0
**/
public class ESClientUtil { private static final String HOST = "127.0.0.1";
private static final Integer PORT = 9200; public static RestHighLevelClient getESClient() {
return new RestHighLevelClient(RestClient.builder(new HttpHost(HOST, PORT)));
// 还有一种方式
// return new RestHighLevelClient(RestClient.builder(HttpHost.create("http://ip:9200")));
}
}

操作索引

import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
import org.elasticsearch.action.admin.indices.flush.FlushResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; import static com.zixieqing.hotel.constant.MappingConstant.mappingContext; /**
* elasticsearch的索引库测试
* 规律:esClient.indices().xxx(xxxIndexRequest(IndexName), RequestOptions.DEFAULT)
* 其中 xxx 表示要对索引进行得的操作,如:create、delete、get、flush、exists.............
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest(classes = HotelApp.class)
public class o1IndexTest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://ip:9200")));
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* 创建索引 并 创建字段的mapping映射关系
*/
@Test
void createIndexAndMapping() throws IOException {
// 1、创建索引
CreateIndexRequest request = new CreateIndexRequest("person");
// 2、创建字段的mapping映射关系 参数1:编写的mapping json字符串 参数2:采用的文本类型
request.source(mappingContext, XContentType.JSON);
// 3、发送请求 正式创建索引库与mapping映射关系
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
// 查看是否创建成功
System.out.println("response.isAcknowledged() = " + response.isAcknowledged());
// 判断指定索引库是否存在
boolean result = client.indices().exists(new GetIndexRequest("person"), RequestOptions.DEFAULT);
System.out.println(result ? "hotel索引库存在" : "hotel索引库不存在");
} /**
* 删除指定索引库
*/
@Test
void deleteIndexTest() throws IOException {
// 删除指定的索引库
AcknowledgedResponse response = client.indices()
.delete(new DeleteIndexRequest("person"), RequestOptions.DEFAULT);
// 查看是否成功
System.out.println("response.isAcknowledged() = " + response.isAcknowledged());
} // 索引库一旦创建,则不可修改,但可以添加mapping映射 /**
* 获取指定索引库
*/
@Test
void getIndexTest() throws IOException {
// 获取指定索引
GetIndexResponse response = client.indices()
.get(new GetIndexRequest("person"), RequestOptions.DEFAULT);
} /**
* 刷新索引库
*/
@Test
void flushIndexTest() throws IOException {
// 刷新索引库
FlushResponse response = client.indices().flush(new FlushRequest("person"), RequestOptions.DEFAULT);
// 检查是否成功
System.out.println("response.getStatus() = " + response.getStatus());
}
}

Java操作ES中的文档_doc - 重点

这里还需要json依赖,使用jackson或fastjson均可

同时:为了偷懒,所以把lombok也一起导入了

基本的文档CRUD

import com.alibaba.fastjson.JSON;
import com.zixieqing.hotel.pojo.Hotel;
import com.zixieqing.hotel.pojo.HotelDoc;
import com.zixieqing.hotel.service.IHotelService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* elasticsearch的文档测试
* 规律:esClient.xxx(xxxRequest(IndexName, docId), RequestOptions.DEFAULT)
* 其中 xxx 表示要进行的文档操作,如:
* index 新增文档
* delete 删除指定id文档
* get 获取指定id文档
* update 修改指定id文档的局部数据
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest(classes = HotelApp.class)
public class o2DocumentTest {
@Autowired
private IHotelService service; private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* 添加文档
*/
@Test
void addDocumentTest() throws IOException { // 1、准备要添加的文档json数据
// 通过id去数据库获取数据
Hotel hotel = service.getById(36934L);
// 当数据库中定义的表结构和es中定义的字段mapping映射不一致时:将从数据库中获取的数据转成 es 中定义的mapping映射关系对象
HotelDoc hotelDoc = new HotelDoc(hotel); // 2、准备request对象 指定 indexName+文档id
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString()); // 3、把数据转成json
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON); // 4、发起请求,正式在ES中添加文档 就是根据数据建立倒排索引,所以这里调研了index()
IndexResponse response = client.index(request, RequestOptions.DEFAULT); // 5、检查是否成功 使用下列任何一个API均可 若成功二者返回的结果均是 CREATED
System.out.println("response.getResult() = " + response.getResult());
System.out.println("response.status() = " + response.status());
} /**
* 根据id删除指定文档
*/
@Test
void deleteDocumentTest() throws IOException {
// 1、准备request对象
DeleteRequest request = new DeleteRequest("indexName", "docId"); // 2、发起请求
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
// 查看是否成功 成功则返回 OK
System.out.println("response.status() = " + response.status());
} /**
* 获取指定id的文档
*/
@Test
void getDocumentTest() throws IOException {
// 1、获取request
GetRequest request = new GetRequest"indexName", "docId"); // 2、发起请求,获取响应对象
GetResponse response = client.get(request, RequestOptions.DEFAULT); // 3、解析结果
HotelDoc hotelDoc = JSON.parseObject(response.getSourceAsString(), HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
} /**
* 修改指定索引库 和 文档id的局部字段数据
* 全量修改是直接删除指定索引库下的指定id文档,然后重新添加相同文档id的文档即可
*/
@Test
void updateDocumentTest() throws IOException {
// 1、准备request对象
UpdateRequest request = new UpdateRequest("indexName", "docId"); // 2、要修改那个字段和值 注:参数是 key, value 形式 中间是 逗号
request.doc(
"price",500
); // 3、发起请求
UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
// 查看结果 成功则返回 OK
System.out.println("response.status() = " + response.status());
}
}

批量操作文档

本质:把请求封装了而已,从而让这个请求可以传递各种类型参数,如:删除的、修改的、新增的,这样就可以搭配for循环

package com.zixieqing.hotel;

import com.alibaba.fastjson.JSON;
import com.zixieqing.hotel.pojo.Hotel;
import com.zixieqing.hotel.pojo.HotelDoc;
import com.zixieqing.hotel.service.IHotelService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetRequest;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException;
import java.util.List; /**
* elasticsearch 批量操作文档测试
* 规律:EsClient.bulk(new BulkRequest()
* .add(xxxRequest("indexName").id().source())
* , RequestOptions.DEFAULT)
* 其中:xxx 表示要进行的操作,如
* index 添加
* delete 删除
* get 查询
* update 修改
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest(classes = HotelApp.class)
public class o3BulkDocumentTest {
@Autowired
private IHotelService service; private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* 批量添加文档数据到es中
*/
@Test
void bulkAddDocumentTest() throws IOException {
// 1、去数据库批量查询数据
List<Hotel> hotels = service.list(); // 2、将数据库中查询的数据转成 es 的mapping需要的对象
BulkRequest request = new BulkRequest();
for (Hotel hotel : hotels) {
HotelDoc hotelDoc = new HotelDoc(hotel);
// 批量添加文档数据到es中
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
} // 3、发起请求
BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
// 检查是否成功 成功则返回OK
System.out.println("response.status() = " + response.status());
} /**
* 批量删除es中的文档数据
*/
@Test
void bulkDeleteDocumentTest() throws IOException {
// 1、准备要删除数据的id
List<Hotel> hotels = service.list(); // 2、准备request对象
BulkRequest request = new BulkRequest();
for (Hotel hotel : hotels) {
// 根据批量数据id 批量删除es中的文档
request.add(new DeleteRequest("hotel").id(hotel.getId().toString()));
} // 3、发起请求
BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
// 检查是否成功 成功则返回 OK
System.out.println("response.status() = " + response.status());
} // 批量获取和批量修改是同样的套路 批量获取还可以使用 mget 这个API /**
* mget批量获取
*/
@Test
void mgetTest() throws IOException {
List<Hotel> hotels = service.list(); // 1、准备request对象
MultiGetRequest request = new MultiGetRequest();
for (Hotel hotel : hotels) {
// 添加get数据 必须指定index 和 文档id,可以根据不同index查询
request.add("hotel", hotel.getId().toString());
} // 2、发起请求,获取响应
MultiGetResponse responses = client.mget(request, RequestOptions.DEFAULT);
for (MultiGetItemResponse response : responses) {
GetResponse resp = response.getResponse();
// 如果存在则打印响应信息
if (resp.isExists()) {
System.out.println("获取到的数据= " +resp.getSourceAsString());
}
}
}
}

Java进行DSL文档查询

其实这种查询都是套路而已,一看前面玩的DSL查询的json形式是怎么写的,二看你要做的是什么查询,然后就是用 queryBuilds 将对应的查询构建出来,其他都是相同套路了

查询所有 match all

match all:查询出所有数据

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* es的dsl文档查询之match all查询所有,也可以称之为 全量查询
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest
public class o1MatchAll {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* 全量查询:查询所有数据
*/
@Test
void matchAllTest() throws IOException {
// 1、准备request
SearchRequest request = new SearchRequest("indexName");
// 2、指定哪种查询/构建DSL语句
request.source().query(QueryBuilders.matchAllQuery());
// 3、发起请求 获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4、处理响应结果
// 4.1、获取结果中的Hits
SearchHits searchHits = response.getHits();
// 4.2、获取Hits中的total
long total = searchHits.getTotalHits().value;
System.out.println("总共获取了 " + total + " 条数据");
// 4.3、获取Hits中的hits
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 4.3.1、获取hits中的source 也就是真正的数据,获取到之后就可以用来处理自己要的逻辑了
String source = hit.getSourceAsString();
System.out.println("source = " + source);
}
}
}

Java代码和前面玩的DSL语法的对应情况:

全文检索查询

match 单字段查询 与 multi match多字段查询

下面的代码根据情境需要,可以自行将响应结果处理进行抽取

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* DLS之全文检索查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配
* match_query 单字段查询 和 multi_match_query 多字段查询
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest
public class o2FullTextTest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* match_query 单字段查询
*/
@Test
void matchQueryTest() throws IOException {
// 1、准备request
SearchRequest request = new SearchRequest("indexName");
// 2、准备DSL
request.source().query(QueryBuilders.matchQuery("city", "上海"));
// 3、发送请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
} /**
* multi match 多字段查询 任意一个字段符合条件就算符合查询条件
*/
@Test
void multiMatchTest() throws IOException {
SearchRequest request = new SearchRequest("indexName");
request.source().query(QueryBuilders.multiMatchQuery("成人用品", "name", "business"));
SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
}
}

精确查询

精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,所以不会对搜索条件分词

range 范围查询 和 term精准查询

term:根据词条精确值查询

range:根据值的范围查询

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* DSL之精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,所以 不会 对搜索条件分词
* range 范围查询 和 term 精准查询
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest
public class o3ExactTest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* term 精准查询 根据词条精确值查询
* 和 match 单字段查询有区别,term要求内容完全匹配
*/
@Test
void termTest() throws IOException {
SearchRequest request = new SearchRequest("indexName");
request.source().query(QueryBuilders.termQuery("city", "深圳"));
SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
} /**
* range 范围查询
*/
@Test
void rangeTest() throws IOException {
SearchRequest request = new SearchRequest("indexName");
request.source().query(QueryBuilders.rangeQuery("price").lte(250));
SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
}
}

地理坐标查询

geo_distance 附近查询
package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* DSL之地理位置查询
* geo_bounding_box 矩形范围查询 和 geo_distance 附近查询
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest
public class o4GeoTest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* geo_distance 附近查询
*/
@Test
void geoDistanceTest() throws IOException {
SearchRequest request = new SearchRequest("indexName");
request.source()
.query(QueryBuilders.geoDistanceQuery("location")
.distance("15km").point(31.21,121.5));
SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
}
}

复合查询

function_score 算分函数查询 是差不多的道理

bool 布尔查询之must、should、must not、filter查询

布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分

注意: 搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:

  • 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
  • 其它过滤条件,采用filter查询。不参与算分
package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* DSL之复合查询:基础DSL查询进行组合,从而得到实现更复杂逻辑的复合查询
* function_score 算分函数查询
*
* bool布尔查询
* must 必须匹配每个子查询 即:and “与” 参与score算分
* should 选择性匹配子查询 即:or “或” 参与score算分
* must not 必须不匹配 即:“非" 不参与score算分
* filter 必须匹配 即:过滤 不参与score算分
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest
public class o5Compound {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* bool布尔查询
* must 必须匹配每个子查询 即:and “与” 参与score算分
* should 选择性匹配子查询 即:or “或” 参与score算分
* must not 必须不匹配 即:“非" 不参与score算分
* filter 必须匹配 即:过滤 不参与score算分
*/
@Test
void boolTest() throws IOException {
SearchRequest request = new SearchRequest("indexName");
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 构建must 即:and 与
boolQueryBuilder.must(QueryBuilders.termQuery("city", "北京"));
// 构建should 即:or 或
boolQueryBuilder.should(QueryBuilders.multiMatchQuery("速8", "brand", "name"));
// 构建must not 即:非
boolQueryBuilder.mustNot(QueryBuilders.rangeQuery("price").gte(250));
// 构建filter 即:过滤
boolQueryBuilder.filter(QueryBuilders.termQuery("starName", "二钻")); request.source().query(boolQueryBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
}
}

Java代码和前面玩的DSL语法对应关系:

fuzzy 模糊查询

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* DSL之模糊查询
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest
public class o6FuzzyTest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* 模糊查询
*/
@Test
void fuzzyTest() throws IOException {
SearchRequest request = new SearchRequest("indexName");
// fuzziness(Fuzziness.ONE) 表示的是:字符误差数 取值有:zero、one、two、auto
// 误差数 指的是:fuzzyQuery("name","深圳")这里面匹配的字符的误差 可以有几个字符不一样,多/少几个字符?
request.source().query(QueryBuilders.fuzzyQuery("name", "深圳").fuzziness(Fuzziness.ONE));
SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
}
}

排序和分页查询

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* DSL之排序和分页
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest
public class o7SortAndPageTest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* sort 排序查询
*/
@Test
void sortTest() throws IOException {
SearchRequest request = new SearchRequest("indexName");
request.source()
.query(QueryBuilders.matchAllQuery())
.sort("price", SortOrder.ASC);
SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
} /**
* page 分页查询
*/
@Test
void pageTest() throws IOException {
int page = 2, size = 20;
SearchRequest request = new SearchRequest("indexName");
request.source()
.query(QueryBuilders.matchAllQuery())
.from((page - 1) * size).size(size); SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 处理响应结果,后面都是一样的流程 都是解析json结果而已
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("获取了 " + total + " 条数据");
for (SearchHit hit : searchHits.getHits()) {
String dataJson = hit.getSourceAsString();
System.out.println("dataJson = " + dataJson);
}
}
}

高亮查询

返回结果处理的逻辑有点区别,但思路都是一样的

package com.zixieqing.hotel.dsl_query_document;

import com.alibaba.fastjson.JSON;
import com.zixieqing.hotel.HotelApp;
import com.zixieqing.hotel.pojo.HotelDoc;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.CollectionUtils; import java.io.IOException;
import java.util.Map; /**
* DSL之高亮查询
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest(classes = HotelApp.class)
public class o8HighLightTest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} /**
* 高亮查询
* 返回结果处理不太一样
*/
@Test
void highLightTest() throws IOException {
SearchRequest request = new SearchRequest("hotel");
request.source()
.query(QueryBuilders.matchQuery("city", "北京"))
.highlighter(SearchSourceBuilder.highlight()
.field("name") // 要高亮的字段
.preTags("<em>") // 前置HTML标签 默认就是em
.postTags("</em>") // 后置标签
.requireFieldMatch(false)); // 是否进行查询字段和高亮字段匹配 // 发起请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 处理响应结果
for (SearchHit hit : response.getHits()) {
String originalData = hit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(originalData, HotelDoc.class);
System.out.println("原始数据为:" + originalData); // 获取高亮之后的结果
// key 为要进行高亮的字段,如上为field("name") value 为添加了标签之后的高亮内容
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
// 根据高亮字段,获取对应的高亮内容
HighlightField name = highlightFields.get("name");
if (name != null) {
// 获取高亮内容 是一个数组
String highLightStr = name.getFragments()[0].string();
hotelDoc.setName(highLightStr);
}
} System.out.println("hotelDoc = " + hotelDoc);
}
}
}

代码和DSL语法对应关系: request.source() 获取到的就是返回结果的整个json文档

聚合查询

聚合(aggregations可以让我们极其方便的实现对数据的统计、分析、运算

聚合常见的有三类:

  • 桶(Bucket)聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

注意:参加聚合的字段必须是keyword、日期、数值、布尔类型,即:只要不是 text 类型即可,因为text类型会进行分词,而聚合不能进行分词

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException;
import java.util.List; /**
* 数据聚合 aggregation 可以让我们极其方便的实现对数据的统计、分析、运算
* 桶(Bucket)聚合:用来对文档做分组
* TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
* Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
*
* 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
* Avg:求平均值
* Max:求最大值
* Min:求最小值
* Stats:同时求max、min、avg、sum等
*
* 管道(pipeline)聚合:其它聚合的结果为基础做聚合
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest(classes = HotelApp.class)
public class o9AggregationTest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} @Test
void aggregationTest() throws IOException {
// 获取request
SearchRequest request = new SearchRequest("indexName");
// 组装DSL
request.source()
.size(0)
.query(QueryBuilders
.rangeQuery("price")
.lte(250)
)
.aggregation(AggregationBuilders
.terms("brandAgg")
.field("brand")
.order(BucketOrder.aggregation("scoreAgg.avg",true))
.subAggregation(AggregationBuilders
.stats("scoreAgg")
.field("score")
)
); // 发送请求,获取响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 处理响应结果
System.out.println("response = " + response);
// 获取全部聚合结果对象 getAggregations
Aggregations aggregations = response.getAggregations();
// 根据聚合名 获取其聚合对象
Terms brandAgg = aggregations.get("brandAgg");
// 根据聚合类型 获取对应聚合对象
List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
for (Terms.Bucket bucket : buckets) {
// 根据key获取其value
String value = bucket.getKeyAsString();
// 将value根据需求做处理
System.out.println("value = " + value);
}
}
}

请求组装对应关系:

响应结果对应关系:

自动补全查询

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import java.io.IOException; /**
* 自动补全 completion类型: 这个查询会匹配以用户输入内容开头的词条并返回
* 参与补全查询的字段 必须 是completion类型
* 字段的内容一般是用来补全的多个词条形成的数组
*
* <p>@author : ZiXieqing</p>
*/ @SpringBootTest(classes = HotelApp.class)
public class o10Suggest {
private RestHighLevelClient client; @BeforeEach
void setUp() {
this.client = new RestHighLevelClient(
RestClient.builder(HttpHost.create("http://ip:9200"))
);
} @AfterEach
void tearDown() throws IOException {
this.client.close();
} @Test
void completionTest() throws IOException {
// 准备request
SearchRequest request = new SearchRequest("hotel");
// 构建DSL
request.source()
.suggest(new SuggestBuilder()
.addSuggestion(
"title_suggest",
SuggestBuilders.completionSuggestion("title")
.prefix("s")
.skipDuplicates(true)
.size(10)
)); // 发起请求,获取响应对象
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 解析响应结果
// 获取整个suggest对象
Suggest suggest = response.getSuggest();
// 通过指定的suggest名字,获取其对象
CompletionSuggestion titleSuggest = suggest.getSuggestion("title_suggest");
for (CompletionSuggestion.Entry options : titleSuggest) {
// 获取每一个options中的test内容
String context = options.getText().string();
// 按需求对内容进行处理
System.out.println("context = " + context);
}
}
}

代码与DSL、响应结果对应关系:

ES与MySQL数据同步

这里的同步指的是:MySQL发生变化,则elasticsearch索引库也需要跟着发生变化

数据同步一般有三种方式:同步调用方式、异步通知方式、监听MySQL的binlog方式

1、同步调用:

  • 优点:实现简单,粗暴
  • 缺点:业务耦合度高

2、异步通知:

  • 优点:低耦合,实现难度一般
  • 缺点:依赖mq的可靠性

3、监听MySQL的binlog文件:

  • 优点:完全解除服务间耦合
  • 缺点:开启binlog增加数据库负担、实现复杂度高

高级篇链接

地址:https://www.cnblogs.com/xiegongzi/p/15770665.html

ELK之Elastic-Search 整理(一):基础理论 与 DSL语法 及 Java操作ES的更多相关文章

  1. Elastic Search快速上手(2):将数据存入ES

    前言 在上手使用前,需要先了解一些基本的概念. 推荐 可以到 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.htm ...

  2. Elastic Search 上市了,市值翻倍,这群人财务自由了!

    国庆长假,大部分人还深浸在风花雪月之中,而就在昨天(美国时间10月5号),我们 Java 程序员所熟知的大名鼎鼎的 Elastic Search 居然在美国纽约证券交易所上市了! 当说到搜索时,大部分 ...

  3. elastic search book [ ElasticSearch book es book]

    谁在使用ELK 维基百科, github都使用 ELK (ElasticSearch es book) ElasticSearch入门 Elasticsearch入门,这一篇就够了==>http ...

  4. elastic search 学习 一

    初步阅读了elastic search 的文档,并使用command实践操作. 大概明白其概念模型.

  5. Elastic Search对Document的搜索

    在ES中使用的重点.ES中存储的数据.核心就是为了提供全文搜索能力的.搜索功能非常重要.多练. 1 query string searchsearch的参数都是类似http请求头中的字符串参数提供搜索 ...

  6. 2 - 基于ELK的ElasticSearch 7.8.x技术整理 - java操作篇 - 更新完毕

    3.java操作ES篇 3.1.摸索java链接ES的流程 自行创建一个maven项目 3.1.1.依赖管理 点击查看代码 <properties> <ES-version>7 ...

  7. SQL数据同步到ELK(二)- Elastic Search 安装

    开篇废话 没错,前面扯了一堆SQL SERVER,其实我连Elastic Search根本没动手玩过(是不是与时代有点脱节了?),那今天我就准备尝试安装一个ELK的简单集群出来(这个集群是使用我的小米 ...

  8. tpot从elastic search拉攻击数据之一 找本地数据端口

    前面,我们已经在ubuntu服务器上部署好了tpot,并启动进行数据捕获 可以通过64297端口登陆到kibana可视化平台查看捕获到攻击的情况. 现在要拉取攻击数据了,但是该怎么拉呢? 看了一上午的 ...

  9. Elastic Search快速上手(1):简介及安装配置

    前言 最近开始尝试学习Elastic Search,因此决定做一些简单的整理,以供后续参考,快速上手使用ES. 简介 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多 ...

  10. elastic search&logstash&kibana 学习历程(一)es基础环境的搭建

    elastic search 6.1.x 常用框架: 1.Lucene Apache下面的一个开源项目,高性能的.可扩展的工具库,提供搜索的基本架构: 如果开发人员需用使用的话,需用自己进行开发,成本 ...

随机推荐

  1. pdo类

    testmysql.php <?php require_once "./mypdo.php"; //do something... //查一行 $id = 3; //$sql ...

  2. 如何获取Github Token

    登录我们的github账号,点击头像后选择Settings 进入界面之后下拉到左侧菜单的最后,选择Developer settings 进入界面后,选择Personal access tokens-- ...

  3. Oracle中ALTER TABLE的五种用法(四、五)

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1 ...

  4. 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(12.A)- uSDHC eMMC启动时间(RT1170)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RT1170 uSDHC eMMC启动时间. 本篇是 i.MXRT1170 启动时间评测第五弹,前四篇分别给大家评测了 ...

  5. 树莓派 ubuntu server 22.x 连接无线网络

    前言 树莓派系统安装完成后,需要配置网络,由于家里没有多余的网线(网线多少有点乱),所以决定配置无线上网的方式,现在记录下来操作过程 具体操作 sudo nano /etc/netplan/xxxxx ...

  6. PHP 中使用 ElasticSearch 的最佳实践(上)

    PHP 中使用 ElasticSearch 的最佳实践 引言 PHP 开发者其实使用到 ES 的情况并不多,因为开发的大多数项目可能都没有快速模糊搜索的需求. 即使有这样的需求,用 MySQL 的 l ...

  7. CSS——2D转换

  8. 一个简单demo展示接口请求超时处理

    package main import ( "context" "errors" "fmt" "time" ) type ...

  9. Android 12(S) MultiMedia Learning(六)NuPlayer Decoder

    接下来将会从4个角度来记录NuPlayerDecoder部分 相关代码路径: http://aospxref.com/android-12.0.0_r3/xref/frameworks/av/medi ...

  10. .NET桌面程序混合开发之三:WebView2与JS的深度应用

    在 WebView2 控件中使用 JavaScript 根据需求自由扩展原生应用的能力.本文探讨如何在 WebView2 中使用 JavaScript,并列举如何使用高级 WebView2 特性和功能 ...