初步了解ES适合新手逐步学习

一、ES基础查询

1、es基础查询

1.1 准备数据

# 准备数据
PUT test_index/_doc/1
{
  "name":"顾老二",
  "age":30,
  "from": "gu",
  "desc": "皮肤黑、武器长、性格直",
  "tags": ["黑", "长", "直"]
}

PUT test_index/_doc/2
{
  "name":"大娘子",
  "age":18,
  "from":"sheng",
  "desc":"肤白貌美,娇憨可爱",
  "tags":["白", "富","美"]
}

PUT test_index/_doc/3
{
  "name":"龙套偏房",
  "age":22,
  "from":"gu",
  "desc":"mmp,没怎么看,不知道怎么形容",
  "tags":["造数据", "真","难"]
}

PUT test_index/_doc/4
{
  "name":"石头",
  "age":29,
  "from":"gu",
  "desc":"粗中有细,狐假虎威",
  "tags":["粗", "大","猛"]
}

PUT test_index/_doc/5
{
  "name":"魏行首",
  "age":25,
  "from":"广云台",
  "desc":"仿佛兮若轻云之蔽月,飘飘兮若流风之回雪,mmp,最后竟然没有嫁给顾老二!",
  "tags":["闭月","羞花"]
}

1.2 match和term

match 和 term 中必须要加条件,但是我们有时候需要查询所有,不带条件,需要用到 match_all
match:会将查询条件分词,然后进行查询。会先对搜索词进行分词,比如“白雪公主和苹果”,会分成“白雪”“公主”“苹果”。含有相关内容的字段,都会被检索出来。
term:会将查询条件完整的匹配相关字段。在ES中保存的文档,都会对单词进行分词处理,然后建立倒排索引,比如我们存了一个HelloWorld,但是很有可能会被分词成hello和world两个单词,这个时候如果你用term查询,通过HelloWorld是查不到的,因为term是不做分词处理的。这个时候可以通过match或者字段.keyword来代替。
通过term这种查询方式在blogs这个索引中查找title为Quick disjunction的文档,是找不到的,因为Quick disjunction已经做了分词处理
POST blogs/_search
{
  "query": {
    "term": {
      "title": {  # 查找的字段
        "value": "Quick disjunction"  # 查找的数据
      }
    }
  }
}


将上面的语句稍作改动即可
POST blogs/_search
{
  "query": {
    "term": {
      "title.keyword": { # 每个字段都有一个keyword这个属性,是没有分词之前元数据
        "value": "Quick disjunction"
      }
    }
  }
}

做查询,返回的结果会给每一个文档做一个相关性算分,用_score来表示,如果一个查询匹配到多条数据,那么_score最高的会排在最前面,表示匹配度最高。这个算分的过程其实是比较消耗性能的,如果我们不关注这个属性的话,可以通过Filter的方式绕过算分这个环节,避免一些开销,并且Filter还可以利用缓存,提升查询效率。

POST blogs/_search
{
  "query": {
    "constant_score": {  # constant:常数,表示跳过算分,返回的_score是一个常数
      "filter": {
        "term": {
          "title.keyword": {
            "value": "Quick disjunction"
          }
        }
      }
    }
  }
}

但是可能大多数情况下,我们还是比较关注score这个分数的,甚至有可能希望为某一次特殊的查询去调整这个分数,比如百度竞价排名的广告。。那么这个时候可以通过另一个参数boost (提高,使增长)这个参数去实现。他是一个浮点类型的数字,默认是1.0,如果我们想去降低分数,只需要把他控制在0.0-1.0之间,越小分数越低,如果我们想提高分数,只需要把他从1.0开始往大调整,越大算出来的分数越高。

POST blogs/_search
{
  "query": {
    "term": {
      "title.keyword": {
        "value": "Quick disjunction",
        "boost": 2
      }
    }
  }
}

默认情况下,Elasticsearch 将字词查询限制为最多65536个字词。 这包括使用术语查找获取的术语。 你可以使用 index.max_terms_count 设置更改此限制。

1.3 match_all

# 查询所有
GET test_index/_search
{
  "query": {
    "match_all": {}
  }
}

1.4 前缀查询match_phrase_prefix

# 查英文   beautiful   --->be开头的---》能查到

GET test_index/_search
{
  "query": {
    "match_phrase_prefix": {
      "name": "顾"
    }
  }
}

1.5 match_phrase。短语模糊查询.

即它会将给定的短语(phrase)当成一个完整的查询条件。match_phrase与slop一起用,能保证分词间的邻近关系,slop参数告诉match_phrase查询词条能够相隔多远时仍然将文档视为匹配,默认是0。为0时必须相邻才能被检索出来。

# 会分词,分词完成后,如果写了slop,会按分词之间间隔是slop数字去抽

GET t1/doc/_search
{
  "query": {
    "match_phrase": {
      "title": {
        "query": "中国世界",
        "slop": 2
      }
    }
  }
}

1.6 多条件查询,或的关系

# 只要name或者desc中带龙套就查出来

GET test_index/_search  
{
  "query": {
    "multi_match": {
      "query": "龙套",
      "fields": ["name", "desc"]
    }
  }
}

1.7 指定查询字段

GET test_index/_search 
{
  "query": { "match_all": {} },
  "_source": ["id", "createTime"]
}

1.8 wildcard通配符查询。

match_phrase与slop一起用,能保证分词间的邻近关系,slop参数告诉match_phrase查询词条能够相隔多远时仍然将文档视为匹配,默认是0。为0时 必须相邻才能被检索出来。与mysql中的 Like 是一个套路,可以在查询时,在字符串中指定通配符*和占位符?Wildcard 性能会比较慢。如果非必要,尽量避免在开头加通配符 ? 或者 *,这样会明显降低查询性能。

wildcard查询支持下面几个参数:

  • value:查询条件,就是我们指定的通配符,目前支持两种:表示可以匹配0到多个字符,?表示匹配任何单个字符。注意在正则表达式中,不能用或者?开头,因为这样可能匹配到大量的数据,导致性能下降严重。
  • boost:用来减少或增加查询相关性算分的参数。
  • case_insensitive:默认值false,如果设置为true,表示不区分大小写。
  • rewrite:可以重写查询方法,目前还没实践到,关于更多说明可以参考:rewrite parameter
 {
    "wildcard": {
        "form_name.keyword": "*very*"
    }
}

# 查询的内容非空
{
    "wildcard": {
        "form_name": "*"
    }
}
 
GET test_index/_search
{
  "query": {
    "wildcard": {
      "user.id": {
        "value": "ki*y",
        "boost": 1.0,
        "rewrite": "constant_score"
      }
    }
  }
}

1.9 查看分词结果

# fieldName可以字段名,也可以是字段名.
http://localhost:9200/{index}/{type}/{id}/_termvectors?fields={fieldName} 
# 具体示例
http://localhost:9200/a_index_text/_doc/n1A6ioUBFQ9q5qTUK1mH/_termvectors?fields=phone.short_char

利用该字段的分词器查看分词结果。查看user_addresses索引上address_name字段的分词效果

