ElasticSearch、ES、es使用教程

Elasticsearch

简介

官网学习文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index.html

Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene™ 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单:

  • 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。

  • 实时分析的分布式搜索引擎。

  • 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。

一、基本概念

1、index(索引)

动词,相当于mysql中的insert

名字,相当于mysql中的database

2、Type(类型)

在index中,可以定义一个或多个类型

类似于mysql中的table,每一种类型的数据放在一起

3、Document(文档)

保存在某个索引(index)下,某种类型(Type)的一个数据,文档是JSON格式的,Document就像是mysql中的某个table里面的内容

简单总结下:Index—>数据库;Type—>表;Document—>数据

4、倒排索引机制

分词器

二、安装

1、下载镜像文件

# 使用docker安装
[root@pihao ~] docker pull elasticsearch:7.4.2
# 可视化检索数据
[root@pihao ~] docker pull kibana:7.4.2

2、创建实例

1、ElasticSearch
# 宿主机用于存放配置文件以及数据文件
[root@pihao ~] mkdir -p /mydata/elasticsearch/config
[root@pihao ~] mkdir -p /mydata/elasticsearch/data
[root@pihao ~] chmod -R 777 /mydata/elasticsearch/ # 给data文件授权,不然后面挂载会失败
# 设置任何ip都能访问
[root@pihao ~] echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
# 启动容器 9200:http请求通信端口;9300:分布式集群之间用于通信的端口
[root@pihao ~] 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.4.2

# 查看容器的启动日志 (测试启动成功!)
[root@pihao ~] docker logs xxxx

