ElasticSearch 全文检索引擎
文章目录
1、ElasticSearch相关概念(术语)
Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。Elasticsearch比传统关系型数据库如下:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
2、Elasticsearch核心概念
1) index 索引(索引库)
一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。
2) 类型 type
在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型
3)字段Field
相当于是数据表的字段,对文档数据根据不同属性进行的分类标识
4) 映射 mapping
mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。
5)文档document
一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。
6)接近实时 NRT
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)
7)集群 cluster
一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群
8)节点 node
一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
9)分片和复制 shards&replicas
一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘
空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问
题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你
可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被
放置到集群中的任何节点上。分片很重要,主要有两方面的原因:
1)允许你水平分割/扩展你的内容容量。
2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。
至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。
在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。
复制之所以重要,有两个主要原因: 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。
3、Elasticsearch 安装
本章节将采用Docker安装,由于windows安装es非常简单,这里不再重复
(1) docker镜像下载
docker pull elasticsearch:5.6.8
(2)安装es容器
docker run -di --name=es -p 9200:9200 -p 9300:9300 elasticsearch:5.6.8
#解决运行时内存不足闪退的问题
docker run -id --name=es -e "ES_JAVA_OPTS=-Xms128m -Xmx128m" -p 9200:9200 -p 9300:9300 elasticsearch:5.6.8
9200端口(Web管理平台端口) 9300(服务默认端口)
浏览器输入地址访问: http://192.168.216.128:9200/
(3)开启远程连接
上面完成安装后,es并不能正常使用,elasticsearch从5版本以后默认不开启远程连接,程序直接连接会报如下错误:
failed to load elasticsearch nodes : org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{5ttLpMhkRjKLkvoY7ltUWg} {192.168.211.132}{192.168.211.132:9300}]
我们需要修改es配置开启远程连接,代码如下:
登录容器
docker exec -it es /bin/bash
查看目录结构 输入: dir
root@dc2e39a21480:/usr/share/elasticsearch# dir
NOTICE.txt README.textile bin config data lib logs modules plugins
进入config目录
cd config
查看文件
root@dc2e39a21480:/usr/share/elasticsearch/config# ls
elasticsearch.yml log4j2.properties scripts
修改elasticsearch.yml文件
root@dc2e39a21480:/usr/share/elasticsearch/config# vi elasticsearch.yml
bash: vi: command not found
vi命令无法识别,因为docker容器里面没有该命令,我们可以安装该编辑器。
安装vim编辑器
apt-get update
apt-get install vim
安装好了后,修改elasticsearch.yml配置,如下图:
vi elasticsearch.yml
修改如下图:
同时添加下面一行代码:
cluster.name: my-elasticsearch
重启docker
docker restart es
(4)系统参数配置
重启后发现重启启动失败了,这时什么原因呢?这与我们刚才修改的配置有关,因为elasticsearch在启动的时候会进行一些检查,比如最多打开的文件的个数以及虚拟内存区域数量等等,如果你放开了此配置,意味着需要打开更多的文件以及虚拟内存,所以我们还需要系统调优
修改vi /etc/security/limits.conf ,追加内容 (nofile是单个进程允许打开的最大文件个数 soft nofile 是软限制 hard nofile是硬限制 )
* soft nofile 65536
* hard nofile 65536
修改vi /etc/sysctl.conf,追加内容 (限制一个进程可以拥有的VMA(虚拟内存区域)的数量 )
vm.max_map_count=655360
执行下面命令 修改内核参数马上生效
sysctl -p
重新启动虚拟机,再次启动容器,发现已经可以启动并远程访问
reboot
(5)跨域配置
修改elasticsearch容器/config下的配置文件:elasticsearch.yml,增加以下三句命令,并重启:
http.cors.enabled: true
http.cors.allow-origin: "*"
network.host: 192.168.216.128
其中: http.cors.enabled: true:此步为允许elasticsearch跨域访问,默认是false。 http.cors.allow-origin: "":表示跨域访问允许的域名地址(表示任意)。
重启
docker restart es
小提示:如果想让容器开启重启,可以执行下面命令
docker update --restart=always 容器名称或者容器id
4、ElasticSearch的客户端操作
实际开发中,主要有三种方式可以作为elasticsearch服务的客户端:
- 第一种,elasticsearch-head插件
- 第二种,使用elasticsearch提供的Restful接口直接访问
- 第三种,使用elasticsearch提供的API进行访问
4.1、 elasticsearch-head
ElasticSearch不同于Solr自带图形化界面,我们可以通过安装ElasticSearch的head插件,完成图形化界面的效果,完成索引数据的查看。安装插件的方式有两种,在线安装和本地安装。本文档采用本地安装方式进行head插件的安装。elasticsearch-5-*以上版本安装head需要安装node和grunt
1)下载head插件:https://github.com/mobz/elasticsearch-head
2)将elasticsearch-head-master压缩包解压到任意目录,但是要和elasticsearch的安装目录区别开
3)下载nodejs:https://nodejs.org/en/download/
安装完毕,可以通过cmd控制台输入:node -v 查看版本号
5)将grunt安装为全局命令 ,Grunt是基于Node.js的项目构建工具
在cmd控制台中输入如下执行命令:
cnpm install -g grunt-cli
执行结果如下图:
6)进入elasticsearch-head-master目录启动head,在命令提示符下输入命令:
npm install
grunt server
7)打开浏览器,输入 http://localhost:9100,看到如下页面:
如果不能成功连接到es服务,需要修改ElasticSearch容器的config目录下的配置文件:
config/elasticsearch.yml,增加以下两句命令:
http.cors.enabled: true
http.cors.allow-origin: "*"
然后重新启动ElasticSearch服务。
4.2、使用Postman工具进行Restful接口访问
ElasticSearch的接口语法
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
其中:
参数 | 解释 |
---|---|
VERB | 适当的 HTTP 方法 或 谓词 : GET 、 POST 、 PUT 、 HEAD 或者 DELETE 。 |
PROTOCOL | http 或者 https (如果你在 Elasticsearch 前面有一个 https 代理) |
HOST | Elasticsearch 集群中任意节点的主机名,或者用 localhost 代表本地机器上的节点。 |
PORT | 运行 Elasticsearch HTTP 服务的端口号,默认是 9200 。 |
PATH | API 的终端路径(例如 _count 将返回集群中文档数量)。Path 可能包含多个组件,例如: _cluster/stats 和 _nodes/stats/jvm |
QUERY_STRING | 任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读) |
BODY | 一个 JSON 格式的请求体 (如果请求需要的话) |
创建索引index和映射mapping
请求url:
PUT 192.168.216.128:9200/blog1
请求体:
{
"mappings": {
"article": {
"properties": {
"id": {
"type": "long",
"store": true,
"index":"not_analyzed"
},
"title": {
"type": "text",
"store": true,
"index":"analyzed",
"analyzer":"standard"
},
"content": {
"type": "text",
"store": true,
"index":"analyzed",
"analyzer":"standard"
}
}
}
}
}
post man 截图:
elasticsearch-head查看:
创建索引后设置Mapping
我们可以在创建索引时设置mapping信息,当然也可以先创建索引然后再设置mapping。
在上一个步骤中不设置maping信息,直接使用put方法创建一个索引,然后设置mapping信息。
请求的url:
POST http://192.168.216.128:9200/blog2/hello/_mapping
请求体:
{
"hello": {
"properties": {
"id":{
"type":"long",
"store":true
},
"title":{
"type":"text",
"store":true,
"index":true,
"analyzer":"standard"
},
"content":{
"type":"text",
"store":true,
"index":true,
"analyzer":"standard"
}
}
}
}
PostMan截图
删除索引index
请求url:
DELETE 192.168.216.128:9200/blog1
postman截图:
elasticsearch-head查看:
创建文档document
请求url:
POST 192.168.216.128:9200/blog1/sun/1
请求体:
{
"id":1,
"title":"ElasticSearch是一个基于Lucene的搜索服务器",
"content":"它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。 Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引 擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。"
}
postman截图:
elasticsearch-head查看:
修改文档document
请求url:
POST 192.168.216.128:9200/blog1/sun/1
请求体:
{
"id":1,
"title":"【修改】ElasticSearch是一个基于Lucene的搜索服务器",
"content":"【修改】它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。 Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引 擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。"
}
postman截图:
elasticsearch-head查看:
删除文档document
请求url:
DELETE 192.168.216.128:9200/blog1/article/1
postman截图:
elasticsearch-head查看:
查询文档-根据id查询
请求url:
GET192.168.216.128:9200/blog2/sun/1
postman截图:
查询文档-querystring查询
请求url:
POST 192.168.216.128:9200/blog2/sun/_search
请求体:
{
"query": {
"query_string": {
"default_field": "title",
"query": "搜索服务器"
}
}
}
postman截图:
注意:
将搜索内容"搜索服务器"修改为"钢索",同样也能搜索到文档,该原因会在下面讲解中得到答案
{
"query": {
"query_string": {
"default_field": "title",
"query": "钢索"
}
}
}
钢索 -> “钢”, “索”,搜索是分成两个词,注意Standard标准分词器,会把汉字每个字一个词存到索引库中的tilte,也是按照Standard进行的分词所以搜索钢索能搜到这个document
查询文档-term查询
请求url:
POST 192.168.216.128:9200/blog2/sun/_search
请求体:
{
"query": {
"term": {
"title": "搜索"
}
}
}
postman截图:
- query_string:搜索之前对搜索的关键词分词
- term:对搜索的关键词不分词
5、IK分词器
5.1、IK分词器安装
(1)安装ik分词器
IK分词器下载地址https://github.com/medcl/elasticsearch-analysis-ik/releases
将ik分词器上传到服务器上,然后解压,并改名字为ik
unzip elasticsearch-analysis-ik-5.6.8.zip
mv elasticsearch ik
将ik目录拷贝到docker容器的plugins目录下
docker cp ./ik es:/usr/share/elasticsearch/plugins
重启容器
(2)IK分词器测试
访问: http://192.168.216.128:9200/_analyze?analyzer=ik_smart&pretty=true&text=我是程序员
访问: http://192.168.216.128:9200/_analyze?analyzer=ik_max_word&pretty=true&text=我 是程序员
- ik_max_word:会将文本做最细粒度的拆分
比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
- ik_smart:会做最粗粒度的拆分
比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂
6、Kibana使用-掌握DSL语句
6.1 Kibana下载安装
(1)镜像下载
docker pull docker.io/kibana:5.6.8
(2)安装kibana容器
执行如下命令,开始安装kibana容器
docker run -it -d -e ELASTICSEARCH_URL=http://192.168.216.128:9200 --name kibana -p 5601:5601 kibana:5.6.8
ELASTICSEARCH_URL=http://192.168.216.128:9200:是指链接的ES地址
restart=always:每次服务都会重启,也就是开启启动
5601:5601:端口号
快捷键
ctrl+i 自动缩进
ctrl+enter 提交请求
down 打开自动补全菜单
enter或tab 选中项自动补全
esc 关闭补全菜单
(3)访问测试
访问 http://192.168.216.128:5601 如下:
6.2、DSL语句使用
6.2.1、索引操作
(1)查询所有索引
GET /_cat/indices?v
(2)删除某个索引
DELETE /索引名
(3)新增索引
PUT /索引名 如PUT /user
(4)创建映射
PUT /user/userinfo/_mapping
{
"properties": {
"name":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"city":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"age":{
"type": "long"
},
"description":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
(5)新增文档数据
PUT /user/userinfo/1 {
"name":"李四",
"age":22,
"city":"深圳",
"description":"李四来自湖北武汉!"
}
我们再增加3条记录:
#新增文档数据 id=2
PUT /user/userinfo/2 {
"name":"王五",
"age":35,
"city":"深圳",
"description":"王五家住在深圳!"
}
#新增文档数据 id=3
PUT /user/userinfo/3 {
"name":"张三",
"age":19,
"city":"深圳",
"description":"在深圳打工,来自湖北武汉"
}
#新增文档数据 id=4
PUT /user/userinfo/4 {
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}
#新增文档数据 id=5
PUT /user/userinfo/5 {
"name":"赵子龙",
"age":77,
"city":"广州",
"description":"赵子龙来自深圳宝安,但是在广州工作!",
"address":"广东省茂名市" }
#新增文档数据 id=6
PUT /user/userinfo/6 {
"name":"赵毅",
"age":55,
"city":"广州",
"description":"赵毅来自广州白云区,从事电子商务8年!"
}
#新增文档数据 id=7
PUT /user/userinfo/7 {
"name":"赵哈哈",
"age":57,
"city":"武汉",
"description":"武汉赵哈哈,在深圳打工已有半年了,月薪7500!"
}
(6)修改数据
a.替换操作
更新数据可以使用之前的增加操作,这种操作会将整个数据替换掉,代码如下:
#更新数据,id=4
PUT /user/userinfo/4 {
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳工作!"
}
使用GET命令查看:
#根据ID查询
GET /user/userinfo/4
b.更新操作
我们先使用下面命令恢复数据:
#恢复文档数据 id=4
PUT /user/userinfo/4 {
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}
使用POST更新某个列的数据
#使用POST更新某个域的数据
POST /user/userinfo/4/_update
{
"doc":{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳工作!"
}
}
使用GET命令查看:
#根据ID查询
GET /user/userinfo/4
(7)删除Document
#删除数据
DELETE user/userinfo/7
6.2.2、数据查询
(1)查询所有数据
#查询所有
GET /user/_search
(2)根据ID查询
#根据ID查询
GET /user/userinfo/2
(3)Sort排序
#搜索排序
GET /user/_search {
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
}
}
(4)分页
#分页实现
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
},
"from": 0,
"size": 2
}
解释:
from:从下N的记录开始查询
size:每页显示条数
6.2.3、查询模式
(1)term查询
term主要用于分词精确匹配,如字符串、数值、日期等(不适合情况:
1.列中除英文字符外有其它值
2.字符串值中有冒号或中文
3.系统自带属性如_version)
如下案例:
#查询-term
GET _search
{
"query":{
"term":{
"city":"武汉"
}
}
}
(2)terms 查询
terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配 。
案例如下:
#查询-terms 允许多个Term
GET _search {
"query":{
"terms":{
"city": [
"武汉",
"广州"
]
}
}
}
(3)match查询
GET _search {
"query": {
"match": {
"city": "广州武汉"
}
}
}
(4)query_string查询
GET _search {
"query": {
"query_string": {
"default_field": "city",
"query": "广州武汉"
}
}
}
(3) range 查询
range过滤允许我们按照指定范围查找一批数据。例如我们查询年龄范围
案例如下:
#-range 范围过滤
#gt表示> gte表示=>
#lt表示< lte表示<=
GET _search
{
"query":{
"range": {
"age": {
"gte": 30,
"lte": 57
}
}
}
}
(4)exists
exists 过滤可以用于查找拥有某个域的数据
案例如下:
#搜索 exists:是指包含某个域的数据检索
GET _search
{
"query": {
"exists":{
"field":"address"
}
}
}
(5) bool 查询
bool 可以用来合并多个条件查询结果的布尔逻辑,它包含一下操作符:
- must : 多个查询条件的完全匹配,相当于 and。
- must_not : 多个查询条件的相反匹配,相当于 not。
- should : 至少有一个查询条件匹配, 相当于 or。
这些参数可以分别继承一个过滤条件或者一个过滤条件的数组:
案例如下:
#过滤搜索 bool
#must : 多个查询条件的完全匹配,相当于 and。
#must_not : 多个查询条件的相反匹配,相当于 not。
#should : 至少有一个查询条件匹配, 相当于 or。
GET _search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "深圳"
}
}
},
{
"range":{
"age":{
"gte":20,
"lte":99
}
}
}
]
}
}
}
(6) match_all 查询
可以查询到所有文档,是没有查询条件下的默认语句。
案例如下:
#查询所有 match_all
GET _search {
"query": {
"match_all": {}
}
}
(7) match 查询
match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。
如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符:
案例如下:
#字符串匹配
GET _search {
"query": {
"match": {
"description": "武汉"
}
}
}
(8)prefix 查询
以什么字符开头的,可以更简单地用 prefix ,例如查询所有以张开始的用户描述
案例如下:
#前缀匹配 prefix
GET _search {
"query": {
"prefix": {
"name": {
"value": "赵"
}
}
}
}
(9)multi_match 查询
multi_match查询允许你做match查询的基础上同时搜索多个字段,在多个字段中同时查一个
案例如下:
#多个域匹配搜索
GET _search {
"query": {
"multi_match": {
"query": "深圳",
"fields": [
"city",
"description"
]
}
}
}
7、ElasticSearch编程操作
创建工程,导入坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sunzihan</groupId>
<artifactId>es-text</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>5.6.8</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.6.8</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.24</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
创建索引index
package com.sunzihan.es;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.Test;
import java.net.InetAddress;
/**
* 2 @Author: sunzihan
* 3 @Date: 2022/3/3 23:02
* 4
*/
public class ElasticsearchTest {
@Test
public void createIndices() throws Exception{
//配置
Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build();
//创建客户端
PreBuiltTransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("192.168.216.128"),9300));
//创建名称为blog2的索引
client.admin().indices().prepareCreate("blog2").get();
//释放资源
client.close();
}
}
创建映射mapping
@Test
public void addMapping() throws Exception{
//配置
Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build();
//创建客户端
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("192.168.216.128"),9300));
/*
* 添加映射
* 格式:
* "mappings": {
* "article": {
* "properties": {
* "id": {
* "type": "long",
* "store": true,
* "index":"not_analyzed"
* },
* "title": {
* "type": "text",
* "store": true,
* "index":"analyzed",
* "analyzer":"standard"
* },
* "content": {
* "type": "text",
* "store": true,
* "index":"analyzed",
* "analyzer":"standard"
* }
* }
* }
* }
*
*/
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder()
.startObject()
.startObject("article")
.startObject("properties")
.startObject("id")
.field("type", "long").field("store", "true")
.endObject()
.startObject("title")
.field("type", "text").field("store", "true").field("analyzer", "ik_smart")
.endObject()
.startObject("content")
.field("type", "text").field("store", "true").field("analyzer", "ik_smart")
.endObject()
.endObject()
.endObject()
.endObject();
//创建映射
PutMappingRequest mapping = Requests.putMappingRequest("blog2").type("article").source(xContentBuilder);
client.admin().indices().putMapping(mapping).get();
//释放资源
client.close();
}
将相同的内容提取,并在测试运行之前调用
@Before
public void setting() throws Exception{
//配置
Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build();
//创建客户端
client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("192.168.216.128"),9300));
}
建立文档document
建立文档(通过XContentBuilder)
@Test
public void testAddDocument() throws Exception {
//创建一个client对象
// 创建一个文档对象
XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("id", 2)
.field("title", "北方入秋速度明显加快 多地降温幅度最多可达10度 22222")
.field("content", "阿联酋一架客机在纽约机场被隔离 10名乘客病倒").endObject();
//把文档对象添加到索引库
client.prepareIndex()
//设置索引名称
.setIndex("blog2")
//设置type
.setType("article")
//设置文档的id,如果不设置的话自动的生成一个id
.setId("2")
//设置文档信息
.setSource(builder)
//执行操作
.get();
//关闭客户端
client.close();
}
建立文档(使用Jackson转换实体)
1)创建Article实体
public class Article {
private Integer id;
private String title;
private String content;
getter/setter...
}
2)添加jackson坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.1</version>
</dependency>
3)代码实现
@Test
public void testAddDocument2() throws Exception {
//创建一个Article对象
Article article = new Article();
//设置对象的属性
article.setId(3l);
article.setTitle("MH370坠毁在柬埔寨密林?中国一公司调十颗卫星去拍摄");
article.setContent("警惕荒唐的死亡游戏!俄15岁少年输掉游戏后用电锯自杀");
//把article对象转换成json格式的字符串。
ObjectMapper objectMapper = new ObjectMapper();
String jsonDocument = objectMapper.writeValueAsString(article);
System.out.println(jsonDocument);
//使用client对象把文档写入索引库
client.prepareIndex("blog2","article", "3") .setSource(jsonDocument, XContentType.JSON) .get();
//关闭客户端
client.close();
}
@Test
public void testAddDocument3() throws Exception {
for (int i = 4; i < 100; i++) {
//创建一个Article对象
Article article = new Article();
//设置对象的属性
article.setId(i);
article.setTitle("女护士路遇昏迷男子跪地抢救:救人是职责更是本能" + i);
article.setContent("江西变质营养餐事件已致24人就医 多名官员被调查" + i);
//把article对象转换成json格式的字符串。
ObjectMapper objectMapper = new ObjectMapper();
String jsonDocument = objectMapper.writeValueAsString(article);
System.out.println(jsonDocument);
//使用client对象把文档写入索引库
client.prepareIndex("blog2","article", i + "") .setSource(jsonDocument, XContentType.JSON) .get(); }/
/关闭客户端
client.close();
}
查询文档操作
termQuery
@Test
public void testQueryByTerm() throws Exception {
//创建客户端
//创建一个QueryBuilder对象
//参数1:要搜索的字段
//参数2:要搜索的关键词
QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "美丽的女护士");
//执行查询
SearchResponse searchResponse = client.prepareSearch("blog2") .setTypes("article") .setQuery(queryBuilder) .get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印文档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取文档的属性
System.out.println("-----------文档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
QueryString
@Test
public void testQueryByQueryString() throws Exception {
//创建客户端
//创建一个QueryBuilder对象
//参数1:要搜索的字段
//参数2:要搜索的关键词
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("美丽的女护士").defaultField("title");
//执行查询
SearchResponse searchResponse = client.prepareSearch("blog2") .setTypes("article") .setQuery(queryBuilder).get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印文档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取文档的属性
System.out.println("-----------文档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
MatchQuery
@Test
public void testQueryByMatchQuery() throws Exception {
//创建客户端
//创建一个QueryBuilder对象
//参数1:要搜索的字段
//参数2:要搜索的关键词
QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "美丽的女护士");
//执行查询
SearchResponse searchResponse = client.prepareSearch("blog2") .setTypes("article") .setQuery(queryBuilder) .get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印文档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取文档的属性
System.out.println("-----------文档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
重构查询方法
private void search(QueryBuilder queryBuilder) throws Exception {
//执行查询
SearchResponse searchResponse = client.prepareSearch("blog2") .setTypes("article") .setQuery(queryBuilder)
//设置分页信息
.setFrom(0)
//每页显示的行数
.setSize(5) .get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印文档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取文档的属性
System.out.println("-----------文档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
使用文档ID查询文档
@Test
public void testSearchById() throws Exception {
//创建一个client对象
//创建一个查询对象
QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds("1", "2");
search(queryBuilder);
}
查询结果高亮操作
高亮显示代码实现
@Test
public void highlightSearchTest() throws Exception {
QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("北京市民", "title", "content");
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title").field("content");
highlightBuilder.preTags("<em>");
highlightBuilder.postTags("</em>");
//执行查询
SearchResponse searchResponse = client.prepareSearch("blog2") .setTypes("article") .setQuery(queryBuilder)
//设置高亮信息
.highlighter(highlightBuilder)
.get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//取文档的属性
System.out.println("-----------文档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
System.out.println("************高亮结果");
Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
// //取title高亮显示的结果
if (highlightFields.size() <=0) continue;
for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
System.out.println(entry.getKey() + ":\t" + Arrays.toString(entry.getValue().getFragments()));
}
}//关闭client
client.close();
}
8、Spring Data ElasticSearch
踩坑:springboot2.2.0版本中 data: elasticsearch:cluster-name: 和cluster-nodes: 被横线划掉
解决:spring-boot-version 换成2.1.5
1)导入Spring Data ElasticSearch坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sunzihan</groupId>
<artifactId>es-demo-spring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es-demo-spring</name>
<description>es-demo-spring</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)启动器配置文件
package com.sunzihan.esdemospring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author sunzihan
*/
@SpringBootApplication
public class EsDemoSpringApplication {
public static void main(String[] args) {
SpringApplication.run(EsDemoSpringApplication.class, args);
}
}
spring:
data:
elasticsearch:
cluster-name: my-elasticsearch
cluster-nodes: 192.168.216.128:9300
3)编写实体Article
package com.sunzihan.esdemospring.domain;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
* 2 @Author: sunzihan
* 3 @Date: 2022/3/4 21:58
* 4
*/
@Document(indexName = "szh_blog" ,type = "article")
public class Article {
@Id
@Field(type = FieldType.Long,store = true)
private long id;
@Field(type = FieldType.Text,store = true,analyzer = "ik_smart")
private String title;
@Field(type = FieldType.Text,store = true,analyzer = "ik_smart")
private String content;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Article{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
'}';
}
}
4)编写Dao
重点:方法命名规则查询的基本语法findBy + 属性 + 关键词 + 连接符,如果有添加Like则查询是分词
关键字 | 命名规则 | 解释 | 示例 |
---|---|---|---|
and | findByField1AndField2 | 根据Field1和Field2获得数据 | findByTitleAndContent |
or | findByField1OrField2 | 根据Field1或Field2获得数据 | findByTitleOrConten |
is | findByField | 根据Field获得数据 | findByTitle |
not | findByFieldNot | 根据Field获得补集数据 | findByTitleNot |
between | findByFieldBetween | 获得指定范围的数据 | findByPriceBetween |
lessThanEqual | findByFieldLessThan | 获得小于等于指定值的数据 | findByPriceLessThan |
package com.sunzihan.esdemospring.domain;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchCrudRepository;
import java.util.List;
/**
* 2 @Author: sunzihan
* 3 @Date: 2022/3/4 22:14
* 4 参数传实体类跟主键
*/
public interface ArticleDao extends ElasticsearchCrudRepository<Article,Long> {
public List<Article> findByTitleLike(String title);
public List<Article> findByTitle(String title);
public List<Article> findByTitleOrContent(String title,String content);
public List<Article> findByTitleOrContent(String title, String content, Pageable pageable);
}
6)创建测试类
package com.sunzihan.esdemospring;
import com.sunzihan.esdemospring.domain.Article;
import com.sunzihan.esdemospring.domain.ArticleDao;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
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.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsDemoSpringApplicationTests {
@Autowired
private ElasticsearchTemplate template;
@Autowired
private ArticleDao articleDao;
@Test
public void createIndex() {
//创建索引
template.createIndex(Article.class);
}
@Test
public void addDocument() throws Exception{
for (int i = 1; i <20 ; i++) {
//创建一个Article实体类对象
Article article = new Article();
article.setId(i);
article.setTitle("关于"+i+"个帅哥的故事");
article.setContent("有"+i+"帅哥");
//将文档写入索引库
articleDao.save(article);
}
}
@Test
public void deleteDocument() throws Exception{
//根据id删除
articleDao.deleteById(11L);
//全部删除
// articleDao.deleteAll();
}
@Test
public void findAll(){
//查询全部
articleDao.findAll().forEach(System.out::println);
}
@Test
public void findById(){
//根据id查询
System.out.println( articleDao.findById(1L));
;
}
@Test
public void findByTitle(){
//没有分词
// articleDao.findByTitle("我是帅哥").forEach(System.out::println);
//分词,加like就有分词
articleDao.findByTitleLike("我是帅哥").forEach(System.out::println);
}
@Test
public void findByTitleOrContent(){
//查询内容和标题
articleDao.findByTitleOrContent("帅哥","帅哥").forEach(System.out::println);
}
@Test
public void findByTitleOrContentPage(){
//查询内容和标题并分页
PageRequest pageRequest = PageRequest.of(1, 5);
articleDao.findByTitleOrContent("帅哥","帅哥",pageRequest).forEach(System.out::println);
}
@Test
public void testNativeSearchQuery() throws Exception{
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.queryStringQuery("帅哥").defaultField("title"))
.withPageable(PageRequest.of(1,5))
.build();
template.queryForList(query,Article.class).forEach(System.out::println);
}
}
聚合查询
实体类
package com.sunzihan.esdemospring.domain;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
* 2 @Author: sunzihan
* 3 @Date: 2022/3/5 0:13
* 4
*/
@Document(indexName = "szh_car",type = "car")
public class Car {
@Id
@Field(type = FieldType.Long,store = true)
private Long id;
@Field(type = FieldType.Text,store = true,analyzer = "ik_smart")
private String name;
@Field(type = FieldType.Text,store = true,analyzer = "ik_smart",fielddata = true)
private String brand;
@Field(type = FieldType.Text,store = true,analyzer = "ik_smart",fielddata = true)
private String color;
@Field(type = FieldType.Text,store = true,analyzer = "ik_smart")
private Double price;
public Car() {
}
public Car(Long id, String name, String color, String brand, Double price) {
this.id = id;
this.name = name;
this.brand = brand;
this.color = color;
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"id=" + id +
", name='" + name + '\'' +
", brand='" + brand + '\'' +
", color='" + color + '\'' +
", price=" + price +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
Dao
package com.sunzihan.esdemospring.dao;
import com.sunzihan.esdemospring.domain.Car;
import org.springframework.data.elasticsearch.repository.ElasticsearchCrudRepository;
/**
* 2 @Author: sunzihan
* 3 @Date: 2022/3/5 0:18
* 4
*/
public interface CarDao extends ElasticsearchCrudRepository<Car,Long> {
}
初始化索引库
@RunWith(SpringRunner.class)
@SpringBootTest
public class CarDaoTest {
@Autowired
private CarDao carDao;
@Test
public void initIndex() {
carDao.save(new Car(1l, "比亚迪A1", "红色", "比亚迪", 50000d));
carDao.save(new Car(2l, "比亚迪A2", "白色", "比亚迪", 70000d));
carDao.save(new Car(3l, "比亚迪A3", "白色", "比亚迪", 80000d));
carDao.save(new Car(4l, "比亚迪A4", "红色", "比亚迪", 60000d));
carDao.save(new Car(5l, "比亚迪A5", "红色", "比亚迪", 90000d));
carDao.save(new Car(6l, "宝马A1", "红色", "宝马", 10000d));
carDao.save(new Car(7l, "宝马A2", "黑色", "宝马", 20000d));
carDao.save(new Car(8l, "宝马A3", "黑色", "宝马", 30000d));
carDao.save(new Car(9l, "宝马A4", "红色", "宝马", 40000d));
carDao.save(new Car(10l, "宝马A5", "红色", "宝马", 50000d));
carDao.save(new Car(11l, "奔驰A1", "红色", "奔驰", 10000d));
carDao.save(new Car(12l, "奔驰A2", "黑色", "奔驰", 20000d));
carDao.save(new Car(13l, "奔驰A3", "黑色", "奔驰", 30000d));
carDao.save(new Car(14l, "奔驰A4", "红色", "奔驰", 40000d));
carDao.save(new Car(15l, "奔驰A5", "红色", "奔驰", 50000d));
}
}
划分桶
GET /car_index/car/_search {
"query": {
"bool": {
"should": [
{
"match_all": {}
}
]
}
},
"aggs": {
"group_by_bland": {
"terms": {
"field": "color"
}
}
}
}
桶内度量
GET /car_index/car/_search
{
"query": {
"bool": {
"should": [
{
"match_all": {}
}
]
}
},
"aggs": {
"group_by_bland": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
Spring Data ElasticSearch代码实现
划分桶
@Test
public void testQuerySelfAggs(){
//查询条件的构建器
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery());
//排除所有的字段查询,
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
//添加聚合条件
queryBuilder.addAggregation(AggregationBuilders.terms("group_by_color").field("c olor"));
//执行查询,把查询结果直接转为聚合page
AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carDao.search(queryBuilder.build());
//从所有的聚合中获取对应名称的聚合
StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color");
//从聚合的结果中获取所有的桶信息
List<StringTerms.Bucket> buckets = agg.getBuckets();
for (StringTerms.Bucket bucket : buckets) {
String brand = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
System.out.println("color = " + brand+" 总数:"+docCount);
}
}
桶内度量
@Test
public void testQuerySelfSubAggs(){
//查询条件的构建器
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery());
//排除所有的字段查询,
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
//添加聚合条件
queryBuilder.addAggregation(AggregationBuilders.terms("group_by_color").field("c olor") .subAggregation(AggregationBuilders.avg("avg_price").field("price")));
//执行查询,把查询结果直接转为聚合page
AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carDao.search(queryBuilder.build());
//从所有的聚合中获取对应名称的聚合
StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color");
//从聚合的结果中获取所有的桶信息
List<StringTerms.Bucket> buckets = agg.getBuckets();
for (StringTerms.Bucket bucket : buckets) {
String brand = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
//取得内部聚合
InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg_price");
System.out.println("brand = " + brand+" 总数:"+docCount+" 平均价 格:"+avg.getValue());
}
}
> Spring Data ElasticSearch代码实现
划分桶
```java
@Test
public void testQuerySelfAggs(){
//查询条件的构建器
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery());
//排除所有的字段查询,
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
//添加聚合条件
queryBuilder.addAggregation(AggregationBuilders.terms("group_by_color").field("c olor"));
//执行查询,把查询结果直接转为聚合page
AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carDao.search(queryBuilder.build());
//从所有的聚合中获取对应名称的聚合
StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color");
//从聚合的结果中获取所有的桶信息
List<StringTerms.Bucket> buckets = agg.getBuckets();
for (StringTerms.Bucket bucket : buckets) {
String brand = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
System.out.println("color = " + brand+" 总数:"+docCount);
}
}
桶内度量
@Test
public void testQuerySelfSubAggs(){
//查询条件的构建器
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery());
//排除所有的字段查询,
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
//添加聚合条件
queryBuilder.addAggregation(AggregationBuilders.terms("group_by_color").field("c olor") .subAggregation(AggregationBuilders.avg("avg_price").field("price")));
//执行查询,把查询结果直接转为聚合page
AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carDao.search(queryBuilder.build());
//从所有的聚合中获取对应名称的聚合
StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color");
//从聚合的结果中获取所有的桶信息
List<StringTerms.Bucket> buckets = agg.getBuckets();
for (StringTerms.Bucket bucket : buckets) {
String brand = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
//取得内部聚合
InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg_price");
System.out.println("brand = " + brand+" 总数:"+docCount+" 平均价 格:"+avg.getValue());
}
}