GET user_addresses/_analyze
{
  "field": "address_name",
  "text": "山东省青岛市黄岛区"
}

查看指定分词器及filter(GET或者POST都行)

GET _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text":"Hello WORLD"
}
# 结果
{
  "tokens" : [
    {
      "token" : "hello",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "world",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 1
    }
  ]
}

1.10查看指定索引总数据量

GET rest_logs-*/_count

# 结果
{
  "count" : 12371823,
  "_shards" : {
    "total" : 631,
    "successful" : 631,
    "skipped" : 0,
    "failed" : 0
  }
}

1.11 统计总存储空间占用

GET /_cat/shards?v

# 结果
index                                     shard prirep state       docs   store ip             node
rest_logs-2022.08.03                      0     r      STARTED  8357996   2.6gb ***    default-es-default-0
rest_logs-2022.08.03                      0     p      STARTED  8357996   2.6gb ***  default-es-default-3
rest_logs-2022.10.08                      0     r      STARTED   721722 290.6mb ***  default-es-default-4

1.12 查看正在数据迁移的索引及进度

GET _cat/recovery
rt_yonghu_serv_hsicrm_wo_userauthenticationbase       0 2.2m  peer        done  172.16.81.124 es-cn-7pp29cxc0000dlvzw-8e9e6447-0008 172.16.81.119 es-cn-7pp29cxc0000dlvzw-8e9e6447-0006 n/a n/a 159 159 100.0% 159 2658832139  2658832139  100.0% 2658832139  0       0       100.0%
rt_yonghu_serv_hsicrm_wo_userauthenticationbase       0 2.5m  peer        done  172.16.81.120 es-cn-7pp29cxc0000dlvzw-8e9e6447-0005 172.16.81.121 es-cn-7pp29cxc0000dlvzw-8e9e6447-0002 n/a n/a 150 150 100.0% 150 2659359161  2659359161  100.0% 2659359161  0       0       100.0%

1.13 ids查询。

ids是相对来说比较简单的一种dsl,类似于mysql的where id in ()的语义,他支持value属性,可以传入一个数组,里面填上你想要查询的ID的文档即可

GET rest_logs-*/_search
{
  "query": {
    "ids" : {
      "values" : ["1", "4", "100"]  # 返回属性_id为14100的文档,如果存在的话
    }
  }
}

1.14 exists(Exists Query)

exists是用来匹配文档的mapping中是否包含某一个字段,如果是就返回。比如我有下面两个文档:ID为4的文档有两个字段:title和body,ID为5的文档还有一个字段content。那么如果我们想要查询包含content字段的文档,就可以用exists去做。

POST test_index/_search
{
  "query": {
    "exists": {"field": "content"}
  }
}

还有一点需要注意的是,如果content的value值为null或者[],那么是不会被匹配到的。

1.15 prefix (Prefix Query)

prefix查询很好理解,就是将查询关键字作为一个前缀进行匹配,比如下面的例子,如果title是以Ela作为前缀的都会被匹配到,这里是区分大小写的,如果要设置不区分大小写,可以将case_insensitive设置为true。

POST test_index/_search
{
  "query": {
    "prefix": { "title.keyword":"Ela"}
  }
}

1.16 fuzzy(Fuzzy Query)

上面提到的term查询是一种精确查询,必须要求你输入的查询条件和文档中的数据完全匹配才可以。但是有时候可能用户忘了一个单词怎么写,只记得前3个字母,或者拼写错了,那么如果用term是查不到的。这个时候我们就需要根据用户的输入做一个猜测,进行一个模糊匹配。那么fuzzy正好是为了解决这个问题而出现的。

为了理解fuzzy的用法,这里我们需要先了解一个概念:编辑距离,他是一个单词要变成另一个单词,需要经过几次转换的这个次数。比如java这个单词要变成jbva,只需要将b替换成a,因此这个编辑距离就是1。那么这个转换总共有四种手段

改变其中一个字母,比如box -> fox
删除其中一个字符,比如black -> lack
插入一个新的字母,比如sic -> sick
调整两个相邻字母的位置,比如cat -> act
fuzzy实现模糊查询就是基于这个编辑距离去做的,他会将用户输入的搜索条件,结合一个编辑距离,然后基于我们上面提到的四种手段去做一个扩展和变形,得到一个集合,这个过程我们可以叫做扩展模糊选项,然后将扩展后的模糊选项作为查询条件展开精确匹配,最终将所有的结果进行返回。那么这个编辑距离就需要我们去指定,在es中是通过fuzziness去指定的,他的含义就是最大编辑距离。

POST test_index/_search
{
  "query": {
    "fuzzy": {
      "title.keyword": {
        //es中存储的是ElasticSearch,编辑距离是2,那么只要你的输入经过两次转换都变成ElasticSearch,就会匹配到
        "value": "ElasticSearh",   
        "fuzziness": 2
      }
    }
  }
}

fuzzy除了可以指定最大编辑距离之外,还有其他几个参数,这里在做一个总结:

fuzziness:最大编辑距离,值可以是数字类型,也可以是AUTO:[low],[high]这种格式,表示如果查询的单词长度在 [0,low) 这个范围内,编辑距离为0,如果在 [low,high) 这个范围之内,编辑距离为1,如果大于high,则编辑距离为2;除了这两种格式外,还支持AUTO这种写法,等同于AUTO:3,6

max_expansions:最大扩展数量,前面我们提到了扩展模糊选项,假如一个查询扩展了3到5个扩展选项,那么是是很有意义的,如果扩展了1000个模糊选项,其实也就意义不大了,会让我们又迷失在海量的数据中。因此有了max_expansions这个参数,限制最大扩展数量,默认值是50。切记这个值不可以太大,否则会导致性能问题

prefix_length:指定开始多少个字符不可以被模糊

transpositions:boolean值,默认true,表示扩展模糊选项的时候,是否允许两个相邻字符的位置互换。实践过程设置了false,理论上我存储ElasticSearch,检索ElasticSaerch应该搜不到,但是却搜索到了。有点不太理解。

1.17 range(Range Query)

range查询,更好理解了,就是范围查询嘛,他的语法可以参考下面的代码。这里呢出现了gte、lte是什么意思呢,其实就是ES中对于范围的描述,这里给大家总结一下:

gt:大于
gte:大于等于
lt:小于
lte:小于等于

GET test_index/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 10,
        "lte": 20,
        "boost": 2.0
      }
    }
  }
}

除了上面说到的表示范围的参数之外,range查询还支持下面的一些参数:

format:string类型,支持对查询的时间进行格式化,如果我们不提供会用默认的格式:“yyyy-MM-dd”
time_zone:用来指定时区,可以是UTC时区的偏移量,比如+01:00,或者IANA时区,比如America/Los_Angeles
relation:用来表示我们指定的范围和文档中数据的关系。一共有三种枚举值:

  • INTERSECTS (Default):匹配的文档中的范围字段的值和我们的指定的查询范围是一个交集的关系
  • CONTAINS:匹配的文档中的范围字段的值完全包含了我们指定的查询范围
  • WITHIN:匹配的文档中的范围字段的值在我们的查询范围之中