# 或者使用地址访问  http:主机ip:9200,返回结果如下,说明启动成功
{
  "name" : "a1b49510002c",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "i-8c_jowQjWmurRdQ_E94Q",
  "version" : {
    "number" : "7.4.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "2f90bbf7b93631e52bafb59b3b049cb44ec25e96",
    "build_date" : "2019-10-28T20:40:44.881551Z",
    "build_snapshot" : false,
    "lucene_version" : "8.2.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search" # You Know, for Search
}
2、Kibana
[root@pihao ~] docker run --name kibana -e ELASTICSEARCH_HOSTS=http://112.74.167.52:9200 \
-p 5601:5601 -d kibana:7.4.2

启动成功后通过 http://你主机ip:5601 直接访问kibana的web页面,如出现下图则表示启动成功!

三、初步检索

1、_cat

用于查询es的一些信息

  • GET /_cat/nodes:查看所有节点
  • GET /_cat/health:查看es健康状态
  • GET /_cat/master:查看主节点
  • GET /_cat/indices:查看所有索引 show databases

2、索引一个文档(保存)

保存一个数据,保存在哪个索引的哪个类型下,指定用哪个唯一标识

  • PUT customer/external/1
# 表示在customer这个索引下的external类型下的1号数据
{
	"name":"pihao"
}
# 这是返回响应的内容:带"_"的表示元数据
{
    "_index": "customer", # 哪个index
    "_type": "external",  # 哪个类型
    "_id": "1", # 唯一编号
    "_version": 1, # 版本信息,会叠加类似git
    "result": "created", # created 新建;update 更新 
    "_shards": { # 分片
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}
  • POST /customer/external

post也可用于新增,如果不指定id,就会自动生成id,指定id就会修改这个数据,并新增版本号

post可以新增可以修改,put也可新增修改,但是put必须指定id,如果不指定就会报错

3、查询文档

  • GET /customer/external/1
{
    "_index": "customer",
    "_type": "external",
    "_id": "1", # 记录id
    "_version": 2,
    "_seq_no": 5,  # 并发控制字段,每次更新就会+1,用来做乐观锁
    "_primary_term": 1, # 同上,主分片重新分配,如重启就会变化
    "found": true, # 表示找到了
    "_source": {  # 数据的内容
        "name": "pihao"
    }
}
# 乐观锁修改携带,表示只有在seq_no=0并且primary_term=1的情况下才会更改
PUT /customer/external/1?if_seq_no=0&if_primary_term=1 
### Body 略

4、更新文档

  • POST /customer/external/1/_update

    与之前的差不多,但是这种方式的更新会与原来的值进行对比,_version不会叠加

# /_update这种方式必须要带上 "doc"
{
	"doc":{
		"name":"pihao"
	}
}
# 如果该次更改的值不发生变化,那么"result"的值就是noop,表示no opearation, _version以及_seq_no也不会改变
{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 2,
    "result": "noop", # 没有修改
    "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
    },
    "_seq_no": 5,
    "_primary_term": 1
}

5、删除文档和索引

  • DELETE /customer/external/1 删除某条数据
  • DELETE /customer 删除整个索引

注意:es中暂时没有提供删除类型的操作!

6、bulk批量API

往es中批量导入数据

  • POST cusomter/external/_bulk
# 请求体的格式,这个并不是json的格式,不能在postman中测试
{"操作类型"{"元数据"}}\n # 第一行
{"请求体"}\n # 第二行

#例如
{"index":{"_id":"1"}}
{"name":"pihao"}
{"index":{"_id":"2"}}
{"name":"haoge"}


  • POST _bulk

每个执行的动作都是独立的,如果其中某个单一的动作失败了,不会影响到其他的动作

# _bulk与上面的请求不一致,没有指定index以及type
# 例 复杂示例
{"delete":{"_index":"website","_type":"blog","_id":"123"}} # 删除操作
{"create":{"_index":"website","_type":"blog","_id":"123"}} # 创建
{"title":"my first blog website"}
{"index":{"_index":"website","_type":"blog"}} # 创建
{"title":"my second blog website"}
{"update":{"_index":"website","_type":"blog","_id":"123"}} # 更新
{"doc":{"title":"my update bolg website"}}

在之前安装好的kibana中测试

ElasticSearch、ES、es使用教程_第1张图片

ElasticSearch、ES、es使用教程_第2张图片

7、样本测试数据

数据导入

测试数据下载地址:https://download.elastic.co/demos/kibana/gettingstarted/accounts.zip

拿到数据后在kibana测试导入(ctrl+home回到页面最顶处)

  • POST /bank/acccount/_bulk
{"index":{"_id":"1"}}
{"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"[email protected]","city":"Brogan","state":"IL"}
## 后续数据省略...

ElasticSearch、ES、es使用教程_第3张图片

ElasticSearch、ES、es使用教程_第4张图片

四、进阶检索

1、SearchAPI

es支持两种基本方式检索:

  • 一个是通过使用 REST request URL 发送搜索参数(url+检索参数)

  • 另一个是通过使用 REST reqeust body 来发送检索参数

1)、检索信息
  • 一切检索从 _search开始

GET /bank/_search 检索bank下的所有信息,包括docs和type

GET /bank/_search?q=*&sort=account_number:asc 请求参数方式检索

检索条件在url中

ElasticSearch、ES、es使用教程_第5张图片

查询到1000条,默认只返回10条,类似分页查询

检索条件在请求体中

该种检索方式用的多

GET /bank/_search
{
	"query":{
		"match_all":{}
	},
	"sort":[
		{
			"account_number":{
				"order":"desc"
			}
		}
	]
}

ElasticSearch、ES、es使用教程_第6张图片

2、QueryDSL

Elasticsearch提供了要给可以执行查询的Json风格的DSL(domain-specific-language 领域特定语言)。这个被称为Query DSL。该查询语言非常全面

1)、基本语法
# query_name有:query,form,sort,post_filter等等
query_name:{
	argument:value,
	argument:value...
}
  • 如果针对某个具体字段,查询结构如下:
GET /bank/_search
{
	query_name:{
		field_name:{
			argument:value,
			argument:value...
		}
	}
}
# 分页查询 查询5条数据
GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": "asc"
    }
  ],
  "from": 0,
  "size": 5
}
2)、返回部分字段 _source

类似mysql中的select 字段名 而不是select *

GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": "asc"
    }
  ],
  "from": 0,
  "size": 5,
  "_source":["balance","firstname"] # 指定需要返回的字段
}
3)、match匹配查询
  • 基本类型(非字符串),精确匹配
GET /bank/_search
{
	"query":{
		"match":{
			"account_number":"20"  # 这里也可以是数字20
		}
	}
}
# match 返回account_number=20的
  • 字符串,全文检索
GET /bank/_search
{
	"query":{
		"match":{
			"address":"mill"
		}
	}
}
# 返回address字段中包含"mill"的所有记录,全文检索+模糊匹配
# 全文检索按照评分经行排序,会对检索条件进行分词匹配
4)、match_phrase短语匹配

将需要匹配的值当成一个整体单词(不分词进行检索)

GET /bank/_search
{
	"query":{
		"match_phrase":{
			"address":"mill road"
		}
	}
}
# 返回address字段中包含"mill road"的所有记录,并给出相关性得分
5)、multi_match多字段匹配
GET /bank/_search
{
	"query":{
		"multi_match":{
			"query":"mill",
			"fields":["state","address"]
		}
	}
}
## 返回"state"或者"address"字段中包含有"mill"的所有记录
6)、bool复合查询

在复合查询中,用的比较多的有: must,must_not,should,filter

GET /bank/_search
{
	"query":{
		"bool":{ # 复合查询
			"must":[
				{
					"match":{
						"age":"40"
					}
				}
			],
			"must_not":[
				  {
				  "match":{
					  "gender":"F"
				  }
				}
			],
			"should": [ # 表示应该,有最好,没有也没关系,加分项
			  {
			    "match": {
			      "firstname": "Cameron"
			    }
			  }
			]
		}
	}
}
# 返回age=20并且gender!=F的所有数据
7)、filter 结果过滤

filter的作用和must的作用差不多,但是使用filter不会维护score得分

GET /bank/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "age": {
            "gte": 18,
            "lte": 30
          }
        }
      }
    }
  }
}
# 返回age在18-30范围的数据
GET /bank/_search
{
  "query": {
    "bool": {
      "must": [ # 使用must与上面的效果一样,不同的是会维护score
        {
          "range": {
            "age": {
              "gte": 18,
              "lte": 30
            }
          }
        }
      ]
    }
  }
}
8)、term

和match一样,匹配某个属性的值。全文检索字段用match,其他非text字段匹配用tern

GET /bank/_search
{
	"query":{
		"term":{
			"age":28
		}
	}
}
# 记住:查询非文本字段使用term语句,文本的就是用match

来看一个现象 match_phrase和 .keyword的区别

ElasticSearch、ES、es使用教程_第7张图片

9)、aggregations 执行聚合

聚合提供了从数据中分组和提取数据的能力,最简单的聚合方法大致等于 sql group by 和sql的聚合函数,在ElasticSearch中,你可以执行查询和多个聚合,并且在一次使用中得到各自的返回结果,使用一次简洁和简化的API来避免网络往返

## 搜索address中包含有mill的所有人的年龄分布以及平均年龄
GET /bank/_search
{
	"query":{
		"match":{
			"address":"mill"
		}
	},
	"aggs":{ # 表示聚合
		"ageAgg":{ # 第一个聚合,取个名字
			"terms":{ # 聚合类型 
				"field":"age",
                 "order": { "_count": "asc" } # 按数量排序
				"size":10 # 只看10个
			}
		},
		"ageAvg":{ # 第二个聚合,取个名字
			"avg":{ # 聚合类型
				"field":"age"
			}
		}
	},
	"size":0 # 如果不想看具体的击中数据可以加size限定
}

按照年龄聚合,并且请求这些年龄段的这些人的平均薪资

# 第一步,先查出年龄分布
GET /bank/_search
{
	"query":{
		"match_all":{}
	},
	"aggs":{
		"ageAgg":{
			"terms":{
				"field":"age",
				"size":100
			}
		}
	}
}

# 然后还要查询出里面每个年龄的平均工资,那么就需要进行子聚合

ElasticSearch、ES、es使用教程_第8张图片

