- ==ELASTICSEARCH==
- ==docker上安装==
- 下载
- 启动、设置开机自启
- 测试
- ==工具推荐==
- 在线版postman(送给不想安装postman的)
- idea插件(送给不想安装kibana的)
- 1、Cap-elasticsearch-client
- 2、elasticsearch query-EDQL
- ==初步检索==
- _CAT
- 索引一个文档
- 查看文档
- 更新文档
- 删除文档或索引
- bulk批量api
- 样本测试数据
- ==注意==
- ==检索==
- search Api
- Query DSL
- 基本语法格式
- 返回部分字段
- match匹配查询
- match_phrase [短句匹配]
- multi_math【多字段匹配】
- bool用来做复合查询
- Filter【结果过滤】
- term
- Aggregation(执行聚合)
- Mapping
- 查看映射关系
- 创建索引并指定类型:
- 更新索引 -- 数据迁移
- 数据类型
- nested
- 分词器
- 下载ik分词器:
- 自定义词典:
- ==整合spring-boot==
- 配置
- 测试存数据:
- Search API
- 批量存数据 -- 模板
-
中文文档 https://www.kancloud.cn/yiyanan/elasticsearch_7_6/1651637
-
Elasticsearch是Elastic Stack核心的分布式搜索和分析引擎。
-
类比es与MySQL
es | mysql2 |
---|---|
索引 | 数据库 |
类型 | 数据表 |
文档 | 数据 |
- 倒排索引机制
- 分词:将整句拆分为单词,保存每个单词都有哪些记录与之对应
- 检索:将检索拆分为单词,并查询对应记录
- 相关性得分:谁的正确率更高
- 命令
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
注意:带请求体的get请求发不了!
- 网址:https://www.apifox.cn/
- 注册邮箱可随意,无需验证码
- 然后就可以使用:
- 使用web版
- 点示例项目
- 类似postman:
注意:get请求看似可以带body,实际应该并没有带!例如,可以测试查询部分字段,实际还是查了所有
注意:聚合函数aggs使用不了,无法返回聚合内容!
- 在插件中搜索
elasticsearch
选第三个·
- 配置前两个就好了
- 选倒数第二个图标 -> Dev Tools -> console
- 简单使用:
- 下图选第二个
- 相对上一个插件(Cap-elasticsearch-client),它可以用聚合函数!
-
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索引数据,再查,显示索引找不到
-
语法格式:(每两行是一部分)
{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 ApiES支持两种基本方式检索;
- 通过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
Query DSL 基本语法格式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
)
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排序,多字段排序,会在前序字段相等时后续字段内部排序,否则以前序为准;
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的所有记录,并给出相关性得分
查看结果:
multi_math【多字段匹配】如果用
match
,则结果为:
在"address", “firstname"里面查"road lee”,会分词
GET bank/_search
{
"query": {
"multi_match": {
"query": "road lee",
"fields": ["address", "firstname"]
}
}
}
查询结果:
复合语句可以合并,任何其他查询语句,包括符合语句。这也就意味着,复合语句之间
可以互相嵌套,可以表达非常复杂的逻辑。
-
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【结果过滤】
并不是所有的查询都需要产生分数,特别是哪些仅用于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)
和match一样。匹配某个属性的值。全文检索字段用match,其他非text字段匹配用term
。
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”字段值,请使用匹配。
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-term-query.html
使用term匹配查询
GET bank/_search
{
"query": {
"term": {
"age": 30
}
}
}
查询结果:
如果查文本,可能一条结果也没有!
聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于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
- 搜索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 查看映射关系- 语法:
GET /索引库名/_mapping
- 示例:
GET /bank/_mapping
- 结果
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,则可理解为冗余字段
- 要想更新类型,需要创建新的索引,并迁移数据,不能直接修改!基本格式如下:
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:
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"
}
}
}
}
}
分词器
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?"
}
结果:
- 对中文不友好:
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:会得到更多的词
输入网址即可
在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.clientgroupId>
<artifactId>elasticsearch-rest-high-level-clientartifactId>
<version>7.6.2version>
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为前缀的配置
官网:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-document-index.html
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
获取结果:
官网: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";
}
想去哪里?
- ==ELASTICSEARCH==
- ==docker上安装==
- 下载
- 启动、设置开机自启
- 测试
- ==工具推荐==
- 在线版postman(送给不想安装postman的)
- idea插件(送给不想安装kibana的)
- 1、Cap-elasticsearch-client
- 2、elasticsearch query-EDQL
- ==初步检索==
- _CAT
- 索引一个文档
- 查看文档
- 更新文档
- 删除文档或索引
- bulk批量api
- 样本测试数据
- ==注意==
- ==检索==
- search Api
- Query DSL
- 基本语法格式
- 返回部分字段
- match匹配查询
- match_phrase [短句匹配]
- multi_math【多字段匹配】
- bool用来做复合查询
- Filter【结果过滤】
- term
- Aggregation(执行聚合)
- Mapping
- 查看映射关系
- 创建索引并指定类型:
- 更新索引 -- 数据迁移
- 数据类型
- nested
- 分词器
- 下载ik分词器:
- 自定义词典:
- ==整合spring-boot==
- 配置
- 测试存数据:
- Search API
- 批量存数据 -- 模板
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)