基础理论和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. Java简单实现MQ架构和思路01

    实现一个 MQ(消息队列)架构可以涉及到很多方面,包括消息的生产和消费.消息的存储和传输.消息的格式和协议等等.下面是一个简单的 MQ 架构的实现示例,仅供参考: 定义消息格式和协议:我们可以定义一个 ...

  2. IDEA社区版(IDEA Community Edition)创建Springboot父子项目

    1. 因为社区办不支持使用spring Spring Initializr 的方式创建项目, 但是我们可以考虑使用别的方式达到效果: 创建方式有3种: 第一种:使用https://start.spri ...

  3. PageOffice 在线编辑 office文件,回调父页面

    一.子页面调用父页面的方法 var value=window.external.CallParentFunc("ParentFunName(Arguments);");//父页面的 ...

  4. Laravel框架中数据库分表时Model使用方法

    前言: 0.最近在使用laravel框架做MySQL分表的时候经过实践和踩坑,总结了以下3种可行的分表方法,亲测可用. 1.本人公司做的是SaaS系统,以店铺为维度.店铺id(shop_id) 命名规 ...

  5. Python读Excel数据自动化填入日常办公的网页表单

      前言 本篇内容,让你完全掌握Python是如何自动化办公的~ 一.环境准备 1.1  Python 3.7.0 1.2  Pycharm  (Python 开发工具) 1.3 Selenium  ...

  6. SpringAi

    Spring AI 初学 Spring AI 官方地址 "spring 不生产 AI,只是 AI 工具的搬运工" 项目可以查看gitee Open AI 前期准备 Open AI官 ...

  7. Visual Studio 智能代码插件:CodeGeeX

    前言 在软件开发领域,高效的编程助手一直是提升开发者效率和质量的关键.随着人工智能技术的不断发展,智能编程助手逐渐成为开发者们不可或缺的工具.其中,CodeGeeX作为一款专为Visual Studi ...

  8. 莫烦tensorflow学习记录 (3)建造我们第一个神经网络

    另一个学习文档http://doc.codingdict.com/tensorflow/tfdoc/tutorials/overview.html 定义 add_layer() https://mof ...

  9. LlamaFS自组织文件管理器

    LlamaFS是一个自组织文件管理器.它可以基于文件内容和修改时间等属性自动重命名和组织您的文件.它能让你不把时间花在对文件的复制.粘贴.重命名.拷贝.排序等简单操作上.有幸在Github上看到Lla ...

  10. 【Socket】解决TCP粘包问题

    一.介绍 TCP一种面向连接的.可靠的.基于字节流的传输层协议. 三次握手: 客户端发送服务端连接请求,等待服务端的回复. 服务端收到请求,服务端回复客户端,可以建立连接,并等待. 客户端收到回复并发 ...