# 第二部 继续进行子聚合
GET /bank/_search
{
	"query":{
		"match_all":{}
	},
	"aggs":{
		"ageAgg":{
			"terms":{
				"field":"age",
				"size":100
			},
			"aggs":{ # 在ageAgg聚合中继续聚合
				"ageAvg":{
					"avg":{
						"field":"balance" # 求薪资的平均值
					}
				}
			}
		}
	}
}

ElasticSearch、ES、es使用教程_第9张图片

查出所有年龄分布,并且这些年龄段中M的平均新增和F的平均薪资以及这个年龄段的总体平均薪资

GET /bank/_search
{
	"query":{
		"match_all":{}
	},
	"aggs":{
		"ageAgg":{
			"terms":{ # 先找出所有年龄的分布:数量就是每个年龄的数量 ageCount
				"field":"age",
				"size":100
			},
			"aggs":{
				"ageBalanceAvg":{ # 所有年龄分布的总平均薪资
					"avg":{
						"field":"balance"
					}
				},
				"genderAgg":{
                    "terms":{ # 对每个年龄按性别再次分布:数量就是:ageCount x 2
                        "field":"gender.keyword"
                    },
                    "aggs":{
                    	"balanceAvg":{
                    		"avg":{
                    			"field":"balance"
                    		}
                    	}
                    }
				}
			}
		}
	}
}

ElasticSearch、ES、es使用教程_第10张图片

3、Mapping

1)、字段类型
2)、映射

Maping是用来定义一个文档document,以及它所包含的属性field是如何存储和索引的,比如

  • 哪些字符串属性应该看作全文本属性
  • 哪些属性包含数字,日期或者地理位置
  • 文档中的所有属性是否都能被索引
  • 日期的格式
  • 自定义映射规则来执行动态添加属性

查看mapping信息

GET /bank/_mapping

ElasticSearch、ES、es使用教程_第11张图片

3)、新版本改动
1、创建映射
PUT /my_index # 发送put请求
{
	"mappings":{
		"properties":{
			"age":{"type":"integer"},
			"email":{"type":"keyword"},
			"name":{"type":"text"}
		}
	}
}

### 返回结果
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "my_index"
}
2、添加新的字段映射
PUT /my_index/_mapping
{
	"properties":{
		"employee-id":{ # 新增加一个employee-id的字段,类型为keyword精确匹配
			"type":"keyword",
			"index":false # 不需要被索引,默认的是所有的index都为true
		}
	}
}

## 返回结果
{
  "acknowledged" : true
}
3、更新映射

对于已经存在的映射字段,我们不能更新。这就好比你mysql数据库中已经有数据了,你突然给我说有个字段定义的类型错了。尼玛的揍不死你!所以这样的操作不允许的,必须要创建新的索引进行数据迁移

4、数据迁移

先创建出 newbank 的正确映射。然后使用如下方式经行数据迁移

# 创建新索引并重新指定映射类型
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": "keyword"},
			"state":{"type": "keyword"}
		}
	}
}

### 结果返回
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "newbank"
}

## 导入数据
POST /_reindex
{
	"source":{
		"index":"bank",
		"type":"account" # 老版本导入数据时指定这个type,版本7之后舍弃了type这个概念(测试:可以不用加type)
	},
	"dest":{
		"index":"newbank"
	}
}
## ok

4、分词

一个tokenizer(分词器)接收一个字符流,将之分割为独立的tokens(词元,通常是独立的单词),然后输出tokens流

参考官方文档Analyzers章节 https://www.elastic.co/guide/en/elasticsearch/reference/7.5/analysis-standard-analyzer.html

# 测试
POST _analyze
{
  "analyzer": "standard",
  "text": "张坤你要挺住"
}

## 发现返回一个一个的汉字,这就是默认分词器的效果,所以我们需要下载我们自己的分词器,这样就能识别中文的短语
{
  "tokens" : [
    {
      "token" : "张",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "",
      "position" : 0
    },
    {
      "token" : "坤",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "",
      "position" : 1
    }]
 }
1)、安装ik分词器