range查询也可以用在时间上,并且还可以做时间的计算,在es中,用y表示年,M表示月,d表示天,h表示小时。。。因此可以用+1d、-2h这种方法来进行时间的计算,可以通过/d表示四舍五入到最近的一天。也可以支持now取当前时间。关于时间计算的更多资料可以参考:Date Math

GET test_index/_search
{
  "query": {
    "range": {
      "timestamp": {
        "time_zone": "+01:00", 
        "gte": "2020-01-01T00:00:00", 
        "lte": "now"
      }
    }
  }
}

1.18 regexp(Regexp Query)

通配符查询,功能还是太有限了,如果能支持正则表达式岂不是堪称完美?说曹操曹操到,正则表达式查询,ES也为我们提供了。例如下面的例子,会匹配到user.id是以k开头,y结尾的单词。中间的.*表示匹配任意长度的任意字符像ky、kay、kimchy等都可以被匹配到。

在regexp中,有这么几个参数,需要关注一下,除过value,其他都是可选参数:

  • value:查询条件,指定我们输入的正则表达式,关于更多的正则表达式语句可以参考:Regular expression syntax
  • flags:可以通过这个参数为正则表达式提供一些更丰富的选项
    • ALL:默认值,允许所有的操作符
    • COMPLEMENT:允许操作符,用来实现一个求反的效果,例如:abc可以匹配到adc,aec,但是不会匹配到abc。
    • INTERVAL:允许操作符<>,用来匹配数字范围,例如:foo<01-100>可以匹配到foo01一直到foo100
    • INTERSECTION:允许操作符&,用来and的意思,就是说&符号两遍的表达式都满足才能被匹配到,例如aaa.+&.+bbb可以匹配到aaabbb
    • ANYSTRING:允许操作符@,用来匹配任何一个完整的字符串。例如@&~(abc.+)匹配任何字符串,除过以abc开头的
  • case_insensitive:默认值false,如果设置为true,表示正则表达式不区分大小写。
  • max_determinized_states:据官网介绍,es底层对正则表达式的解析是通过lucene来实现的,那么在lucene解析正则表达式的过程中,会将每个正则表达式转换为包含多个确定状态的状态机,默认值是10000,我们也可以通过这个参数去修改。而且据官网说这个生成状态机的过程是一个很耗时的过程,为了防止资源耗尽,我们应该控制这个参数。如果一个正则表达式特别复杂的话,也可以适当的去把这个参数往大调整。
  • rewrite:可以重写查询方法,目前还没实践到,关于更多说明可以参考:rewrite parameter
GET test_index/_search
{
  "query": {
    "regexp": {
      "user.id": {
        "value": "k.*y",
        "flags": "ALL",
        "case_insensitive": true,
        "max_determinized_states": 10000,
        "rewrite": "constant_score"
      }
    }
  }
}

2、Elasticsearch之排序查询

# 结构化查询 
GET test_index/_search
{
  "query": {
    "match": {
      "name": "zz"
    }
  }
}


# 排序查询---》可以按多个排序条件-->sort的列表中继续加
GET test_index/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "sort": [
    {
      "age": {
        "order": "asc"
      }
    }
  ]
}


# 不是所有字段都可以排序--->只能是数字字段

3、Elasticsearch之分页查询

# from 和 size,from表示偏移量,从0开始,size表示每页显示的数量

GET test_index/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ], 
  "from": 3,
  "size": 2
}

4、Elasticsearch之布尔查询

#   must(and)  should(or)  must_not(not)    filter

# must条件  and条件
GET test_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            # 模糊查询
            "from": "sheng"
             #精确查询
            # "from.keyword": "sheng"
          }
        },
        {
          "match": {
            "age": 18
          }
        }
      ]
    }
  }
}


"""
# 咱们认为的and查询,但是实际不行
GET test_index/_search
{
  "query": {
    "match": {
      "from": "sheng",
      "age":18
    }
  }
}
"""


# shoud   or 条件---》搜索框,就是用它
GET test_index/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "from": "sheng"
          }
        },
        {
          "match": {
            "age": 22
          }
        }
      ]
    }
  }
}



# must_not  既不是也不是
GET test_index/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "from": "gu"
          }
        },
        {
          "match": {
            "tags": "可爱"
          }
        },
        {
          "match": {
            "age": 18
          }
        }
      ]
    }
  }
}



# 查询 from为gu,age大于25的数据怎么查 filter  /gt> lt< lte=
GET test_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "gu"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "lt": 25
          }
        }
      }
    }
  }
}

# age字段等于40且state字段不包含ID的文档
GET test_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

5、Elasticsearch之查询结果过滤

# 只查某几个字段

GET test_index/_search
{
  "query": {
    "match": {
      "name": "顾老二"
    }
  },
  "_source": ["name", "age"]
}

6、Elasticsearch之高亮查询

# 默认高亮
GET test_index/_search
{
  "query": {
    "match": {
      "name": "石头"
    }
  },
  "highlight": {
    "fields": {
      "name": {}
    }
  }
}


# 自定义高亮
GET test_index/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "highlight": {
    "pre_tags": "<b class='key' style='color:red'>",
    "post_tags": "</b>",
    "fields": {
      "from": {}
    }
  }
}

7、Elasticsearch之聚合函数

# avg   max   min   sum
GET iot_deviceop/_search
{
  "aggs": {
    "aggs_total": {
      "stats": {
        "field": "ts"
      }
    }
  },
  "size": 0
}
# 结果
{
  "took" : 3871,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "aggs_total" : {
      "count" : 420275990,
      "min" : 1.04328556706E12,
      "max" : 1.689223778363E12,
      "avg" : 1.6677018684532202E12,
      "sum" : 7.008950537890268E20
    }
  }
}

# avg 求平均年龄
GET test_index/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_avg": {
      "avg": {
        "field": "age"
      }
    }
  },
  "_source": ["name", "age"]
}


# 对搜索结果进行聚合,使用aggs来表示,类似于MySql中的group by,例如对name字段进行聚合,统计出相同name的文档数量
GET test_index/_search
{
  // 不显示命中(hits)的所有文档信息
  "size": 0,
  "aggs": {
    "group_by_name": {
      "terms": {
        "field": "name.keyword"
      }
    }
  }
}
# 结果
{
  "took" : 170,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_vin" : {
      "doc_count_error_upper_bound" : 33265,
      "sum_other_doc_count" : 1767999,
      "buckets" : [
        {
          "key" : "第1天签到",
          "doc_count" : 4123788
        },
        {
          "key" : "第2天签到",
          "doc_count" : 2237997
        },
        {
          "key" : "第3天签到",
          "doc_count" : 1842282
        },
        {
          "key" : "第4天签到",
          "doc_count" : 1615952
        },
        {
          "key" : "第5天签到",
          "doc_count" : 1466083
        },
        {
          "key" : "第6天签到",
          "doc_count" : 1365050
        },
        {
          "key" : "第7天签到",
          "doc_count" : 1275374
        },
        {
          "key" : "食材临期即时提醒",
          "doc_count" : 321093
        },
        {
          "key" : "解锁多种智能模式",
          "doc_count" : 289915
        },
        {
          "key" : "远程操控冰箱温度",
          "doc_count" : 282822
        }
      ]
    }
  }
}


