文章目录
ELASTICSEARCH
-
中文文档 https://www.kancloud.cn/yiyanan/elasticsearch_7_6/1651637
-
Elasticsearch是Elastic Stack核心的分布式搜索和分析引擎。
-
类比es与MySQL
es | mysql2 |
---|---|
索引 | 数据库 |
类型 | 数据表 |
文档 | 数据 |
- 倒排索引机制
- 分词:将整句拆分为单词,保存每个单词都有哪些记录与之对应
- 检索:将检索拆分为单词,并查询对应记录
- 相关性得分:谁的正确率更高
docker上安装
- 命令
free -m
可查看剩余内存
下载
- es
docker pull elasticsearch:7.6.2
- kibana(用于可视化,相当于sqlyog,可不安装,后面用postman等工具)
docker pull kibana:7.6.2
启动、设置开机自启
- 新建目录
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
echo "http.host: 0.0.0.0" >>/mydata/elasticsearch/config/elasticsearch.yml
- 权限
chmod -R 777 /mydata/elasticsearch/
- chmod -R 777:任何用户、任何组可读、可写、可执行
- 写入http.host: 0.0.0.0,允许所有ip访问
- 启动Elastic search
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.6.2
- -Xms64m -Xmx128m:初始64M,es最多占用128M
- -d:后台
- 设置开机启动elasticsearch
docker update elasticsearch --restart=always
- 启动kibana:(注意ip替换为虚拟机ip)
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://ip:9200 -p 5601:5601 -d kibana:7.6.2
让kibana在http://ip:9200访问es
- 设置开机启动kibana
docker update kibana --restart=always
测试
-
查看elasticsearch版本信息:
http://ip:9200/
返回json数据
-
显示elasticsearch 节点信息
http://ip:9200/_cat/nodes
,
-
访问Kibana:
http://ip:5601/app/kibana
工具推荐
在线版postman(送给不想安装postman的)
注意:带请求体的get请求发不了!
- 网址:https://www.apifox.cn/
- 注册邮箱可随意,无需验证码
- 然后就可以使用:
- 使用web版
- 点示例项目
- 类似postman:
注意:get请求看似可以带body,实际应该并没有带!例如,可以测试查询部分字段,实际还是查了所有
idea插件(送给不想安装kibana的)
1、Cap-elasticsearch-client
注意:聚合函数aggs使用不了,无法返回聚合内容!
- 在插件中搜索
elasticsearch
选第三个·
- 配置前两个就好了
- 选倒数第二个图标 -> Dev Tools -> console
- 简单使用:
2、elasticsearch query-EDQL
- 下图选第二个
- 相对上一个插件(Cap-elasticsearch-client),它可以用聚合函数!
初步检索
_CAT
-
GET
/_cat/nodes
:查看所有节点
如:http://ip:9200/_cat/nodes:
注:*表示集群中的主节点
-
GET
/_cat/health
:查看es健康状况
如: http://ip:9200/_cat/health
注:green表示健康值正常
-
GET
/_cat/master
:查看主节点
如: http://ip:9200/_cat/master
-
GET
/_cat/indices
:查看所有索引 ,等价于mysql数据库的show databases;
如: http://ip:9200/_cat/indices
索引一个文档
-
保存一个数据:保存在哪个索引的哪个类型下,指定用哪个唯一标识。
-
例如:发
PUT
请求http://ip:9200/customer/external/1
携带如下json数据:{ "name":"John Doe" }
-
返回结果:
-
请求解释:在customer索引下的external类型下保存1号数据,数据内容为json内容
-
返回结果解释:
-
这些带有下划线开头的,称为元数据,反映了当前的基本信息。
-
"_index": "customer"
表明该数据在哪个数据库下; -
"_type": "external"
表明该数据在哪个类型下; -
"_id": "1"
表明被保存数据的id; -
"_version": 1
被保存数据的版本 -
“result”: “created” 这里是创建了一条数据,如果重新put一条数据,则该状态会变为updated,并且版本号也会发生变化。
-
-
PUT和POST:
- POST:新增。如果不指定id,会自动生成id。指定id就会修改这个数据,并新增版本号;
- PUT:可以新增也可以修改。PUT必须指定id;由于PUT需要指定id,我们一般用来做修改操作,不指定id会报错。
-
选用POST方式:
- 添加数据的时候,不指定ID,会自动的生成id,并且类型是新增:
- 添加数据的时候,不指定ID,会自动的生成id,并且类型是新增:
查看文档
-
GET
/customer/external/1
http://ip:9200/customer/external/1
-
_seq_no
在请求后面加“?if_seq_no=1&if_primary_term=1
”,当序列号匹配的时候,才进行修改,否则不修改。 -
示例:
- 第一次
put
请求:http://ip:9200/customer/external/1?if_seq_no=6&if_primary_term=1
参数:{ "name":"1" }
- 成功:
- 再发一次:409
- 第一次
更新文档
1、 POST
更新文档:
-
http://ip:9200/customer/external/1/
{ "name":"update" }
再发一次:
两次都是更新 -
http://ip:9200/customer/external/1/_update
,数据:(相对上一次没变){ "doc":{ "name":"update" } }
结果:(result是noop,_sep_no等信息没变)
2、put
更新:
不可带_update
,不会对比原来的数据,同不带_update的post。
带_update (post) | 不带_update (post、put) |
---|---|
数据封装在doc里 | 直接写要更新的数据 |
会对比原来的数据 | 不会对比原来的数据 |
如果要改数据与原数据相同,_seq_no等信息不变 | 无论如何,_seq_no等信息都会改变 |
删除文档或索引
-
DELETE
请求http://ip:9200/customer/external/1
-
DELETE
请求http://ip:9200/customer
注:elasticsearch并没有提供删除类型的操作,只提供了删除索引和文档的操作。 -
实例:删除id=1的数据,删除后继续查询
-
实例:删除整个costomer索引数据,再查,显示索引找不到
bulk批量api
-
语法格式:(每两行是一部分)
{action:{metadata}} {request body } {action:{metadata}} {request body }
-
这里的批量操作,当发生某一条执行发生失败时,其他的数据仍然能够接着执行,也就是说彼此之间是独立的。
-
bulk api以此按顺序执行所有的action(动作)。如果一个单个的动作因任何原因失败,它将继续处理它后面剩余的动作。当bulk api返回时,它将提供每个动作的状态(与发送的顺序相同),所以您可以检查是否一个指定的动作是否失败了。
实例1: 执行多条数据(如果有错误,请在数据最后加一个空行)
POST customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"John Doe"}
或者发送post请求,http://ip:9200/customer/external/_bulk
,将上面的数据删除第一行作为请求体
执行结果(部分)
如果有报错:
请在请求体最后加上一个空行
,例如:
实例2:对于整个索引执行批量操作
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"my first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"my second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"my updated blog post"}}
或者发送post请求,http://ip:9200/_bulk
,将上面的数据删除第一行作为请求体
运行结果:(部分)
样本测试数据
网址:https://github.com/elastic/elasticsearch/blob/7.4/docs/src/test/resources/accounts.json
POST bank/account/_bulk
或:
post方式请求http://ip:9200/bank/account/_bulk
查看索引信息:
GET/_cat/indices
注意
前面主要使用apifox进行学习,由于其无法发送带请求体的get请求,后面学习将采用idea插件进行!
检索
search Api
ES支持两种基本方式检索;
- 通过REST request uri 发送搜索参数 (uri +检索参数);
- 通过REST request body 来发送它们(uri+请求体);
实例:
- uri +检索参数
GET bank/_search?q=*&sort=account_number:asc
- uri+请求体进行检索
GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" },
{"balance":"desc"}
]
}
返回结果:
只返回10条数据
详细的字段信息,参照: https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-search.html
The response also provides the following information about the search request:
took
– how long it took Elasticsearch to run the query, in millisecondstimed_out
– whether or not the search request timed out_shards
– how many shards were searched and a breakdown of how many shards succeeded, failed, or were skipped.max_score
– the score of the most relevant document foundhits.total.value
- how many matching documents were foundhits.sort
- the document’s sort position (when not sorting by relevance score)hits._score
- the document’s relevance score (not applicable when usingmatch_all
)
Query DSL
基本语法格式
Elasticsearch提供了一个可以执行查询的Json风格的DSL。这个被称为Query DSL,该查询语言非常全面。
结构
一个查询语句的典型结构
QUERY_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,...
}
如果针对于某个字段,那么它的结构如下:
{
QUERY_NAME:{
FIELD_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,...
}
}
}
实例:
GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"sort": [
{
"account_number": {
"order": "desc"
}
}
]
}
结果:
返回结果解释
query定义如何查询;
- match_all查询类型【代表查询所有的所有】,es中可以在query中组合非常多的查询类型完成复杂查询;
- 除了query参数之外,我们可也传递其他的参数以改变查询结果,如sort,size;
- from+size限定,完成分页功能;
- sort排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准;
_source(返回部分字段)
GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"sort": [
{
"account_number": {
"order": "desc"
}
}
],
"_source": ["balance","firstname"]
}
查询结果:
match(匹配查询)
- 基本类型(非字符串),精确控制
GET bank/_search
{
"query": {
"match": {
"account_number": "20"
}
}
}
match返回account_number=20的数据。
查询结果:
- 字符串,全文检索
GET bank/_search
{
"query": {
"match": {
"address": "Place"
}
}
}
全文检索,最终会按照评分进行排序,会对检索条件进行分词匹配。
分词实例:
GET bank/_search
{
"query": {
"match": {
"address": "878 Street"
}
}
}
match_phrase (短句匹配,不分词)
将需要匹配的值当成一整个单词(不分词)进行检索
GET bank/_search
{
"query": {
"match_phrase": {
"address": "mill road"
}
}
}
查处address中包含mill_road的所有记录,并给出相关性得分
查看结果:
如果用
match
,则结果为:
multi_math(多字段匹配)
在"address", “firstname"里面查"road lee”,会分词
GET bank/_search
{
"query": {
"multi_match": {
"query": "road lee",
"fields": ["address", "firstname"]
}
}
}
查询结果:
bool(条件查询,must、must_not、should)
复合语句可以合并,任何其他查询语句,包括符合语句。这也就意味着,复合语句之间
可以互相嵌套,可以表达非常复杂的逻辑。
-
must
:必须达到must所列举的所有条件 -
must_not
,必须不匹配must_not所列举的所有条件。 -
should
,应该满足should所列举的条件。(可不满足)应该达到should列举的条件,如果到达会增加相关文档的评分,并不会改变查询的结果。如果query中只有should且只有一种匹配规则,那么should的条件就会被作为默认匹配条件二区改变查询结果。
实例1:
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "M"
}
},
{
"match": {
"address": "road"
}
}
],
"must_not": [
{
"match": {
"age": "38"
}
}
],
"should": [
{
"match": {
"city": "alden"
}
}
]
}
}
}
查询结果:
能够看到相关度越高,得分也越高。
实例2:范围查询:age在20~30之间的
GET /bank/_search
{
"query": {
"bool": {
"must": [
{
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
]
}
}
}
filter(结果过滤,不计算分数,效率比match高)
并不是所有的查询都需要产生分数,特别是哪些仅用于filtering过滤的文档。为了不计算分数
,elasticsearch会自动检查场景并且优化查询的执行。
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "mill"
}
}
],
"filter": {
"range": {
"balance": {
"gte": "10000",
"lte": "30000"
}
}
}
}
}
}
先是查询所有匹配address=mill的文档,然后再根据10000<=balance<=20000进行过滤查询结果
查询结果:
在boolean查询中,must
, should
和must_not
元素都被称为查询子句 。 文档是否符合每个“must”或“should”子句中的标准,决定了文档的“相关性得分”。 得分越高,文档越符合您的搜索条件。 默认情况下,Elasticsearch返回根据这些相关性得分排序的文档。
“must_not”子句中的条件被视为“过滤器”。
它影响文档是否包含在结果中, 但不影响文档的评分方式。 还可以显式地指定任意过滤器来包含或排除基于结构化数据的文档。
filter在使用过程中,并不会计算相关性得分:
GET bank/_search
{
"query": {
"bool": {
"filter": {
"range": {
"balance": {
"gte": "10000",
"lte": "30000"
}
}
}
}
}
}
查询结果:(结果中,_score是0)
term(匹配非文本字段的属性值)
和match一样。匹配某个属性的值。全文检索字段用match,其他非text字段匹配用term
。
官网描述:https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-term-query.html
- Avoid using the
term
query fortext
fields.
避免对文本字段使用“term”查询- By default, Elasticsearch changes the values of
text
fields as part of analysis. This can make finding exact matches fortext
field values difficult.
默认情况下,Elasticsearch作为analysis的一部分更改’ text '字段的值。这使得为“text”字段值寻找精确匹配变得困难。- To search
text
field values, use the match.
要搜索“text”字段值,请使用匹配。
使用term匹配查询
GET bank/_search
{
"query": {
"term": {
"age": 30
}
}
}
查询结果:
如果查文本,可能一条结果也没有!
Aggregation(执行聚合)
简介
聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于SQL Group by和SQL聚合函数。在elasticsearch中,执行搜索返回this(命中结果),并且同时返回聚合结果,把以响应中的所有hits(命中结果)分隔开的能力。这是非常强大且有效的,你可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的API啦避免网络往返。
- aggs:执行聚合。聚合语法如下:
"aggs":{
"aggs_name":{
"AGG_TYPE":{}
}
},
- "aggs_name:这次聚合的名字,方便展示在结果集中
- "AGG_TYPE:聚合的类型(avg,term,terms等)
- 部分截图:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
terms、avg(桶聚合、度量聚合)
- 搜索address中包含mill的所有人的年龄分布以及平均年龄,但不显示这些人的详情
GET /bank/_search
{
"query": {
"match": {
"address":"mill"
}
},
"aggs": {
"myterms": {
"terms": {
"field": "age",
},
},
"myavg": {
"avg": {
"field": "age"
}
}
},
"size": 0,
}
size:0 表示不显示搜索数据
查询结果:
- 按照年龄聚合,并且求这些年龄段的这些人的平均薪资
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageTerms": {
"terms": {"field": "age"},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
},
},
"size": 0,
}
输出结果:
- 查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
},
"ageBalanceAvg": {
"avg": {
"field": "balance"
}
}
}
}
},
"size": 0
}
输出结果:
Mapping(映射关系)
_mapping(查看映射关系)
- 语法:
GET /索引库名/_mapping
- 示例:
GET /bank/_mapping
- 结果
mappings(创建索引并指定类型)
https://www.elastic.co/guide/en/elasticsearch/reference/current/explicit-mapping.html
# 指定类型
PUT /my-index-000001
{
"mappings": {
"properties": {
"age": { "type": "integer" },
"email": { "type": "keyword" },
"name": { "type": "text" }
}
}
}
# 查看类型
GET /my-index-000001/_mapping
- 结果:
- 添加字段映射:
PUT /my-index-000001/_mapping
{
"properties": {
"employee-id": {
"type": "keyword",
"index": false
}
}
}
- index:是否被索引,默认true,如果为false,则可理解为冗余字段
_reindex(更新索引、数据迁移)
- 要想更新类型,需要创建新的索引,并迁移数据,不能直接修改!基本格式如下:
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html
POST _reindex
{
"source": {
"index": "my-index-000001"
},
"dest": {
"index": "my-new-index-000001"
}
}
- source:原数据位置
- dest:迁移到的位置
- 必须先建索引,并指定类型
- 例如,把bank索引下account类型下的所有数据迁移到newbank索引下(新版es的类型被弃用了)
# 创建映射
PUT /newbank
{
"mappings": {
"properties": {
"account_number": {
"type": "long"
},
"address": {
"type": "text",
},
"age": {
"type": "integer"
},
"balance": {
"type": "long"
},
"city": {
"type": "keyword",
},
"email": {
"type": "keyword",
},
"employer": {
"type": "keyword",
},
"firstname": {
"type": "text",
},
"gender": {
"type": "keyword",
},
"lastname": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"state": {
"type": "keyword",
}
}
}
}
# 查看
GET /newbank/_mapping
# 迁移
POST _reindex
{
"source": {
"index": "bank",
"type": "account",
},
"dest": {
"index": "newbank"
}
}
# 查询
GET /newbank/_search
- 最终结果:
类型是_doc:
数据类型
nested
https://www.elastic.co/guide/en/elasticsearch/reference/5.5/nested.html
- 问题:
存储如下数据:
PUT my_index/my_type/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
user是个数组对象,会被扁平化处理:
此时检索在原记录中没有的Alice Smith,依然会有结果(结果略)
GET my_index/_search
{
"query": {
"bool": {
"must": [
{ "match": { "user.first": "Alice" }},
{ "match": { "user.last": "Smith" }}
]
}
}
}
- 用
nested
类型,可不进行扁平化处理,防止查到错误的数据
PUT my_index
{
"mappings": {
"my_type": {
"properties": {
"user": {
"type": "nested"
}
}
}
}
}
- 查询方法:
GET my_index/_search
{
"query": {
"nested": {
"path": "数组名",
"query": {
"bool": {
"must": [
{ "match": { "数组名.属性名1": "属性值1" }},
{ "match": { "数组名.属性名2": "属性值2" }}
]
}
}
}
}
}
- 聚合方法:
{
"query": {
"match_all": {}
},
"aggs": {
"聚合名称1": {
"nested": {
"path": "数组名"
},
"aggs": {
"聚合名称2": {
"terms": {
"field": "数组名.属性名"
},
}
}
}
}
}
分词器
https://www.elastic.co/guide/en/elasticsearch/reference/current/test-analyzer.html
标准分词器
- 使用标准分词器测试分词:
POST _analyze
{
"tokenizer": "standard",
"filter": [ "lowercase", "asciifolding" ],
"text": "Is this déja vu?"
}
结果:
- 对中文不友好:
ik分词器
下载:github地址:https://github.com/medcl/elasticsearch-analysis-ik/
-
我使用的es是7.6.2版本,如果下载别的版本,最终重启es会出现如下报错:
-
7.6.2版本在这里下载:其他版本把7.6.2换成对应版本即可
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2.zip
解压:
-
自定义解压到ik目录下:
-
解压后:
传输到服务器上:
- 利用xftp传输到虚拟机的es插件目录下:
授权:
cd /mydata/elasticsearch/plugins
chmod -R 777 ik/
- 或者直接在xftp中授权:
- 右键文件点击更改权限:
- 改为777
重启容器:
docker restart 容器id
测试一下:
POST _analyze
{
"tokenizer": "ik_smart",
"text": "我是中国人"
}
- ik_smart 和 ik_max_word:
ik_smart 可以看成是把单词以空格分开了,单词个数没多
ik_max_word:会得到更多的词
自定义词典:
输入网址即可
整合spring-boot
在7.15版本说推荐使用Java API:
但是,为了学习,依然选择Java High Level REST Client
:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started.html
配置
- maven
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.2</version>
</dependency>
- yaml(将es的配置写在yaml文件中)
elasticsearch:
localhost: ip #ip改为自己的
port: 9200
scheme: http
- 配置类:
import lombok.Setter;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
@Setter
public class ESConfig {
private String localhost;
private int port;
private String scheme;
public ESConfig() {
}
public static RequestOptions REQUEST_OPTIONS;
static {
REQUEST_OPTIONS=RequestOptions.DEFAULT;
}
@Bean
public RestHighLevelClient restHighLevelClient() {
return new RestHighLevelClient(
RestClient.builder(
new HttpHost(localhost, port, scheme)
));
}
}
- @ConfigurationProperties(prefix = “elasticsearch”):
匹配配置文件中以elasticsearch为前缀的配置
测试存数据:
import com.alibaba.fastjson.JSON;
import com.ljy.gulimallelasticsearch.config.ESConfig;
import lombok.Data;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
class GulimallElasticsearchApplicationTests {
@Autowired
RestHighLevelClient restHighLevelClient;
@Test
void 保存数据() throws IOException {
//构造一个请求
IndexRequest request = new IndexRequest("users");
request.id("1");//数据id
User user = new User();
user.setId(1L);
user.set姓名("张三");
user.set性别("男");
String jsonString = JSON.toJSONString(user);
request.source(jsonString, XContentType.JSON);
//发请求、获取响应
IndexResponse indexResponse = restHighLevelClient.index(request, ESConfig.REQUEST_OPTIONS);
System.out.println(indexResponse);
}
@Data
class User {
Long id;
String 姓名;
String 性别;
}
}
GET /users/_search
获取结果:
Search API
官网:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html
- 测试查询:
import com.ljy.gulimallelasticsearch.config.ESConfig;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.builder.SearchSourceBuilder;
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.concurrent.TimeUnit;
@SpringBootTest
public class SearchApiTest {
@Autowired
RestHighLevelClient client;
@Test
public void 测试查询() throws IOException {
//https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html
//构造查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//query
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
//terms
searchSourceBuilder.aggregation(AggregationBuilders.terms("myterms").field("age"));
//avg
searchSourceBuilder.aggregation(AggregationBuilders.avg("balanceAvg").field("balance"));
searchSourceBuilder.from(10).size(50).timeout(new TimeValue(60, TimeUnit.SECONDS));
System.out.println("请求体:" + searchSourceBuilder);
//构造请求
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("bank");//在/bank索引下查
SearchRequest source = searchRequest.source(searchSourceBuilder);
//发请求、得响应
SearchResponse searchResponse = client.search(source, ESConfig.REQUEST_OPTIONS);
//响应状态等信息
RestStatus status = searchResponse.status();
System.out.println("status:" + status);
// TimeValue took = searchResponse.getTook();
// Boolean terminatedEarly = searchResponse.isTerminatedEarly();
// boolean timedOut = searchResponse.isTimedOut();
// int totalShards = searchResponse.getTotalShards();
// int successfulShards = searchResponse.getSuccessfulShards();
// int failedShards = searchResponse.getFailedShards();
//命中得记录:
SearchHits hits = searchResponse.getHits();
// TotalHits totalHits = hits.getTotalHits();
// // the total number of hits, must be interpreted in the context of totalHits.relation
// long numHits = totalHits.value;
// // whether the number of hits is accurate (EQUAL_TO) or a lower bound of the total (GREATER_THAN_OR_EQUAL_TO)
// TotalHits.Relation relation = totalHits.relation;
// float maxScore = hits.getMaxScore();
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
System.out.println("hit:" + hit);
System.out.println("hit Score:" + hit.getScore() );
System.out.println("Source:" + hit.getSourceAsMap());
}
//聚合函数:
Aggregations aggregations = searchResponse.getAggregations();
Terms terms = aggregations.get("myterms");
for (Terms.Bucket bucket : terms.getBuckets()) {
System.out.println("key:"+bucket.getKeyAsString());
System.out.println("doc count:"+bucket.getDocCount());
}
Avg averageAge = searchResponse.getAggregations().get("balanceAvg");
double avg = averageAge.getValue();
System.out.println("balance avg:" + avg);
}
}
- 结果:(部分)
批量存数据 – 模板
- service:
import com.alibaba.fastjson.JSON;
import com.ljy.common.to.es.SkuEsModel;
import com.ljy.gulimallelasticsearch.config.ESConfig;
import com.ljy.gulimallelasticsearch.constant.ProductConstant;
import com.ljy.gulimallelasticsearch.service.EsSaveService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @program: gulimall
* @description:
* @author: liangjiayy
* @create: 2022-04-27 18:57
**/
@Service
@Slf4j
public class EsSaveServiceImpl implements EsSaveService {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Override
/**
* 批量保存上架商品数据
* @param skuEsModelList:要保存的数据
*/
public boolean productStatusUp(List<SkuEsModel> skuEsModelList) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel skuEsModel : skuEsModelList) {
//索引
IndexRequest request = new IndexRequest(ProductConstant.PRODUCT_INDEX);
//id
request.id(skuEsModel.getSkuId().toString());
//请求体
request.source(JSON.toJSONString(skuEsModel), XContentType.JSON);
bulkRequest.add(request);
}
//批量保存
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, ESConfig.REQUEST_OPTIONS);
List<String> ids = Arrays.stream(bulk.getItems())
.filter(b -> b.getFailure() != null).map(BulkItemResponse::getId)
.collect(Collectors.toList());
if (bulk.hasFailures()) {
log.info("商品上架部分信息保存失败...失败的集合:{}", ids);
return false;
}
return true;
}
}
- 要用到的常量:
public class ProductConstant {
//sku在es中保存的索引
public static final String PRODUCT_INDEX="gulimall_product";
}
附
例子
有如下索引:
查询:
# 条件查询
GET gulimall_product/_search
{
"query": {
"bool": {
#标题搜索手机
"must": [
{
"match": {
"skuTitle":"手机",
},
}],
"filter": [
#catalogId必须为指定值
{
"term": {
"catalogId":"225"
},
},
#brandId必须为指定得几个值
{
"terms": {
"brandId":[
"2",
"3"],
},
},
# 数组的查询
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term":
{
"attrs.attrId":"10"
}
},
{
"terms":
{
"attrs.attrValue": [
"16G",
"32G"]
}
}
]
}
}
}
},
#hasStock必须为指定值
{
"term": {
"hasStock": "true"
}
},
#确定价格范围;gte(great-than-equal):大于等于;gt:大于;
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 123,
}
}
}
],
},
},
#排序,asc升序,desc降序
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
#从0开始,返回五条
"from": 0, "size": 5,
#高亮显示
"highlight":{
"fields": {
"skuTitle":{
}
},
"pre_tags":"<b style='color:red'>",
"post_tags":"</b>"
}
}
聚合:
# 聚合查询
GET gulimall_product/_search
{
"query": {
"match_all": {
}
},
"aggs": {
#根据brandId聚合
"brandId_aggs": {
"terms": {
"field": "brandId",
"size": 10,
},
#上一个结果,再根据brandName、brandImg聚合
"aggs": {
"brandName_aggs": {
"terms": {
"field": "brandName",
},
},
"brandImg_aggs":{
"terms": {
"field": "brandImg",
},
}
}
},
#根据catalogId聚合
"catalogId_aggs":{
"terms": {
"field": "catalogId",
},
#上一个聚合结果再根据catalogName聚合
"aggs": {
"catalogName_aggs": {
"terms": {
"field": "catalogName",
},
}
}
},
#数组的聚合
"attrs_aggs":{
"nested": {
"path": "attrs"
},
#根据attrs.attrId聚合,聚合后,再根据attrName、attrValue聚合
"aggs": {
"attrs_id_aggs": {
"terms": {
"field": "attrs.attrId",
},
"aggs": {
"attrs_name_aggs": {
"terms": {
"field": "attrs.attrName",
},
},
"attrs_value_aggs": {
"terms": {
"field": "attrs.attrValue",
},
}
}
}
}
}
},
#不显示查询的结果,显示有聚合的结果
"size": 0,
}
想去哪里?