注意:不能使用默认的 elasticsearch-plugin install xxx.zip 进行安装

下载地址: https://github.com/medcl/elasticsearch-analysis-ik/releases 下载对应版本

# 将下载好的ik分词器安装包放置在docker的外部挂载文件中
[root@pihao plugins]# pwd
/mydata/elasticsearch/plugins
[root@pihao plugins]# ls
elasticsearch-analysis-ik-7.4.2.zip  # 使用unzip解压出来
[root@pihao plugins]# unzip elasticsearch-analysis-ik-7.4.2.zip -d 指定的目录
# 紧接着查看docker容器中的elasticsearch,看看ik分词器有没有安装好
[root@pihao plugins]# docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED        STATUS             
feb7cdd7b777   kibana:7.4.2          "/usr/local/bin/dumb…"   45 hours ago   Up 45 hours   
a1b49510002c   elasticsearch:7.4.2   "/usr/local/bin/dock…"   46 hours ago   Up 46 hours   

[root@pihao plugins]# docker exec -it a1b49510002c /bin/bash
[root@a1b49510002c elasticsearch]# cd plugins/
[root@a1b49510002c plugins]# ls
ik7.4.2 # 解压安装的ik分词器
[root@a1b49510002c plugins]# cd ..
[root@a1b49510002c elasticsearch]# cd bin/
[root@a1b49510002c bin]# elasticsearch-plugin list 查看已安装的插件
ik7.4.2 # 显示已安装了ik
[root@a1b49510002c bin]# 安装成功

2、测试分词器

测试之前记得重启docker elasticsearch 容器

# analyzer 指定为 ik_smart \ ik_max_word

ElasticSearch、ES、es使用教程_第12张图片

ElasticSearch、ES、es使用教程_第13张图片

3、自定义词库

由于安装解压的词库还不能满足我们日常网络用于所需,所以我们需要导入新的词库

1、启动一个nginx应用

为了演示,我们需要启动一个能访问的应用tomcat或者nginx都行,在这里我就快速启动一个nginx吧,然后添加自己的词库

## 安装nginx 
[root@pihao mydata]#  docker run -p 80:80 --name nginx -d nginx:1.10
# 复制nginx容器中的配置文件
[root@pihao mydata]# ls
elasticsearch  mysql  nginx  redis
[root@pihao mydata]# docker container cp nginx:/etc/nginx .    将容器中nginx的/etc/nginx下的文件复制
[root@pihao mydata]# ls
elasticsearch  mysql  nginx  redis
[root@pihao mydata]# cd nginx/
[root@pihao nginx]# ls
conf.d  fastcgi_params  koi-utf  koi-win  mime.types  modules  nginx.conf  scgi_params  uwsgi_params 
[root@pihao nginx]# 然后停掉nginx,把上述的nginx文件名字改为conf,然后再创建一个nginx的文件夹,把conf移入nginx上面启动一个nginx的容器主要是为了获得里面的配置文件。不然启动的时候没法挂载配置文件

# 正式启动nginx
[root@pihao nginx]# docker run -p 80:80 --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10

[root@pihao nginx]# 启动成功,在nginx/html里面写一个index.html文件  

hello nginx

启动测试成功!

添加自定义的分词

## 在nginx的html配置下新建es文件夹,创建fenci.txt文件再添加如下测试内容

[root@pihao es]# pwd
/mydata/nginx/html/es
[root@pihao es]# ls
fenci.txt
[root@pihao es]# cat fenci.txt
张坤
菜嵩松
刘彦春
朱少醒
谢志宇
葛兰
[root@pihao es]# 测试访问 http://112.74.167.52/es/fenci.txt    测试无误 可以从elasticsearch中引入了!
2、引入nginx的词库

修改/usr/share/elasticsearch/plugins/ik/config中的IKAnalyzer.cfg.xml

[root@pihao config]# pwd
/mydata/elasticsearch/plugins/ik7.4.2/config
[root@pihao config]# ls
extra_main.dic         extra_single_word_full.dic      extra_stopword.dic  main.dic         quantifier.dic  suffix.dic
extra_single_word.dic  extra_single_word_low_freq.dic  IKAnalyzer.cfg.xml  preposition.dic  stopword.dic    surname.dic
[root@pihao config]# vim IKAnalyzer.cfg.xml

ElasticSearch、ES、es使用教程_第14张图片

保存退出后重启docker elasticsearch

测试成功

ElasticSearch、ES、es使用教程_第15张图片

五、SpingBoot整合ElasticSearch

Elasticsearch-Rest-Client 官方提供的RestClient,封装了ES的操作,API层次分明,上手简单

官方地址:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

引入Maven依赖

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.11.1</version>
</dependency>

注入RestHighLevelClient

@Configuration
public class ElasticSearchConfig {

    @Bean
    public RestHighLevelClient esRestClient(){
        RestHighLevelClient esRestClient = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("主机ip", 9200, "http")));
        return esRestClient;
    }
    
    public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN); 需要的话就配置
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }

}

测试保存数据

 /**
     * 测试存储数据
     */
    @Test
    public void indexData() throws IOException {
        IndexRequest indexRequest = new IndexRequest("users");
        indexRequest.id("1");
        User user = new User();
        user.setUserName("pihao");
        user.setAge(20);
        user.setGender("male");
        String jsonString = JSON.toJSONString(user);
        indexRequest.source(jsonString, XContentType.JSON); //直接传入json字符串,指定 application/json

        //同步执行操作
        IndexResponse index = client.index(indexRequest,ElasticSearchConfig.COMMON_OPTIONS);
        //提取有用的响应数据
        System.out.println(index.toString());

    }

测试检索

@Test
    public void searchData() throws IOException {
        //创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        //指定索引
        searchRequest.indices("bank");
        //指定DSL,检索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            //构造检索条件
            sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
            //按照年龄的值分布来聚合
            TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
            sourceBuilder.aggregation(ageAgg);
            //按照薪资聚合
            AvgAggregationBuilder balanceAgg = AggregationBuilders.avg("balanceAgg").field("balance");
            sourceBuilder.aggregation(balanceAgg);
//            sourceBuilder.from();
//            sourceBuilder.size();
        System.out.println("检索条件:"+sourceBuilder.toString());
        searchRequest.source(sourceBuilder);

        //执行检索
        SearchResponse searchResponse = client.search(searchRequest,ElasticSearchConfig.COMMON_OPTIONS);
        //分析结果
        SearchHits hits = searchResponse.getHits(); //外层最大的hits
        SearchHit[] searchHits = hits.getHits(); //真正的记录
        for (SearchHit hit :searchHits) {
            /**
             * "_index" : "bank","_type" : "acccount","_id" : "1","_score" : 1.0,"_source"  都有相应的get方法
             */
            String jsonStr = hit.getSourceAsString(); //将返回的source转为json,最后肯定需要映射成java实体类
            Account account = JSON.parseObject(jsonStr, Account.class);
            System.out.println(account);
        }

        // 获取检索到的聚合信息
        Aggregations aggregations = searchResponse.getAggregations();
        Terms ageAgg1 = aggregations.get("ageAgg"); //这里的ageAgg是term类型的,所以可以转成Terms
        List<? extends Terms.Bucket> buckets = ageAgg1.getBuckets();
        for (Terms.Bucket bucket:buckets) {
            String keyAsString = bucket.getKeyAsString(); //打印年龄种类
            long docCount = bucket.getDocCount(); //打印每种年龄出现的频率
            System.out.println("年龄: "+keyAsString+"=====> 频率: "+docCount);
        }
        Avg balanceAvg = aggregations.get("balanceAgg"); //balanceAgg是avg的类型
        double value = balanceAvg.getValue();
        System.out.println("平均薪资是: "+value);


//        for (Aggregation aggregation:aggregations.asList()) {   //可以直接如上面获取到具体的聚合
//            System.out.println("当前聚合的名字:"+aggregation.getName());
//        }
    }

加油!!!

你可能感兴趣的:(java,elasticsearch,es)