# 嵌套聚合,例如对name字段进行聚合统计出相同name的文档数量,再统计出聚合之后的age的平均值
GET test_index/_search
{
  "size": 0,
  "aggs": {
    "group_by_name": {
      "terms": {
        "field": "name.keyword"
      },
      "aggs": {
        "average_age": {
          "avg": {
            "field": "age.keyword"
          }
        }
      }
    }
  }
}
# 结果
{
  "took" : 558,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_vin" : {
      "doc_count_error_upper_bound" : 33265,
      "sum_other_doc_count" : 1767999,
      "buckets" : [
        {
          "key" : "第1天签到",
          "doc_count" : 4123788,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "第2天签到",
          "doc_count" : 2237997,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "第3天签到",
          "doc_count" : 1842282,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "第4天签到",
          "doc_count" : 1615952,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "第5天签到",
          "doc_count" : 1466083,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "第6天签到",
          "doc_count" : 1365050,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "第7天签到",
          "doc_count" : 1275374,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "食材临期即时提醒",
          "doc_count" : 321093,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "解锁多种智能模式",
          "doc_count" : 289915,
          "average_age" : {
            "value" : null
          }
        },
        {
          "key" : "远程操控冰箱温度",
          "doc_count" : 282822,
          "average_age" : {
            "value" : null
          }
        }
      ]
    }
  }
}



# 对聚合搜索的结果进行排序,例如对name字段进行聚合统计出相同name的文档数量,再统计出聚合之后的age的平均值,按age的平均值降序排列
GET test_index/_search
{
  "size": 0,
  "aggs": {
    "group_by_vin": {
      "terms": {
        "field": "name.keyword",
        "order": {
          "average_age": "desc"
        }
      },
      "aggs": {
        "average_age": {
          "avg": {
            "field": "age.keyword"
          }
        }
      }
    }
  }
}



# 按字段值的范围进行分段聚合,例如分段范围为age字段的[20,30] [30,40] [40,50],之后按gender统计文档个数和balance的平均值
GET test_index/_search
{
  "size": 0,
  "aggs": {
    "group_by_age": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 20,
            "to": 30
          },
          {
            "from": 30,
            "to": 40
          },
          {
            "from": 40,
            "to": 50
          }
        ]
      },
      "aggs": {
        "group_by_gender": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance.keyword"
              }
            }
          }
        }
      }
    }
  }
}

# 根据name进行分组,并且取前20GET test_index/_search
{
  "size": 0,
  "aggs": {
    "group_by_vin": {
      "terms": {
        "field": "name.keyword",
        # 取前20"size": 20
        }
      }
    }
  }
}

# 多个平行聚合
GET test_index/_search
{
  "size": 0,
  "aggs": {
      "my-agg-name": {
          "terms": {
            "field": "requestUrl.keyword",
            "size": 5
          }
      },
      "my-agg-name2": {
          "terms": {
            "field": "requestHeader.clientId.keyword",
            "size": 5
          }
      }
    }
}
# 结果
{
  "took" : 6180,
  "timed_out" : false,
  "num_reduce_phases" : 2,
  "_shards" : {
    "total" : 624,
    "successful" : 624,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "my-agg-name2" : {
      "doc_count_error_upper_bound" : 9661,
      "sum_other_doc_count" : 89146934,
      "buckets" : [
        {
          "key" : "callcenter",
          "doc_count" : 147486013
        },
        {
          "key" : "duomai",
          "doc_count" : 49268821
        },
        {
          "key" : "service_employee",
          "doc_count" : 42791694
        }
      ]
    },
    "my-agg-name" : {
      "doc_count_error_upper_bound" : 1819770,
      "sum_other_doc_count" : 236828042,
      "buckets" : [
        {
          "key" : "/api/ecoc/v1/kafkaSend/duoMaiExcute",
          "doc_count" : 49263156
        },
        {
          "key" : "/api/admin/oauth/token",
          "doc_count" : 48386545
        },
        {
          "key" : "/api/dss/userLable/v2/getUserLable",
          "doc_count" : 42795064
        }
      ]
    }
  }
}

