官方网站:https://www.elastic.co/cn/elasticsearch/
中文社区:https://elasticsearch.cn/explore/
jvm.options(Elasticsearch基于Lucene的,而Lucene底层是java实现,因此我们需要配置jvm参数):
找到下边的位置
-Xms1g
-Xmx1g
此为默认设置,可以将他设置的大一点或小一点,例如修改为
-Xms512m
-Xmx512m
elasticsearch.yml:
修改数据和日志目录
path.data: /home/leyou/elasticsearch/data # 数据目录位置,没有此文件夹的话就创建一个
path.logs: /home/leyou/elasticsearch/logs # 日志目录位置,没有此文件夹的话就创建一个
修改绑定的ip(默认只允许本机访问,修改为0.0.0.0后则可以远程访问)
network.host: 0.0.0.0 # 绑定到0.0.0.0,允许任何ip来访问
一些其他参数:
属性名 | 说明 |
---|---|
cluster.name | 配置elasticsearch的集群名称,默认是elasticsearch。建议修改成一个有意义的名称。 |
node.name | 节点名,es会默认随机指定一个名字,建议指定一个有意义的名称,方便管理 |
path.conf | 设置配置文件的存储路径,tar或zip包安装默认在es根目录下的config文件夹,rpm安装默认在/etc/ elasticsearch |
path.data | 设置索引数据的存储路径,默认是es根目录下的data文件夹,可以设置多个存储路径,用逗号隔开 |
path.logs | 设置日志文件的存储路径,默认是es根目录下的logs文件夹 |
path.plugins | 设置插件的存放路径,默认是es根目录下的plugins文件夹 |
bootstrap.memory_lock | 设置为true可以锁住ES使用的内存,避免内存进行swap |
network.host | 设置bind_host和publish_host,设置为0.0.0.0允许外网访问 |
http.port | 设置对外服务的http端口,默认为9200。 |
transport.tcp.port | 集群结点之间通信端口 |
discovery.zen.ping.timeout | 设置ES自动发现节点连接超时的时间,默认为3秒,如果网络延迟高可设置大些 |
discovery.zen.minimum_master_nodes | 主结点数量的最少值 ,此值的公式为:(master_eligible_nodes / 2) + 1 ,比如:有3个符合要求的主结点,那么这里要设置为2 |
1.linux内核过低:
在elasticsearch.yml中修改配置:
bootstrap.system_call_filter: false
2.权限不足,Linux中不能用root,切换账户后必须给账户授权
在root账户下执行命令:chmod -R 777 /es目录/
或者
在root账户下打开文件:vim /etc/security/limits.conf,添加:
* soft nofile 65536
* hard nofile 131072
* soft nproc 4096
* hard nproc 4096
3.线程数不够:[1]: max number of threads [1024] for user [leyou] is too low, increase to at least [4096]
打开文件:vim /etc/security/limits.d/90-nproc.conf
修改 * soft nproc 1024 为 * soft nproc 4096
4.进程虚拟内存:[3]: max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
打开文件:vim /etc/sysctl.conf
添加内容:vm.max_map_count=655360
保存退出后执行命令:sysctl -p
elasticsearch.url: "http://安装了es的IP地址:9200"
进入bin目录下找到启动文件点击启动即可,端口为5601:http://127.0.0.1:5601
在https://github.com/medcl/elasticsearch-analysis-ik 下载对应的版本,然后解压缩到es的安装目录中的plugins目录中
在Kibana控制台输入,出现下图即表示安装成功
# 通过ik分词器来分词
POST /_analyze
{
"analyzer": "ik_smart"
,"text": "我是中国人,我热爱我的祖国"
}
Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。
索引(indices)--------------------------------Databases 数据库
类型(type)-----------------------------Table 数据表()
文档(Document)----------------Row 行
字段(Field)-------------------Columns 列
详细说明:
概念 | 说明 |
---|---|
索引库(indices) | indices是index的复数,代表许多的索引, |
类型(type) | es5版本一个索引有多个类型,es6版本一个索引只有一个类型,es7版本取消类型这个概念 |
文档(document) | 存入索引库原始的数据。比如每一条商品信息,就是一个文档 |
字段(field) | 文档中的属性 |
映射配置(mappings) | 字段的数据类型、属性、是否索引、是否存储等特性 |
是不是与Lucene和solr中的概念类似。
另外,在SolrCloud中,有一些集群相关的概念,在Elasticsearch也有类似的:
要注意的是:Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对你的数据进行分片和副本操作,当你向集群添加新数据时,数据也会在新加入的节点中进行平衡。
_cat接口 | 说明 |
---|---|
GET /_cat/nodes | 查看所有节点 |
GET /_cat/health | 查看ES健康状况 |
GET /_cat/master | 查看主节点 |
GET /_cat/indices | 查看所有索引信息 |
/_cat/indices?v 查看所有的索引信息
es 中会默认提供上面的几个索引,表头的含义为:
字段名 | 含义说明 |
---|---|
health | green(集群完整) yellow(单点正常、集群不完整) red(单点不正常) |
status | 是否能使用 |
index | 索引名 |
uuid | 索引统一编号 |
pri | 主节点几个 |
rep | 从节点几个 |
docs.count | 文档数 |
docs.deleted | 文档被删了多少 |
store.size | 整体占空间大小 |
pri.store.size | 主节点占 |
PUT /索引名 即为: http请求方式为put的 IP地址:9200/索引名 {…}为请求体内容 的接口
或者
所有命令均为以这种格式实现,下边介绍详细命令:
//创建/修改索引(不存在为创建,创建为更新)
//参数可选:指定分片及副本,默认分片为3,副本为2。
PUT /索引名
{
"settings": {
"number_of_shards": 3, //分片
"number_of_replicas": 2 //副本
}
}
//查看索引
GET /索引名
//删除索引
DELETE /索引名称
//创建文档
//PUT 提交的id如果不存在就是新增操作,如果存在就是更新操作,id不能为空
//POST 如果不提供id会自动生成一个id,如果id存在就更新,如果id不存在就新增
PUT/POST /索引名称/类型名/编号
{
"name":"gfk"
}
//查询文档
GET /索引/类型/id
//更新文档
//如果更新的数据和文档中的数据是一样的,那么POST方式提交是不会有任何操作的
POST /索引/类型/id/_update
{
"doc":{
"name":"ggg"
}
}
//删除文档
DELETE /索引/类型/id
//批量操作
POST /gfk/system/_bulk
{"index":{"_id":"1"}}
{"name":"dpb"}
{"index":{"_id":"2"}}
{"name":"dpb2"}
//检索方式分俩种(以索引bank为例)
//1. 通过使用REST request URL 发送检索参数(uri+检索参数)
GET bank/_search # 检索bank下的所有信息,包括 type 和 docs
GET bank/_search?query=*&sort=account_number:asc
//2. 通过使用 REST request body 来发送检索参数 (uri+请求体)
GET bank/_search
{
"query":{
"match_all":{}
},
"sort":[
{
"account_number":"desc"
}
]
}
ElasticSearch提供了一个可以执行的JSON风格的DSL(domain-specific language 领域特定语言),这个被称为Query DSL
完整的语法结构
{
QUERY_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,...
}
}
如果是针对某个字段,那么它的结构为
{
QUERY_NAME:{
FIELD_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,...
}
}
}
//match:条件匹配
//如果对应的字段是基本类型(非字符串类型),则是精确匹配
//如果对应的字段是字符串类型,则是全文检索(类似模糊查询),会ik分词
//以索引bank为例,下同
GET bank/_search
{
"query":{
"match":{
"account_number":20
}
}
}
//match_phrase
//将需要匹配的值当成一个整体单词(不分词)进行检索,短语匹配
GET bank/_search
{
"query":{
"match_phrase":{
"address":"mill road"
}
}
}
//multi_match:多字段匹配
GET bank/_search
{
"query":{
"multi_match":{
"query":"mill road",
"fields":["address","state"]
}
}
}
//bool:复合查询
//must(与)、must_not(非)、should(或)
//must:必须满足
//must_not:必须不满足
//should:不满足也会显示,满足的话相关性分数高
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}
//filter结果过滤
//range范围 gte-lte
GET /bank/_search
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}
//term:和match一样,匹配某个属性的值,全文检索字段用match,其他非text字段匹配用term
//term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串
GET bank/_search
{
"query":{
"term":{
"account_number":20
}
}
}
//terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:
GET bank/_search
{
"query":{
"terms":{
"price":[2699.00,2899.00,3899.00]
}
}
}
//includes:来指定想要显示的字段
GET /gfk/_search
{
"_source": {
"includes":["title","price"]
},
"query": {
"term": {
"price": 2699
}
}
}
//excludes:来指定不想要显示的字段
GET /gfk/_search
{
"_source": {
"excludes": ["images"]
},
"query": {
"term": {
"price": 2699
}
}
}
//fuzzy 查询是 term 查询的模糊等价。它允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:
//上面的查询,也能查询到apple手机
//我们可以通过fuzziness来指定允许的编辑距离:
GET /heima/_search
{
"query": {
"fuzzy": {
"title": {
"value":"appla",
"fuzziness":1
}
}
}
}
//sort 可以让我们按照不同的字段进行排序,并且通过order指定排序的方式
GET /gfk/_search
{
"query": {
"match": {
"title": "小米手机"
}
},
"sort": [
{
"price": {
"order": "desc"
}
}
]
}
//假定我们想要结合使用 price和 _score(得分) 进行查询,并且匹配的结果首先按照价格排序,然后按照相关性得分排序:
GET /gfk/_search
{
"query":{
"bool":{
"must":{ "match": { "title": "小米手机" }},
"filter":{
"range":{"price":{"gt":200000,"lt":300000}}
}
}
},
"sort": [
{ "price": { "order": "desc" }},
{ "_score": { "order": "desc" }}
]
}
桶(bucket)
桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个 桶
,例如我们根据国籍对人划分,可以得到 中国桶
、英国桶
,日本桶
……或者我们按照年龄段对人进行划分:010,1020,2030,3040等。
Elasticsearch中提供的划分桶的方式有很多:
bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量
度量(metrics)
分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为 度量
比较常用的一些度量聚合方式:
下面通过详细案例来体会下
//桶:
//我们按照 汽车的颜色color来划分桶
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}
- size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
- aggs:声明这是一个聚合查询,是aggregations的缩写
- popular_colors:给这次聚合起一个名字,任意。
- terms:划分桶的方式,这里是根据词条划分
- field:划分桶的字段
//结果
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 8,
"max_score": 0,
"hits": []
},
"aggregations": {
"popular_colors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "red",
"doc_count": 4
},
{
"key": "blue",
"doc_count": 2
},
{
"key": "green",
"doc_count": 2
}
]
}
}
}
- hits:查询结果为空,因为我们设置了size为0
- aggregations:聚合的结果
- popular_colors:我们定义的聚合名称
- buckets:查找到的桶,每个不同的color字段值都会形成一个桶
- key:这个桶对应的color字段的值
- doc_count:这个桶中的文档数量
//为刚刚的聚合结果添加 求价格平均值的度量
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
},
"aggs":{
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
- aggs:我们在上一个aggs(popular_colors)中添加新的aggs。可见度量也是一个聚合
- avg_price:聚合的名称
- avg:度量的类型,这里是求平均值
- field:度量运算的字段
//统计每种颜色的汽车中,分别属于哪个制造商,按照make字段再进行分桶
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
},
"aggs":{
"avg_price": {
"avg": {
"field": "price"
}
},
"maker":{
"terms":{
"field":"make"
}
}
}
}
}
}
- 原来的color桶和avg计算我们不变
- maker:在嵌套的aggs下新添一个桶,叫做maker
- terms:桶的划分类型依然是词条
- filed:这里根据make字段进行划分
//阶梯分桶
//histogram是把数值类型的字段,按照一定的阶梯大小进行分组。你需要指定一个阶梯值(interval)来划分阶梯大小
//比如你有价格字段,如果你设定interval的值为200,那么阶梯就会是这样的:
//0,200,400,600,...
//参数min_doc_count为1,来约束最少文档数量为1,这样文档数量为0的桶会被过滤
GET /cars/_search
{
"size":0,
"aggs":{
"price":{
"histogram": {
"field": "price",
"interval": 5000,
"min_doc_count": 1 #不加的话会有很多文档数量为0的桶也显示
}
}
}
}
映射是定义文档的过程,文档包含哪些字段,这些字段是否保存,是否索引,是否分词等
//创建映射字段
PUT /索引库名/_mapping/类型名称
{
"properties": {
"字段名": {
"type": "类型",
"index": true,
"store": true,
"analyzer": "分词器"
}
}
}
类型名称:就是前面将的type的概念,类似于数据库中的不同表
字段名:类似于列名,properties下可以指定许多字段。
每个字段可以有很多属性。例如:
- type:类型,可以是text、long、short、date、integer、object等
- index:是否索引,默认为true
- store:是否存储,默认为false
- analyzer:分词器,这里使用ik分词器:ik_max_word或者ik_smart
//新增映射字段
//创建完成索引的映射关系后,又要添加新的字段的映射,第一个就是先删除索引,然后调整后再新建索引映射,还有一个方式就在已有的基础上新增。
PUT /my_index/_mapping
{
"properties":{
"employee-id":{
"type":"keyword"
,"index":false
}
}
}
//对于存在的映射字段,我们不能更新,更新必须创建新的索引进行数据迁移
//数据迁移
POST_reindex [固定写法]
{
"source":{
"index":"twitter"
},
"dest":{
"index":"new_twitter"
}
}
//老的数据有type的情况
{
"source":{
"index":"twitter",
"type":"account"
},
"dest":{
"index":"new_twitter"
}
}
//查看映射
GET /索引库名/_mapping
org.springframework.boot
spring-boot-starter-data-elasticsearch
spring:
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 127.0.0.1:9300
@Document(indexName = "item",type = "docs", shards = 1, replicas = 0)
public class Item {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title; //标题
@Field(type = FieldType.Keyword)
private String category;// 分类
@Field(type = FieldType.Keyword)
private String brand; // 品牌
@Field(type = FieldType.Double)
private Double price; // 价格
@Field(index = false, type = FieldType.Keyword)
private String images; // 图片地址
}
- @Document 作用在类,标记实体类为文档对象,一般有四个属性
- indexName:对应索引库名称
- type:对应在索引库中的类型,后期版本将会废除点type,所以在以后的版本将没有type !!!!!!!!
- shards:分片数量,默认5
- replicas:副本数量,默认1
- @Id 作用在成员变量,标记一个字段作为id主键
- @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
- type:字段类型,取值是枚举:FieldType
- index:是否索引,布尔类型,默认是true
- store:是否存储,布尔类型,默认是false
- analyzer:分词器名称:ik_max_word
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ItcastElasticsearchApplication.class)
public class IndexTest {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Test
public void testCreate(){
// 创建索引,会根据Item类的@Document注解信息来创建
elasticsearchTemplate.createIndex(Item.class);
// 配置映射,会根据Item类中的id、Field等字段来自动完成映射
elasticsearchTemplate.putMapping(Item.class);
}
}
@Test
public void deleteIndex() {
elasticsearchTemplate.deleteIndex("heima");
}
public interface ItemRepository extends ElasticsearchRepository- {
}
@Autowired
private ItemRepository itemRepository;
@Test
public void index() {
Item item = new Item(1L, "小米手机7", " 手机",
"小米", 3499.00, "http://image.leyou.com/13123.jpg");
itemRepository.save(item);
}
@Test
public void indexList() {
List- list = new ArrayList<>();
list.add(new Item(2L, "坚果手机R1", " 手机", "锤子", 3699.00, "http://image.leyou.com/123.jpg"));
list.add(new Item(3L, "华为META10", " 手机", "华为", 4499.00, "http://image.leyou.com/3.jpg"));
// 接收对象集合,实现批量新增
itemRepository.saveAll(list);
}
修改和新增是同一个接口,区分的依据就是id,这一点跟我们在页面发起PUT请求是类似的。
@Test
public void testQuery(){
Optional- optional = this.itemRepository.findById(1l);
System.out.println(optional.get());
}
@Test
public void testFind(){
// 查询全部,并按照价格降序排序
Iterable
- items = this.itemRepository.findAll(Sort.by(Sort.Direction.DESC, "price"));
items.forEach(item-> System.out.println(item));
}
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
当然,方法名称要符合一定的约定:
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And |
findByNameAndPrice |
{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or |
findByNameOrPrice |
{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is |
findByName |
{"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not |
findByNameNot |
{"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between |
findByPriceBetween |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual |
findByPriceLessThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual |
findByPriceGreaterThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before |
findByPriceBefore |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After |
findByPriceAfter |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like |
findByNameLike |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith |
findByNameStartingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith |
findByNameEndingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing |
findByNameContaining |
{"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In |
findByNameIn(Collection |
{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn |
findByNameNotIn(Collection |
{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near |
findByStoreNear |
Not Supported Yet ! |
True |
findByAvailableTrue |
{"bool" : {"must" : {"field" : {"available" : true}}}} |
False |
findByAvailableFalse |
{"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy |
findByAvailableTrueOrderByNameDesc |
{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
@Test
public void testQuery(){
// 词条查询
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米");
// 执行查询
Iterable- items = this.itemRepository.search(queryBuilder);
items.forEach(System.out::println);
}
@Test
public void testNativeQuery(){
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米"));
// 执行搜索,获取结果
Page- items = this.itemRepository.search(queryBuilder.build());
// 打印总条数
System.out.println(items.getTotalElements());
// 打印总页数
System.out.println(items.getTotalPages());
items.forEach(System.out::println);
}
NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
Page
- :默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
- totalElements:总条数
- totalPages:总页数
- Iterator:迭代器,本身实现了Iterator接口,因此可直接迭代得到当前页的数据
- 其它属性
@Test
public void testNativeQuery(){
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));
// 初始化分页参数
int page = 0;
int size = 3;
// 设置分页参数
queryBuilder.withPageable(PageRequest.of(page, size));
// 执行搜索,获取结果
Page- items = this.itemRepository.search(queryBuilder.build());
// 打印总条数
System.out.println(items.getTotalElements());
// 打印总页数
System.out.println(items.getTotalPages());
// 每页大小
System.out.println(items.getSize());
// 当前页
System.out.println(items.getNumber());
items.forEach(System.out::println);
}
@Test
public void testSort(){
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));
// 排序
queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));
// 执行搜索,获取结果
Page- items = this.itemRepository.search(queryBuilder.build());
// 打印总条数
System.out.println(items.getTotalElements());
items.forEach(System.out::println);
}
//按照品牌brand进行分组
@Test
public void testAgg(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 不查询任何结果
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
// 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
queryBuilder.addAggregation(
AggregationBuilders.terms("brands").field("brand"));
// 2、查询,需要把结果强转为AggregatedPage类型
AggregatedPage- aggPage = (AggregatedPage
- ) this.itemRepository.search(queryBuilder.build());
// 3、解析
// 3.1、从结果中取出名为brands的那个聚合,
// 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
// 3.2、获取桶
List
buckets = agg.getBuckets();
// 3.3、遍历
for (StringTerms.Bucket bucket : buckets) {
// 3.4、获取桶中的key,即品牌名称
System.out.println(bucket.getKeyAsString());
// 3.5、获取桶中的文档数量
System.out.println(bucket.getDocCount());
}
}
//嵌套聚合,求平均值
@Test
public void testSubAgg(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 不查询任何结果
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
// 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
queryBuilder.addAggregation(
AggregationBuilders.terms("brands").field("brand")
.subAggregation(AggregationBuilders.avg("priceAvg").field("price")) // 在品牌聚合桶内进行嵌套聚合,求平均值
);
// 2、查询,需要把结果强转为AggregatedPage类型
AggregatedPage- aggPage = (AggregatedPage
- ) this.itemRepository.search(queryBuilder.build());
// 3、解析
// 3.1、从结果中取出名为brands的那个聚合,
// 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
// 3.2、获取桶
List
buckets = agg.getBuckets();
// 3.3、遍历
for (StringTerms.Bucket bucket : buckets) {
// 3.4、获取桶中的key,即品牌名称 3.5、获取桶中的文档数量
System.out.println(bucket.getKeyAsString() + ",共" + bucket.getDocCount() + "台");
// 3.6.获取子聚合结果:
InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("priceAvg");
System.out.println("平均售价:" + avg.getValue());
}
}
关键API:
AggregationBuilders
:聚合的构建工厂类。所有聚合都由这个类来构建,看看他的静态方法:
AggregatedPage
:聚合查询的结果类。它是Page
的子接口:
AggregatedPage
在Page
功能的基础上,拓展了与聚合相关的功能,它其实就是对聚合结果的一种封装,大家可以对照聚合结果的JSON结构来看。
而返回的结果都是Aggregation类型对象,不过根据字段类型不同,又有不同的子类表示
我们看下页面的查询的JSON结果与Java类的对照关系: