1. 认识Elasticsearch
Elasticsearch结合kibana,Logstash,Beats,是一整套技术栈,被叫做ELK。被广泛应用于在日志数据分析,实时监控等领域。
Elasticsearch对海量数据的搜索时非常快的,远远快于传统的关系型数据库,为什么呢?这取决于它底层采用了一个特殊的索引:倒排索引
- 文档:每条数据就是一个文档
- 词条:文档按照语义分成的词语
传统的关系型数据库通常会给主键值一个聚簇索引(以MySQL为例),真实数据在聚簇索引的叶子节点上,如果通过主键值来做查询,那速度会非常的快。但是模糊查询就不行了,只能全表扫描。而Elasticsearch不仅给给每一个文档的id设置索引,还会给词条来做一个索引。在一次模糊查询过程中,首先会给模糊的词语进行分词,然后根据这个分词去词条里找,因为词条时做过索引的,所以查询会非常块,这时会拿到词条所属的文档id,再拿这些id去文档中查找,文档的id也是做过索引的,所以也是非常快的,于是只有两次查询,就会得到想要的结果。
那怎么分词呢?需要下载分词的插件,我下载的是IK分词器,下载可以去github下载,且最好和你用的Elasticsearch版本一致。我是用的docker部署的es,并且指定了插件挂载的目录,这里不再赘述。
IK分词有两种模式,一种是ik_smart,另一种是ik_max_word
IK分词器允许我们配置拓展词典来增加自定义的词库:
在ik插件目录下的config目录下有个文件 IKAnalyzer.cfg.xml
在配置自己的扩展词典的时候,指定一个文件,这个文件就得在当前目录下,文件的每一个词占一行。
下面对Elasticsearch进行和MySQL的对比,以达到对基础概念有一定的理解:
|--------|---------------|------------------------------------------------|
| MySQL | Elasticsearch | 说明 |
| Table | Index | 索引(index),就是文档的集合,类似数据库 的表(table) |
| Row | Document | 文档(Document),就是一条条的数据,类似 数据库的行(row),文档都是json格式 |
| Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库的列 |
| Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段型约束。类似数据库的表结构(Schema) |
| SQL | DSL | DSL是Elasticsearch提供的JSON风格的请求语句,用来定义搜索条件 |
[Elasticsearch和MySQL的对比]
2.Elasticsearch基础操作
2.1Mapping映射属性和索引库操作
Mapping是对索引库中文档的约束,常见的Mapping属性包括:
-
type
:字段数据类型,常见的简单类型有:-
字符串:
text
(可分词的文本)、keyword
(精确值,例如:品牌、国家、ip地址) -
数值:
long
、integer
、short
、byte
、double
、float
、 -
布尔:
boolean
-
日期:
date
-
对象:
object
-
-
index
:是否创建索引,默认为true
-
analyzer
:使用哪种分词器 -
properties
:该字段的子字段
**2-1-1:**创建索引库和映射示例:
javascript
PUT /索引库名称
{
"mappings": {
"properties": {
"字段名":{
"type": "text",
"analyzer": "ik_smart"
},
"字段名2":{
"type": "keyword",
"index": "false"
},
"字段名3":{
"properties": {
"子字段": {
"type": "keyword"
}
}
},
// ...略
}
}
}
**2-1-2:**Elasticsearch的增删改查都遵循restful风格,所以查询就是
GET /资源名
2-1-3:删除就是
DELETE /资源名
**2-1-4:**而修改其实只能在原有基础上新增:
javascript
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
创建索引库:PUT /索引库名
查询索引库:GET /索引库名
删除索引库:DELETE /索引库名
修改索引库,添加字段:PUT /索引库名/_mapping
在设计索引库与字段映射的时候,要考虑两个方面,一个是这个字段是否在在页面展示,一个是是否参与搜索。
2.2 文档操作
javascript
# 新增文档
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
}
#查询文档
GET /{索引库名称}/_doc/{id}
#删除文档
DELETE /{索引库名}/_doc/id值
#修改文档有两种方式:
# 1.全量修改,先删除原来的文档,在太添加新的,直接覆盖原来的文档;所以这个修改也可以达到和新增文档一样的效果
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
# 2.局部修改
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
批处理文档操作:
javascript
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
index
代表新增操作
_index
:指定索引库名
_id
指定要操作的文档id
{ "field1" : "value1" }
:则是要新增的文档内容
delete
代表删除操作
_index
:指定索引库名
_id
指定要操作的文档id
update
代表更新操作
_index
:指定索引库名
_id
指定要操作的文档id
{ "doc" : {"field2" : "value2"} }
:要更新的文档字段
3.初始化RestClient
第一步,引依赖
javascript
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
第二步,因为SpringBoot默认的ES版本是7.17.10
,所以我们需要覆盖默认的ES版本,我使用的版本是7.12.1:
javascript
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
第三步:初始化RestHighLevelClient:
javascript
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
3.1:基于JavaClient创建索引库
java
@Test
void testCreateIndex() throws IOException {
// 准备request对象
CreateIndexRequest request = new CreateIndexRequest("items");
// 准备请求参数
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
其中MAPPING_TEMPLATE就是json格式创建索引库的字符串,如下
java
private static final String MAPPING_TEMPLATE = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_smart\"\n" +
" },\n" +
" \"price\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"image\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"category\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"brand\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"sold\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"comment_count\":{\n" +
" \"type\": \"integer\",\n" +
" \"index\": false\n" +
" },\n" +
" \"isAD\":{\n" +
" \"type\": \"boolean\"\n" +
" },\n" +
" \"update_time\":{\n" +
" \"type\": \"date\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
查询索引库是否存在
java
@Test
void testGetIndex() throws IOException {
//获取request对象
GetIndexRequest request = new GetIndexRequest("items");
//查询这个索引库是否存在
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println("exists = " + exists);
}
删除索引库
java
@Test
void testDeleteIndex() throws IOException {
//获取request对象
DeleteIndexRequest request = new DeleteIndexRequest("items");
//删除索引库
client.indices().delete(request, RequestOptions.DEFAULT);
}
3.2 基于JavaClient创建一条文档,也叫全量修改
java
@Test
void testIndexDoc() throws IOException {
//准备数据
Item item = itemService.getById(561178L);
ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
String jsonStr = JSONUtil.toJsonStr(itemDoc);
//获取request对象
IndexRequest request = new IndexRequest("items").id(itemDoc.getId());
//准备请求参数
request.source(jsonStr, XContentType.JSON);
//发送请求
client.index(request, RequestOptions.DEFAULT);
}
获取一条文档
java
@Test
void testGetDoc() throws IOException {
//获取request对象
GetRequest request = new GetRequest("items").id("561178");
//发出请求
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//拿到源信息
String json = response.getSourceAsString();
ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);
System.out.println("itemDoc = " + itemDoc);
}
删除一条文档
java
@Test
void testDeleteDoc() throws IOException {
//获取request对象
DeleteRequest request = new DeleteRequest("items").id("561178");
//发出请求
client.delete(request, RequestOptions.DEFAULT);
}
局部修改
java
@Test
void testUpdateDocument() throws IOException {
// 1.准备Request
UpdateRequest request = new UpdateRequest("items", "561178");
// 2.准备请求参数
request.doc(
"price", 58800,
"commentCount", 1
);
// 3.发送请求
client.update(request, RequestOptions.DEFAULT);
}
4.DSL语句
4-1:叶子查询
-
全文检索查询(Full Text Queries):利用分词器对用户输入搜索条件先分词,得到词条,然后再利用倒排索引搜索词条。例如:
-
match
: #全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索 -
multi_match #与match类似,只不过允许同时查询多个字段
GET /{索引库名}/_search
{
"query": {
"match": {
"字段名": "搜索条件"
}
}
}GET /{索引库名}/_search
{
"query": {
"multi_match": {
"query": "搜索条件",
"fields": ["字段1", "字段2"]
}
}
}
-
精确查询(Term-level queries):不对用户输入搜索条件分词,根据字段内容精确值匹配。但只能查找keyword、数值、日期、boolean类型的字段。例如:
ids
term # 这个是精确查询,基于分词表里的精确查询
range
GET /{索引库名}/_search
{
"query": {
"term": {
"字段名": {
"value": "搜索条件"
}
}
}
}
GET /{索引库名}/_search
{
"query": {
"range": {
"字段名": {
"gte": {最小值},
"lte": {最大值}
}
}
}
}
**地理坐标查询:**用于搜索地理位置,搜索方式很多,例如:
geo_bounding_box
:按矩形搜索
geo_distance
:按点和半径搜索
4-2:复合查询
第一类:基于逻辑运算组合叶子查询,实现组合条件,例如
- bool
第二类:基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:
function_score
dis_max
从elasticsearch5.1开始,采用的相关性打分算法是BM25算法,公式如下:
基于这套公式,就可以判断出某个文档与用户搜索的关键字之间的关联度,还是比较准确的。但是,在实际业务需求中,常常会有竞价排名的功能。不是相关度越高排名越靠前,而是掏的钱多的排名靠前。
要想认为控制相关性算分,就需要利用elasticsearch中的function score 查询了。
基本语法:
function score 查询中包含四部分内容:
原始查询 条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
过滤条件:filter部分,符合该条件的文档才会重新算分
算分函数 :符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
weight:函数结果是常量
field_value_factor:以文档中的某个字段值作为函数结果
random_score:以随机数作为函数结果
script_score:自定义算分函数算法
运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
multiply:相乘
replace:用function score替换query score
其它,例如:sum、avg、max、min
function score的运行流程如下:
1)根据原始条件 查询搜索文档,并且计算相关性算分,称为原始算分(query score)
2)根据过滤条件,过滤文档
3)符合过滤条件 的文档,基于算分函数 运算,得到函数算分(function score)
4)将原始算分 (query score)和函数算分 (function score)基于运算模式做运算,得到最终结果,作为相关性算分。
因此,其中的关键点是:
过滤条件:决定哪些文档的算分被修改
算分函数:决定函数算分的算法
运算模式:决定最终算分结果
示例:给IPhone这个品牌的手机算分提高十倍,分析如下:
过滤条件:品牌必须为IPhone
算分函数:常量weight,值为10
算分模式:相乘multiply
对应代码如下:
# 算发函数查询
GET /hotel/_search
{
"query": {
"function_score": {
"query": { .... }, // 原始查询,可以是任意条件
"functions": [ // 算分函数
{
"filter": { // 满足的条件,品牌必须是Iphone
"term": {
"brand": "Iphone"
}
},
"weight": 10 // 算分权重为2
}
],
"boost_mode": "multipy" // 加权模式,求乘积
}
}
}
bool查询,即布尔查询。就是利用逻辑运算来组合一个或多个查询子句的组合。bool查询支持的逻辑运算有:
must:必须匹配每个子查询,类似"与"
should:选择性匹配子查询,类似"或"
must_not:必须不匹配,不参与算分,类似"非"
filter:必须匹配,不参与算分
bool查询的语法如下:
GET /items/_search
{
"query": {
"bool": {
"must": [
{"match": {"name": "手机"}}
],
"should": [
{"term": {"brand": { "value": "vivo" }}},
{"term": {"brand": { "value": "小米" }}}
],
"must_not": [
{"range": {"price": {"gte": 2500}}}
],
"filter": [
{"range": {"price": {"lte": 1000}}}
]
}
}
}
出于性能考虑,与搜索关键字无关的查询尽量采用must_not或filter逻辑运算,避免参与相关性算分。
排序
elasticsearch默认是根据相关度算分(_score
)来排序,但是也支持自定义方式对搜索结果排序。不过分词字段无法排序,能参与排序字段类型有:keyword
类型、数值类型、地理坐标类型、日期类型等。
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"排序字段": {
"order": "排序方式asc和desc"
}
}
]
}
分页
基础分页:elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了
GET /items/_search
{
"query": {
"match_all": {}
},
"from": 0, // 分页开始的位置,默认为0
"size": 10, // 每页文档数量,默认10
"sort": [
{
"price": {
"order": "desc"
}
}
]
}
深度分页
search after
:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
不过一般没有深度分页的需求
高亮显示
实现高亮的思路就是:
-
用户输入搜索关键字搜索数据
-
服务端根据搜索关键字到elasticsearch搜索,并给搜索结果中的关键字词条添加
html
标签 -
前端提前给约定好的
html
标签添加CSS
样式GET /{索引库名}/_search
{
"query": {
"match": {
"搜索字段": "搜索关键字"
}
},
"highlight": {
"fields": {
"高亮字段名称": {
"pre_tags": "",
"post_tags": ""
}
}
}
}
5.Java对于DSL的API
5.1:叶子查询
matchAll查询
java
@Test
void testMatchAll() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
request.source().query(QueryBuilders.matchAllQuery());
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
private void handleResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
// 1.获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条数据");
// 2.遍历结果数组
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 3.得到_source,也就是原始json文档
String source = hit.getSourceAsString();
// 4.反序列化并打印
ItemDoc item = JSONUtil.toBean(source, ItemDoc.class);
System.out.println(item);
}
}
代码解读:
elasticsearch返回的结果是一个JSON字符串,结构包含:
hits
:命中的结果
total
:总条数,其中的value是具体的总条数值
max_score
:所有结果中得分最高的文档的相关性算分
hits
:搜索结果的文档数组,其中的每个文档都是一个json对象
_source
:文档中的原始数据,也是json对象因此,我们解析响应结果,就是逐层解析JSON字符串,流程如下:
SearchHits
:通过response.getHits()
获取,就是JSON中的最外层的hits
,代表命中的结果
SearchHits #getTotalHits().value
:获取总条数信息
SearchHits #getHits()
:获取SearchHit
数组,也就是文档数组
SearchHit #getSourceAsString()
:获取文档结果中的_source
,也就是原始的json
文档数据
match查询
java
@Test
void testMatch() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
muti_match查询
java
@Test
void testMultiMatch() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
request.source().query(QueryBuilders.multiMatchQuery("脱脂牛奶", "name", "category"));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
range查询
java
@Test
void testRange() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
request.source().query(QueryBuilders.rangeQuery("price").gte(10000).lte(30000));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
term查询
java
@Test
void testTerm() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
request.source().query(QueryBuilders.termQuery("brand", "华为"));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
5.2:复合查询
java
@Test
void testBool() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
// 2.1.准备bool查询
BoolQueryBuilder bool = QueryBuilders.boolQuery();
// 2.2.关键字搜索
bool.must(QueryBuilders.matchQuery("name", "脱脂牛奶"));
// 2.3.品牌过滤
bool.filter(QueryBuilders.termQuery("brand", "德亚"));
// 2.4.价格过滤
bool.filter(QueryBuilders.rangeQuery("price").lte(30000));
request.source().query(bool);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
5.3:分页和排序
java
@Test
void testPageAndSort() throws IOException {
int pageNo = 1, pageSize = 5;
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
// 2.1.搜索条件参数
request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
// 2.2.排序参数
request.source().sort("price", SortOrder.ASC);
// 2.3.分页参数
request.source().from((pageNo - 1) * pageSize).size(pageSize);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
5.4:高亮
java
@Test
void testHighlight() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.组织请求参数
// 2.1.query条件
request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));
// 2.2.高亮条件
request.source().highlighter(
SearchSourceBuilder.highlight()
.field("name")
.preTags("<em>")
.postTags("</em>")
);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
handleResponse(response);
}
代码解读:
第
3、4
步:从结果中获取_source
。hit.getSourceAsString()
,这部分是非高亮结果,json字符串。还需要反序列为ItemDoc
对象第
5
步:获取高亮结果。hit.getHighlightFields()
,返回值是一个Map
,key是高亮字段名称,值是HighlightField
对象,代表高亮值第
5.1
步:从Map
中根据高亮字段名称,获取高亮字段值对象HighlightField
第
5.2
步:从HighlightField
中获取Fragments
,并且转为字符串。这部分就是真正的高亮字符串了最后:用高亮的结果替换
ItemDoc
中的非高亮结果
java
//完整代码
private void handleResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
// 1.获取总条数
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条数据");
// 2.遍历结果数组
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 3.得到_source,也就是原始json文档
String source = hit.getSourceAsString();
// 4.反序列化
ItemDoc item = JSONUtil.toBean(source, ItemDoc.class);
// 5.获取高亮结果
Map<String, HighlightField> hfs = hit.getHighlightFields();
if (CollUtils.isNotEmpty(hfs)) {
// 5.1.有高亮结果,获取name的高亮结果
HighlightField hf = hfs.get("name");
if (hf != null) {
// 5.2.获取第一个高亮结果片段,就是商品名称的高亮值
String hfName = hf.getFragments()[0].string();
item.setName(hfName);
}
}
System.out.println(item);
}
}
6.数据聚合
聚合(aggregations
)可以让我们极其方便的实现对数据的统计、分析、运算。
聚合常见的有三类:
桶(
Bucket
**)**聚合:用来对文档做分组(类似于MySQL的group by)
TermAggregation
:按照文档字段值分组,例如按照品牌值分组、按照国家分组
Date Histogram
:按照日期阶梯分组,例如一周为一组,或者一月为一组度量(
Metric
**)**聚合:用以计算一些值,比如:最大值、最小值、平均值等
Avg
:求平均值
Max
:求最大值
Min
:求最小值
Stats
:同时求max
、min
、avg
、sum
等管道(
pipeline
**)**聚合:其它聚合的结果为基础做进一步运算
注意: 参加聚合的字段必须是keyword、日期、数值、布尔类型
java
GET /items/_search
{
"size": 0,
"aggs": {
"category_agg": {
"terms": {
"field": "category",
"size": 20
}
}
}
}
语法说明:
size
:设置size
为0,就是每页查0条,则结果中就不包含文档,只包含聚合
aggs
:定义聚合
category_agg
:聚合名称,自定义,但不能重复
terms
:聚合的类型,按分类聚合,所以用term
field
:参与聚合的字段名称
size
:希望返回的聚合结果的最大数量
带条件的聚合
java
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手机"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20
}
}
}
}
Metric聚合
java
GET /items/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "手机"
}
},
{
"range": {
"price": {
"gte": 300000
}
}
}
]
}
},
"size": 0,
"aggs": {
"brand_agg": {
"terms": {
"field": "brand",
"size": 20
},
"aggs": {
"stats_meric": {
"stats": {
"field": "price"
}
}
}
}
}
}
可以看到我们在
brand_agg
聚合的内部,我们新加了一个aggs
参数。这个聚合就是brand_agg
的子聚合,会对brand_agg
形成的每个桶中的文档分别统计。
stats_meric
:聚合名称
stats
:聚合类型,stats是metric
聚合的一种
field
:聚合字段,这里选择price
,统计价格
Java客户端代码写数据聚合
聚合结果与搜索文档同一级别,因此需要单独获取和解析。具体解析语法如下:
完整代码如下
java
@Test
void testAgg() throws IOException {
// 1.创建Request
SearchRequest request = new SearchRequest("items");
// 2.准备请求参数
BoolQueryBuilder bool = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("category", "手机"))
.filter(QueryBuilders.rangeQuery("price").gte(300000));
request.source().query(bool).size(0);
// 3.聚合参数
request.source().aggregation(
AggregationBuilders.terms("brand_agg").field("brand").size(5)
);
// 4.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 5.解析聚合结果
Aggregations aggregations = response.getAggregations();
// 5.1.获取品牌聚合
Terms brandTerms = aggregations.get("brand_agg");
// 5.2.获取聚合中的桶
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
// 5.3.遍历桶内数据
for (Terms.Bucket bucket : buckets) {
// 5.4.获取桶内key
String brand = bucket.getKeyAsString();
System.out.print("brand = " + brand);
long count = bucket.getDocCount();
System.out.println("; count = " + count);
}
}