# 根据requestUrl进行分组,只取前10个,然后对每个没组里的数据按照@timestamp倒序排列取一条数据(数据里包含requestTime,requestUrl,requestHeader.clientId三个字段),对10条数据分页查询,每页2条;统计所有不同requestUrl的数量
GET rest_logs-*/_search
{
    "query": {
      "match_all": {}  
    },
    "aggs": {
      	# 统计所有requestUrl不同的总数
        "count": {
            "cardinality": {
                "field": "requestUrl.keyword"
            }
        },
        "requestUrl_agg": {
            "terms": {
              	# 按照requestUrl分组
                "field": "requestUrl.keyword",
              	# 取10"size": 10,
              	# 按照分组里的数量倒序排列
          			# 如果是按照分组字段排序,则使用_key
                "order": {
                  "_count": "desc"
                }
            },
            "aggs": {
              	# 自定义名称
                "group_sort": {
                    "top_hits": {
              					# 排序
                        "sort": [
                            {
              									# 分组里按照@timestamp倒序排列
                                "@timestamp": {
                                    "order": "desc"
                                }
                            }
                        ],
												# 分组里的数据,排序后取值的字段
                        "_source": {
                          "includes": [
                            "requestTime",
                            "requestUrl",
                            "requestHeader.clientId"
                            ]
                        }, 
												# 取一条数据,不取一条会报错
												# 报的错:numHits must be > 0; please use TotalHitCountCollector if you just need the total hit count
                        "size": 1
                    }
                },
								# 分页查询
                "bucket_sort": {
                    "bucket_sort": {
                        "sort": [],
                      	# 从哪个位置开始取,offset
                        "from": 0,
                      	# 每页的数量
                        "size": 2
                    }
                }
            }  
        }
    },
		# 此处的size和from没用了
    "size": 0,
    "from": 0
}
# 结果
{
  "took" : 3719,
  "timed_out" : false,
  "num_reduce_phases" : 2,
  "_shards" : {
    "total" : 624,
    "successful" : 624,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "count" : {
      "value" : 221
    },
    "requestUrl_agg" : {
      "doc_count_error_upper_bound" : 121353,
      "sum_other_doc_count" : 57937638,
      "buckets" : [
        {
          "key" : "/api/admin/oauth/token",
          "doc_count" : 49542152,
          "group_sort" : {
            "hits" : {
              "total" : {
                "value" : 49542152,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "rest_logs-2023.01.05",
                  "_type" : "_doc",
                  "_id" : "kxcNgYUBn8gePaBo6-bM",
                  "_score" : null,
                  "_source" : {
                    "requestTime" : "2023-01-05 16:30:51",
                    "requestUrl" : "/api/admin/oauth/token"
                  },
                  "sort" : [
                    1672907451313
                  ]
                }
              ]
            }
          }
        },
        {
          "key" : "/api/ecoc/v1/kafkaSend/duoMaiExcute",
          "doc_count" : 49333145,
          "group_sort" : {
            "hits" : {
              "total" : {
                "value" : 49333145,
                "relation" : "eq"
              },
              "max_score" : null,
              "hits" : [
                {
                  "_index" : "rest_logs-2023.01.05",
                  "_type" : "_doc",
                  "_id" : "ghcNgYUBn8gePaBo6-bM",
                  "_score" : null,
                  "_source" : {
                    "requestTime" : "2023-01-05 16:30:51",
                    "requestUrl" : "/api/ecoc/v1/kafkaSend/duoMaiExcute",
                    "requestHeader" : {
                      "clientId" : [
                        "duomai"
                      ]
                    }
                  },
                  "sort" : [
                    1672907451132
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  }
}

# top_hits查询 查询ts最大的两条信息
GET iot_deviceop/_search
{
  "size": 0, 
  "aggs": {
    "top_age_infot": {
      "top_hits": {
        "size": 2,
        "sort": [
              {
              "ts": {
              "order":"desc"
              }
            }
          ]
      }
    }
  }
}

# max_bucket查询每个桶的最大值,min_bucket查询每个桶的最小值
GET employee/_search
{
  "size": 0, 
  "aggs": {
    "job_info": {
      "terms": {
        "field": "job"
      },
      "aggs": {
        "job_avg_info": {
          "avg": {
            "field": "sal"
          }
        }
      }
    },
    "min_avg_sal_job":{
      "max_bucket": {
        //查询平均工资最高的岗位
        "buckets_path": "job_info>job_avg_info"  
      }
    }
  }
}

8、创建更新删除操作

8.1、创建索引并查看

PUT test_index
GET /_cat/indices?v

8.2、删除索引并查看

DELETE test_index
GET /_cat/indices?v

8.3、查看文档类型

GET test_index/_mapping

8.4、在索引中添加文档

PUT test_index/1
{
  "vin": "B1293049555"
}

8.5、在索引中查看文档

GET /decode/resolve/1

8.6、在索引中修改文档

POST test_index/1/_update
{
  "doc": { "vin": "A1299405555" }
}

8.7、在索引中删除文档

DELETE test_index/1

8.8、在索引中批量操作文档

POST test_index/_bulk
{"index":{"_id":"1"}}
{"name": "aaaa" }
{"index":{"_id":"2"}}
{"name": "bbbb" }

8.9、创建一个数据模型样例(包含index、type、field):

PUT test_index
{
    "settings":{
        "index":{
            "number_of_shards":3,
            "number_of_replicas":0
        }
    },
    "mappings":{
        "resolve":{
            "properties":{
                "id":{
                    "type":"keyword"
                },
                "name":{
                    "type":"keyword"
                },
                "gender":{
                    "type":"keyword"
                },
                "carBrandId":{
                    "type":"text"
                },
                "balance":{
                    "type":"int"
                },
                "age":{
                    "type":"int"
                },
                "address":{
                    "type":"keyword"
                },
                "craeteTime":{
                    "type":"date"
                },
                "statusId":{
                    "type":"keyword"
                },
                "enable":{
                    "type":"boolean"
                }
            }
        }
    }
}

8.10、Elasticsearch给type增加一项field

PUT test_index/_mapping
{
 "properties": {
   "fieldName":{
     "type":"boolean"
   }
 }
}

PUT test_index/_mappings
{
 "properties": {
   "fieldName":{
     "type":"boolean"
   }
 }
}

二、ES查询相关Java的API

1、Java三种操作ES数据库的方式

以下是这三种操作方式(ElasticsearchTemplate、ElasticsearchRepository和ElasticsearchRestTemplate)与Elasticsearch版本的大致对应关系:

  • ElasticsearchTemplate:ElasticsearchTemplate通常与早期版本的Elasticsearch(例如2.x系列和早期的5.x系列)一起使用。这种方式在早期的Spring Data Elasticsearch版本中是常见的。

  • ElasticsearchRepository:ElasticsearchRepository从Spring Data Elasticsearch 3.0版本开始引入,并且在较新的版本中也得到了支持。您可以在3.x版本和4.x版本的Spring Data Elasticsearch中使用ElasticsearchRepository。请注意,具体支持的特性和功能可能会因版本而异。

  • ElasticsearchRestTemplate:ElasticsearchRestTemplate是从Spring Data Elasticsearch 4.0版本开始引入的新特性。它基于Elasticsearch高级REST客户端,这个客户端本身也是从Elasticsearch 6.0版本开始引入的。因此,ElasticsearchRestTemplate主要用于较新版本的Elasticsearch(6.x及更高版本)。

Elasticsearch从6.x升级到7.x改动超级大,ElasticsearchTemplate不建议使用了,改为使用ElasticsearchRestTemplate,ElasticsearchRepository实现复杂查询的方法也不建议使用了。从此我们简单的数据操作可以使用ElasticsearchRepository,而复杂的数据操作只能使用ElasticsearchRestTemplate了。

因此,我们使用SpringBoot集成ES数据库的时候一般采用Elasticsearch 7.x及以上版本。

注意:这三种操作方式都需要集成Spring Data Elasticsearch依赖才能使用。

2、JavaAPI环境准备

新建Maven工程。

添加依赖:

<!-- elasticsearch 的客户端 -->
<dependency>
	<groupId>org.elasticsearch.client</groupId>
	<artifactId>elasticsearch-rest-high-level-client</artifactId>
	<version>7.8.0</version>
</dependency>

<!--整合SpringData,版本是基于SpringBoot的版本来定义的-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

编写配置文件:

spring:
  application:
    name: service-search
  # ES服务地址
  elasticsearch:
    rest:
      # 集群使用逗号分开
      uris: http://10.11.24.200:9201,http://10.11.24.200:9202,http://10.11.24.200:9203
      username: elastic
      password: elastic

3、ElasticsearchRepository操作

实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "person",createIndex = true)
public class Person implements Serializable {
    @Id
    @Field(type = FieldType.Auto)
    private String id;
    
    @Field(type = FieldType.Text,index = true)
    private String name;
    
    @Field(type = FieldType.Integer,index = true)
    private Integer age;
    
    @Field(type = FieldType.Keyword,index = true)
    private String sex;
    
    @Field(type = FieldType.Keyword,index = true)
    private String tel;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Field(type = FieldType.Date, format = DateFormat.date_time, pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
}

接口类:

public interface PersonRepository extends ElasticsearchRepository<Person,String> {
    
}

操作类:

@SpringBootTest(classes = SpringBootApplicationMain.class)
public class ApplicationMainTest {
 
    @Resource
    private PersonRepository personRepository;
 
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
 
    @Test
    public void test(){
        System.out.println(personRepository);
        System.out.println(elasticsearchRestTemplate);
    }
    
    @Test
    public void saveOne(){
        Person person = new Person();
        person.setId("1");
        person.setName("王天龙");
        person.setAge(30);
        person.setSex("man");
        person.setTel("1111111");
        person.setCreateTime(new Date());
        Person savePerson = personRepository.save(person);
        System.out.println(savePerson);
    }
    
    @Test
    public void saveAll(){
        List<Person> personList = new ArrayList<>(3);
        Person person2 = new Person();
        person2.setId("2");
        person2.setName("王天祥");
        person2.setAge(26);
        person2.setSex("男");
        person2.setTel("222222222222");
        person2.setCreateTime(new Date());
        personList.add(person2);
 
        Person person3 = new Person();
        person3.setId("3");
        person3.setName("王杰");
        person3.setAge(31);
        person3.setSex("女");
        person3.setTel("3333333333");
        person3.setCreateTime(new Date());
        personList.add(person3);
        personRepository.saveAll(personList);
    }
    
}

3.1、简单操作的接口

在这里插入图片描述

3.2、复杂操作

您可以创建一个BookRepository接口继承自ElasticsearchRepository,并定义一些查询方法:

接口类:

public interface PersonRepository extends ElasticsearchRepository<Person,String> {
    
    List<Person> findByName(String name);

    List<Person> findByTel(String tel);

    List<Person> findByNameAndTel(String name, String tel);
}

在上面的示例中,PersonRepository继承自ElasticsearchRepository<Person, String>,其中Person是实体类,String是实体类的主键类型。然后,您定义了几个查询方法,如findByNamefindByTelfindByNameAndTel,这些方法的命名规则会被Spring Data Elasticsearch解析为相应的查询操作。

3.2.1、Spring Data JPA写法

Spring Data Elasticsearch能够自动解析您自定义的接口方法,将其转换为相应的Elasticsearch查询。它基于方法的命名规则来生成查询,这些规则与Spring Data JPA类似。以下是一些常见的规律:

  1. 方法名关键字:方法名以findByfindAllBycountBydeleteBy等开头,后面跟着实体属性名,例如findByTitlefindAllByAuthorcountByCategory等。

  2. 属性名和操作符:在方法名中,您可以使用属性名和操作符,如findByTitleAndAuthorfindByPriceGreaterThanfindByPublishedDateBetween等。

  3. 逻辑连接词:您可以在方法名中使用逻辑连接词来组合多个条件,如findByTitleAndAuthorfindByCategoryOrPrice等。

  4. 排序和分页:您可以通过在方法名中添加OrderByPageable来指定排序和分页条件,例如findByTitleOrderByPublishedDateDescfindByAuthorOrderByPriceAsc等。

  5. 限制查询结果数量:使用findFirstfindTop等关键字来限制返回的查询结果数量,例如findTop5ByCategoryfindFirstByAuthorOrderByPublishedDateDesc等。

  6. 使用自定义查询:如果您的需求超出了自动生成的查询,您可以使用@Query注解,在方法上指定自定义的Elasticsearch查询语句。

以下是一个示例,有一个实体类Person,您可以定义一个PersonRepository接口来操作Elasticsearch,并按照命名规则定义一些查询方法:

import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface PersonRepository extends ElasticsearchRepository<Person, String> {

    List<Person> findByFirstName(String firstName);

    List<Person> findByLastNameAndAge(String lastName, int age);

    List<Person> findByAgeGreaterThan(int age);

    List<Person> findByFirstNameAndLastNameOrAge(String firstName, String lastName, int age);

    List<Person> findTop5ByAgeOrderByFirstNameAsc(int age);

    Person findFirstByLastNameOrderByAgeDesc(String lastName);
}

在上述示例中,每个方法名都遵循了Spring Data Elasticsearch的命名规则,可以自动生成相应的查询。如果您需要更复杂的查询,您还可以使用@Query注解来定义自定义的Elasticsearch查询语句。

3.2.2、@Query的写法

当使用Spring Data Elasticsearch中的@Query注解时,您可以自定义Elasticsearch的查询语句,以满足复杂的查询需求。以下是一个使用@Query的操作示例:

假设您有一个实体类Product表示产品信息:

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;

@Document(indexName = "products")
public class Product {

    @Id
    private String id;
    private String name;
    private String category;
    private double price;

    // Getters and setters
}

然后,您可以创建一个ProductRepository接口,并使用@Query注解来定义自定义查询方法:

import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface ProductRepository extends ElasticsearchRepository<Product, String> {

    @Query("{\"bool\": {\"must\": [{\"match\": {\"name\": \"?0\"}}]}}")
    List<Product> findByNameCustom(String name);

    @Query("{\"bool\": {\"must\": [{\"term\": {\"category\": \"?0\"}}, {\"range\": {\"price\": {\"gte\": ?1}}}]}}")
    List<Product> findByCategoryAndPriceRange(String category, double minPrice);
}

在上述示例中,我定义了两个使用@Query注解的自定义查询方法:

  1. findByNameCustom:这个方法使用了Elasticsearch的查询语法,使用match查询来查找指定名称的产品。

  2. findByCategoryAndPriceRange:这个方法使用了更复杂的查询,结合了term查询和range查询,以查找特定类别中价格在指定范围内的产品。

您可以根据自己的需求编写更复杂的查询语句,利用Elasticsearch的强大查询能力来满足各种查询需求。使用@Query注解时,请确保查询语句的语法正确,并且根据实际情况调整查询条件和参数。

在使用Spring Data Elasticsearch的@Query注解中,?0表示方法参数中的第一个参数,?1表示第二个参数,依此类推。这样,您可以在查询语句中引用方法的参数值,以动态地构建查询条件。

4、ElasticsearchRestTemplate操作

4.1、熟悉查询条件的构造

当构建Elasticsearch查询时,QueryBuildersNativeSearchQueryBuilderNativeSearchQuery以及其他类型的Builders(如AggregationBuildersSortBuilders等)是Spring Data Elasticsearch提供的一些工具和类。它们之间的关系如下所示:

                         +----------------+
                         |                |
                         |   QueryBuilders|       
                         |                |         
                         +----------------+
                                 |
                                 | 组合和构建各种查询
                                 |
                                 v
+---------------------+       +-------------------------+
|                     |       |                         |
|NativeSearchQueryBuilder|<---|    NativeSearchQuery    |
|                     |       |                         |
+---------------------+       +-------------------------+
        |                            |
        | 构建和配置查询条件            | 将查询条件传递给
        |                            | Elasticsearch操作
        v                            v
+---------------------+       +----------------------------+
|                     |       |  elasticsearchTemplate     |
|    AggregationBuilders|     |    或者                     |
|                     |       | elasticsearchRestTemplate  |
+---------------------+       +----------------------------+
        |                            |
        | 构建聚合操作                 | 执行各种Elasticsearch操作
        |                            |
        v                            v
  +-----------------+            +-------------------+
  |                 |            |                   |
  | SortBuilders    |            |       ...         |
  |                 |            |                   |
  +-----------------+            +-------------------+
  |                 |
  |    ...          |
  |                 |
  +-----------------+

(1)、关系1

QueryBuilderQueryBuilders是Elasticsearch Java客户端库提供的两个相关但不同的概念。

  1. QueryBuilder
    QueryBuilder是Elasticsearch Java客户端库中的接口,用于构建Elasticsearch查询。它提供了一种抽象方式来构建查询条件,可以用于构建各种类型的查询,如匹配、范围、布尔逻辑、嵌套查询等。QueryBuilder是一个用于构建查询对象的接口,您可以使用它来创建复杂的查询结构,然后传递给Elasticsearch客户端执行查询。

  2. QueryBuilders
    QueryBuilders是Elasticsearch Java客户端库中的一个工具类,用于创建不同类型的查询。它提供了一组静态方法,每个方法返回一个实现了QueryBuilder接口的查询对象,从而简化了构建查询的过程。通过使用QueryBuilders工具类,您可以直接调用方法来创建各种类型的查询,而无需手动创建QueryBuilder实例。

简而言之,QueryBuilder是一个接口,用于构建Elasticsearch查询对象,而QueryBuilders是一个工具类,用于创建实现了QueryBuilder接口的具体查询对象。您可以根据需要选择使用QueryBuilder接口来手动构建查询,或者使用QueryBuilders工具类来更方便地创建查询。

(2)、关系2

在Spring Data Elasticsearch中,NativeSearchQueryBuilderNativeSearchQuery是用于构建Elasticsearch查询条件的两个关键接口/类。它们之间的关系如下:

  1. NativeSearchQueryBuilderNativeSearchQueryBuilder是一个用于构建NativeSearchQuery的构造器类。它提供了一组方法,可以通过链式调用来配置和定制查询条件,例如添加过滤条件、排序、分页等。NativeSearchQueryBuilder最终会产生一个NativeSearchQuery实例,该实例表示一个完整的Elasticsearch查询。

  2. NativeSearchQueryNativeSearchQuery是用于表示Elasticsearch查询的接口。它包含了查询的各种组成部分,例如查询语句、过滤条件、排序规则、分页信息等。NativeSearchQuery可以被传递给Spring Data Elasticsearch的各种操作,例如查询、聚合等,以执行实际的Elasticsearch操作。

简而言之,NativeSearchQueryBuilder用于构建NativeSearchQuery,而NativeSearchQuery用于表示Elasticsearch查询,并作为参数传递给各种Spring Data Elasticsearch操作。

以下是一个简单的示例,演示如何使用NativeSearchQueryBuilder构建NativeSearchQuery

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

@Service
public class SearchService {

    private final ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    public SearchService(ElasticsearchTemplate elasticsearchTemplate) {
        this.elasticsearchTemplate = elasticsearchTemplate;
    }

    public List<Product> searchProductsByCategoryAndPriceRange(String category, double minPrice) {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.termQuery("category", category))
            .withFilter(QueryBuilders.rangeQuery("price").gte(minPrice))
            .build();

        return elasticsearchTemplate.queryForList(query, Product.class);
    }
}

在上述示例中,NativeSearchQueryBuilder用于构建一个查询,该查询过滤出特定类别并且价格高于指定最低价格的产品。然后,通过elasticsearchTemplate执行查询操作。

总结:NativeSearchQueryBuilder用于创建NativeSearchQuery,后者用于表示Elasticsearch查询。这两者之间的关系可以看作是构建和执行查询的关系。

注:

NativeSearchQueryBuilder可以使用的方法:

在这里插入图片描述

4.2、具体代码的执行

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringBootApplicationMain.class)
public class ApplicationMainTest {
 
    @Resource
    private PersonRepository personRepository;
 
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
 
    @Test
    public void test(){
        System.out.println(personRepository);
        System.out.println(elasticsearchRestTemplate);
    }
(1)创建索引
@Test
public void createIndex(){
    IndexCoordinates indexCoordinates = IndexCoordinates.of("person");
    IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(indexCoordinates);
    boolean created = indexOperations.create();
    System.out.println(created);
}
(2)设置mapping
@Test
public void setIndexMappings(){
    IndexCoordinates indexCoordinates = IndexCoordinates.of("person");
    IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(indexCoordinates);
    boolean created = indexOperations.putMapping(Person.class);
    System.out.println(created);
}
(3)设置settings
@Test
public void createIndexWithSetSettings(){
    IndexCoordinates indexCoordinates = IndexCoordinates.of("person");
    IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(indexCoordinates);
    Map<String,Object> settings = new HashMap<>();

    //----------------------------------------静态设置开始----------------------------------------------
    // 静态设置:只能在索引创建时或者在状态为 closed index(闭合的索引)上设置

    //主分片数,默认为5.只能在创建索引时设置,不能修改
    settings.put("index.number_of_shards",2);

    //是否应在索引打开前检查分片是否损坏,当检查到分片损坏将禁止分片被打开
    settings.put("index.shard.check_on_startup","false");//默认值
    //        settings.put("index.shard.check_on_startup","checksum");//检查物理损坏
    //        settings.put("index.shard.check_on_startup","true");//检查物理和逻辑损坏,这将消耗大量内存和CPU
    //        settings.put("index.shard.check_on_startup","fix");//检查物理和逻辑损坏。有损坏的分片将被集群自动删除,这可能导致数据丢失

    //自定义路由值可以转发的目的分片数。默认为 1,只能在索引创建时设置。此值必须小于index.number_of_shards
    settings.put("index.routing_partition_size",1);

    //默认使用LZ4压缩方式存储数据,也可以设置为 best_compression,它使用 DEFLATE 方式以牺牲字段存储性能为代价来获得更高的压缩比例。
    settings.put("index.codec","best_compression");
    //----------------------------------------静态设置结束----------------------------------------------


    //----------------------------------------动态设置开始----------------------------------------------
    //每个主分片的副本数。默认为 1。
    settings.put("index.number_of_replicas",0);

    //基于可用节点的数量自动分配副本数量,默认为 false(即禁用此功能)
    settings.put("index.auto_expand_replicas",false);

    //执行刷新操作的频率,这使得索引的最近更改可以被搜索。默认为 1s。可以设置为 -1 以禁用刷新。
    settings.put("index.refresh_interval","1s");

    //用于索引搜索的 from+size 的最大值。默认为 10000
    settings.put("index.max_result_window",10000);

    // 在搜索此索引中 rescore 的 window_size 的最大值
    settings.put("index.max_rescore_window",10000);

    //设置为 true 使索引和索引元数据为只读,false 为允许写入和元数据更改。
    settings.put("index.blocks.read_only",false);

    // 设置为 true 可禁用对索引的读取操作
    settings.put("index.blocks.read",false);

    //设置为 true 可禁用对索引的写入操作。
    settings.put("index.blocks.write",false);

    // 设置为 true 可禁用索引元数据的读取和写入。
    settings.put("index.blocks.metadata",false);

    //索引的每个分片上可用的最大刷新侦听器数
    settings.put("index.max_refresh_listeners",1000);
    //----------------------------------------动态设置结束----------------------------------------------

    boolean created = indexOperations.create(settings);
    System.out.println(created);
}
(4)删除索引
@Test
public void deleteIndex(){
    IndexCoordinates indexCoordinates = IndexCoordinates.of("person");
    IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(indexCoordinates);
    boolean isDeleted = indexOperations.delete();
    System.out.println(isDeleted);
}
(5)更新某条数据
@Test
public void updateOne(){
    Document document = Document.create();
    document.setId("1");
    document.put("name","天龙战神");

    UpdateQuery.Builder builder = UpdateQuery.builder("1").withDocument(document).withScriptedUpsert(true);

    UpdateResponse updateResponse = elasticsearchRestTemplate.update(builder.build(), IndexCoordinates.of("person"));

    System.out.println(updateResponse.getResult());
}
(6)查询所有数据MatchAll
@Test
public void searchMatchAll() throws IOException {
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().must(new MatchAllQueryBuilder());
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
        .withQuery(boolQueryBuilder);

    NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
    SearchHits<Person> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Person.class);

    searchHits.getSearchHits().forEach(personSearchHit -> {
        Person content = personSearchHit.getContent();
        System.out.println(content);
    });
}
(7)BoolMust多条件查询
@Test
public void searchBoolMustWhere(){
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.must(QueryBuilders.matchQuery("name","王天龙"));
    boolQueryBuilder.must(QueryBuilders.matchQuery("age",30));
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
        .withQuery(boolQueryBuilder);
    NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
    SearchHits<Person> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Person.class);

    searchHits.forEach(personSearchHit -> {
        System.out.println(personSearchHit.getContent());
    });
}
(8)BoolShould多条件查询
@Test
public void searchShouldWhere(){
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.should(QueryBuilders.matchQuery("name","天龙"));
    boolQueryBuilder.should(QueryBuilders.matchQuery("age",26));
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
        .withQuery(boolQueryBuilder);
    NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
    SearchHits<Person> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Person.class);
    searchHits.forEach(personSearchHit -> {
        System.out.println(personSearchHit.getContent());
    });
}
(9)Range查询
@Test
public void searchRange(){
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age").gte(26).lt(31);
    boolQueryBuilder.filter(rangeQueryBuilder);
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
        .withQuery(boolQueryBuilder);
    NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
    SearchHits<Person> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Person.class);
    searchHits.forEach(personSearchHit -> {
        System.out.println(personSearchHit.getContent());
    });
}
(10)聚合搜索1
/**
     * 聚合搜索
     * 聚合搜索,aggs,类似于group by,对age字段进行聚合,
     */
@Test
public void aggregations1() {
    NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
        .withAggregations(AggregationBuilders.terms("count").field("sex"))
        .build();

    SearchHits<Person> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Person.class);
    //取出聚合结果
    ElasticsearchAggregations elasticsearchAggregations = (ElasticsearchAggregations) searchHits.getAggregations();
    Aggregations aggregations = elasticsearchAggregations.aggregations();
    Terms terms = (Terms) aggregations.asMap().get("count");

    for (Terms.Bucket bucket : terms.getBuckets()) {
        String keyAsString = bucket.getKeyAsString();   // 聚合字段列的值
        long docCount = bucket.getDocCount();           // 聚合字段对应的数量
        System.out.println(keyAsString + " " + docCount);
    }
}
(11)聚合搜索2
@Test
public void aggregations2() {
    // 设置要聚合的字段
    TermsAggregationBuilder lyBucket = AggregationBuilders.terms("ly_bucket").field("firstTitleId.keyword").size(2^31-1);
    TermsAggregationBuilder tableBucket = AggregationBuilders.terms("table_bucket").field("tableId.keyword").size(2^31-1);
    // 构建高亮展示字段
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("content");
    highlightBuilder.preTags("<span style='color:#0077FA'>");
    highlightBuilder.postTags("</span>");
    // 指定聚合器中返回的文档数量,默认返回前3个
    tableBucket.subAggregation(AggregationBuilders.topHits("table_result").size(2^31-1).highlighter(highlightBuilder));
    // 合并子聚合
    lyBucket.subAggregation(tableBucket);
    // 构建查询对象
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
    nativeSearchQueryBuilder.withQuery(queryBuilder);
    nativeSearchQueryBuilder.addAggregation(lyBucket);
    SearchHits<ContentEntity> search = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), ContentEntity.class);
    Aggregations aggregations = search.getAggregations();
        if (aggregations != null) {
            Terms lyBucket1 = aggregations.get("ly_bucket");
            List<? extends Terms.Bucket> lyBuckets = lyBucket1.getBuckets();
            for (Terms.Bucket lyBucket2 : lyBuckets) {
                Terms tableBucket1 = lyBucket2.getAggregations().get("table_bucket");
                List<? extends Terms.Bucket> tableBuckets = tableBucket1.getBuckets();
                for (Terms.Bucket table : tableBuckets) {
                    TopHits topHits = table.getAggregations().get("table_result");
                    org.elasticsearch.search.SearchHits hits = topHits.getHits();
                    // 进行高亮展示,返回的值是已经高亮后的字段
                    List<ContentEntity> content = this.getContent(hits);
                    System.out.println(hits);
                    }
                }
            }
        }
}



