基础理论和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个小东西:
-
词条:是指索引中的最小存储或查询单元。这个其实很好理解,白话文来讲就是:字或者词组,英文就是一个单词,中文就是字或词组嘛,比如:你要查询的内容中具备含义的某一个字或词组,这就是词条呗,如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条。但是数据千千万万,一般的数据结构能够存的下吗?不可能的,所以这里做了文章,采用的是B+树和hash存储(如:hashmap)
-
词典:就是词条的集合嘛。字或者词组组成的内容呗
-
倒排表:就是指 关键字 / 关键词 在索引中的位置。 有点类似于数组,你查询数组中某个元素的位置,但是区别很大啊,我只是为了好理解,所以才这么举例子的
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
} // 没有查询条件
}
}
其它查询无非就是查询类型、查询条件的变化
全文检索查询
定义: 利用分词器对用户输入内容分词,然后去倒排索引库中匹配
全文检索查询的基本流程如下:
- 对用户搜索的内容做分词,得到词条
- 根据词条去倒排索引库中匹配,得到文档id
- 根据文档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),有四种函数
- weight:函数结果是常量
- field_value_factor:以文档中的某个字段值作为函数结果
- random_score:以随机数作为函数结果
- script_score:自定义算分函数算法
- 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
- multiply:相乘
- replace:用function score替换query score
- 其它,例如:sum、avg、max、min
function score的运行流程如下:
- 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
- 根据过滤条件,过滤文档
- 符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
- 将原始算分(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方案
高亮查询
高亮显示的实现分为两步:
- 给文档中的所有关键字都添加一个标签,例如
<em>
标签 - 页面给
<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增加数据库负担、实现复杂度高