/**
     * @return List<ContentEntity>
     * @description 给指定字段进行高亮展示
     * @author yandi
     * @date 2023/7/10 9:47
     * @param list
     */
    public List<ContentEntity> getContent(org.elasticsearch.search.SearchHits list){
        List<ContentEntity> listContentEntity = new ArrayList<>();
        list.forEach(v->{
            HighlightField content = v.getHighlightFields().get("content");
            if (content != null) {
                String highlightContent = content.getFragments()[0].toString();
                String sourceAsString = v.getSourceAsString();
                ContentEntity contentEntity = JSON.parseObject(sourceAsString, ContentEntity.class);
                contentEntity.setContent(highlightContent);
                listContentEntity.add(contentEntity);
            }
        });
        return listContentEntity;
    }
(12)分页实现
/**
     * 分页实现
     */
@Test
public void searchWithPageable(){
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().must(new MatchAllQueryBuilder());
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
        .withQuery(boolQueryBuilder)
        //分页实现 可以传递活值在PageRequest.of(0,2)里面
        .withPageable(PageRequest.of(0,2));

    NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
    SearchHits<Person> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Person.class);

    searchHits.getSearchHits().forEach(personSearchHit -> {
        Person content = personSearchHit.getContent();
        System.out.println(content);
    });
}
(13)排序实现
/**
     * 排序实现
     */
@Test
public void searchWithSort(){
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().must(new MatchAllQueryBuilder());
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
        .withQuery(boolQueryBuilder)
        //分页实现
        .withPageable(PageRequest.of(0,10))
        //排序
     .withSorts(SortBuilders.fieldSort("createTime").order(SortOrder.DESC),SortBuilders.fieldSort("age").order(SortOrder.DESC));

    NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
    SearchHits<Person> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Person.class);

    searchHits.getSearchHits().forEach(personSearchHit -> {
        Person content = personSearchHit.getContent();
        System.out.println(content);
    });
}
}

4.3、高亮操作

详情查看该网址:https://www.cnblogs.com/eternityz/p/17039582.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值