我们从表中做搜索功能可以通过 SQL 语句中的 like
语法实现查询,但是会存在几个问题:
- 针对单表的全表扫描效率低
- 关系数据库中提供的查询功能弱
- 关键字匹配比较随意
为解决 搜索功能,我们引入 Elasticsearch 技术
ES 介绍
Elasticsearch 是一个基于 Lucene 的 分布式、高扩展、高实时 的基于RESTful 风格 API 的 搜索与数据分析引擎
基本概念:
-
字段 (Field): 一个字段表示一个属性,类比于数据库表中的属性,数据库中的一行数据通常是多个属性值组成
-
文档 (document):在ES中数据的存储和关系数据库不同,所有的数据都是以文档的JSON document的形式存在,document是ES中 索引 和 搜索 的最小的数据单位,类比于数据库中的一行数据,通常有多个字段值组成
-
映射 (mapping): mapping定义了document中每个字段的类型、字段所使用的分词器等。相当于关系型数据库中的表结构。
-
索引(index): ElasticSearch存储数据的地方,可以理解成关系型数据库中的数据库概念,存放一类相同或者类似的document,比如一个员工索引,商品索引。
-
类型 (Type):逻辑上的数据分类,一种type就像一张表。如用户表、角色表等。在Elasticsearch6.X默认 type为_doc,es 7.x中删除了type的概念
ES 可以实现高效的全文检索功能,其中一个很重要的原因是 ES 使用了 倒排索引
这里要注意的一点是,倒排索引和文档的存储本身没有关系,只是为了快速的全文检索,针对document的一个或者多个字段,所创建的索引
正/倒排索引
正排索引:文档id 对应 分词
倒排索引:分词 对应 文档id
为什么ES效率高
-
分布式架构:Elasticsearch本身设计为分布式系统,能够轻松扩展以支持大规模数据,并通过集群管理多个节点,实现高可用性和负载均衡。
-
基于Lucene:Elasticsearch建立在Apache Lucene之上,这个底层库以高效的全文搜索能力著称。Elasticsearch在Lucene的基础上进行了优化,使查询速度更快。
-
实时搜索:Elasticsearch支持近实时的搜索功能,通过底层的倒排索引结构,能够在数据写入后立即进行搜索,提供高效的查询性能。
-
强大的查询DSL:它提供了丰富的查询语法,可以进行复杂的布尔查询、范围查询、聚合查询、全文本检索等。
-
高效的索引机制:Elasticsearch通过分片(sharding)和副本(replication)机制管理索引,不仅提升了查询性能,还保证了数据的冗余和高可用性。
-
缓存机制:内建的缓存功能会缓存常用查询结果和索引数据,加速后续查询。
ES 操作
ES 虽然是基于 Java 语言开发的,但它的使用却不仅仅局限于 Java 语言,因为 ES 对外提供了 Restful 风格的 API,我们可以通过这些 Restful 风格的 API 向 ES 发送请求,从而操作 ES
操作索引
- 创建索引 PUT请求
PUT http://ip:端口/索引名称
- 查询索引 GET请求
GET http://ip:端口/索引名称 # 查询单个索引信息
GET http://ip:端口/索引名称1,索引名称2... # 查询多个索引信息
GET http://ip:端口/_all # 查询所有索引信息
- 删除索引 DELETE请求
DELETE http://ip:端口/索引名称
- 关闭、打开索引 POST请求
POST http://ip:端口/索引名称/_close
POST http://ip:端口/索引名称/_open
数据类型
- 简单数据类型
- 字符串
text:会分词,不支持聚合
keyword:不会分词,将全部内容作为一个词条,支持聚合
- 数值:long, integer,short, byte,double,float,half_float,scaled_float
half_float:半精度浮点数
scaled_float:可缩放的浮点数(整数)
- “type”: “scaled_float”
- “scaling_factor”: 100 (每个字段都需要绑定一个缩放因子)
-
布尔:boolean
-
二进制:binary
-
范围类型
integer_range, float_range, long_range, double_range, date_range
- 日期:date
字符串中,text 和 keyword 是由数据类型决定的分词与不分词,是指在保存文档数据,添加倒排索引项的时候发生的
- 复杂数据类型
-
数组:
[ ]
没有专门的数组类型,ES 会自动处理数组类型数据 -
对象:
{ }
Object:object (for single JSON objects 单个JSON对象)
基本操作
创建索引
PUT student
添加映射
#添加映射
PUT student/_mapping
{
"properties":{
"name":{
"type":"text"
},
"age":{
"type":"integer"
}
}
}
查询映射
# 查询映射
GET student/_mapping
# 创建并查询映射
PUT student
{
"mappings": {
"properties": {
"id":{
"type": "integer"
},
"name": {
"type": "text"
}
}
}
}
删除索引
DELETE student
修改映射
# 添加索引
PUT student/_mapping
{
"properties":{
"age":{
"type": "integer"
}
}
}
# 不能修改已有字段,需要删除重新添加
添加文档
# 指定唯一标识
POST teacher/_doc/cskaoyan
{
"name":"张三",
"age":18,
"address":"北京"
}
# 不指定唯一标识,会自动生成
POST student/_doc/
{
"id":2,
"name":"lisi",
"age":19
}
查询文档
# 查询一条文档
GET student/_doc/cskaoyan
# 查询所有文档
GET /teacher/_search
修改文档
# 全量修改,会完全覆盖原本的值
POST student/_doc/cskaoyan001
{
"id":100,
"name":"kongling"
}
# 增量修改
POST teacher/_update/cskaoyan
{
"doc": {
"name": "李四"
}
}
删除文档
# 删除指定id文档
DELETE student/_doc/cskaoyan
IK 分词器
对于中文内容的分词,我们通常会采用对中文支持比较好的 IK分词器
IKAnalyzer 是一个开源的,基于 java 语言开发的轻量级的中文分词工具包,基于 Maven 构建,具有 60万字/秒 的高速处理能力,并且支持用户词典扩展定义
IK分词器使用
IK分词器有两种分词模式:ik_max_word 和 ik_smart 模式
ik_max_word
会将文本做 最细粒度 的拆分
- 比如会将 “好好学习, 天天向上” 拆分为
好好学习,好好学、好好、好学、学习、天天向上、天天,向上
#方式一ik_max_word
GET _analyze
{
"analyzer": "ik_max_word",
"text": "好好学习, 天天向上"
}
- ik_max_word 分词器执行如下:
{
"tokens" : [
{
"token" : "好好学习",
"start_offset" : 0,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "好好学",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "好好",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "好学",
"start_offset" : 1,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 3
},
{
"token" : "学习",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "天天向上",
"start_offset" : 6,
"end_offset" : 10,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "天天",
"start_offset" : 6,
"end_offset" : 8,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "向上",
"start_offset" : 8,
"end_offset" : 10,
"type" : "CN_WORD",
"position" : 7
}
]
}
ik_smart
会做 最粗粒度 的拆分
- 比如会将“好好学习, 天天向上”拆分为好好学习、天天向上。
#方式二 ik_smart
GET _analyze
{
"analyzer": "ik_smart",
"text": "好好学习, 天天向上"
}
- ik_smart分词器 执行如下:
{
"tokens" : [
{
"token" : "好好学习",
"start_offset" : 0,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "天天向上",
"start_offset" : 5,
"end_offset" : 9,
"type" : "CN_WORD",
"position" : 1
}
]
}
指定分词器查询
文档的查询可以分为两种查询方式:
- 词条查询(term):词条查询 不会 分析查询条件,只有当词条和查询字符串 完全匹配时才匹配搜索
- 全文查询(match):全文查询 会 分析查询条件,先将查询条件进行分词,然后查询,求并集
准备工作如下:
# 创建索引,添加映射,并指定分词器为ik分词器
PUT member
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"address": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
# 添加文档
POST member/_doc/1
{
"name":"zs",
"age":18,
"address":"武汉市洪山区"
}
POST member/_doc/2
{
"name":"lisi",
"age":18,
"address":"武汉市"
}
POST /member/_doc/3
{
"name":"ww",
"age":18,
"address":"武汉黄陂"
}
# 查询映射
GET member/_search
# 查看分词效果
GET _analyze
{
"analyzer": "ik_max_word",
"text": "武汉市洪山区"
}
下面分别采用两种方式查询,分词都是用IK分词器:
-
词条查询:term
查询member中匹配到"武汉"两字的词条
GET member/_search
{
"query": {
"term": {
"address": {
"value": "武汉"
}
}
}
}
-
全文查询:match
全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集
GET member/_search
{
"query": {
"match": {
"address":"武汉黄陂"
}
}
}
ES 批量操作
通过批量操作,我们可以一次向 Elastic Search 发送多条 增删改 操作。从而达到一定程度上节省带宽的操作
语法
# 定义索引,及其映射
PUT teacher
{
"mappings": {
"properties": {
"id": {"type": "long"},
"name": {"type": "text"},
"age": {"type": "integer"}
}
}
}
#批量操作
#删除1号 新增2号 更新2号
POST _bulk
{"create": {"_index": "teacher", "_id": "2"}}
{"name": "南风", "age": 18}
{"update": {"_index": "teacher", "_id": 2}}
{"doc": {"name":"景天", "age": 19}}
{"delete": {"_index":"teacher", "_id": "1"}}
批量从数据库导入数据
- 创建索引和映射
PUT product
{
"mappings": {
"properties": {
"id":{"type": "long"},
"image": {"type": "keyword"},
"status": {"type": "integer"},
"sellPoint": {
"type": "text",
"analyzer": "ik_max_word"
},
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"num":{"type": "integer"},
"tmName": {"type": "keyword"},
"cid": {"type": "long"},
"price": {"type": "double"},
"limitNum": {"type": "integer"},
"created": {"type": "date"},
"updated": {"type": "date"}
}
}
}
- 代码实现从数据库批量将数据导入Elastic Search
// 从Mysql 批量导入 elasticSearch
@Test
public void test3() throws IOException {
//1.查询所有数据,mysql
List<Item> products = productMapper.findAll();
//2.bulk导入
BulkRequest bulkRequest=new BulkRequest();
//2.1 循环goodsList,创建IndexRequest添加数据
for (Item item : items) {
//将product对象转换为json字符串
String data = JSON.toJSONString(item);
IndexRequest indexRequest=new IndexRequest("product").source(data,XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(response.status());
}
ES 高级查询
match all 全文查询
- 查询所有文档
- 可使用
"from"
"size"
字段进行分页
# from:指定从查询结果集中的第几条文档开始返回
# size:返回文档的总条数
GET product/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 23
}
@Test
public void matchAll() throws IOException {
// 构建查询请求对象,指定查询的索引名称
SearchRequest searchRequest=new SearchRequest("product");
// 创建查询条件构建器SearchSourceBuilder
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
// 查询条件
QueryBuilder queryBuilder= QueryBuilders.matchAllQuery();
// 指定查询条件
sourceBuilder.query(queryBuilder);
// 添加查询条件构建器 SearchSourceBuilder
searchRequest.source(sourceBuilder);
// 添加分页信息 不设置 默认10条
sourceBuilder.from(0);
sourceBuilder.size(100);
// 查询,获取查询结果
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取命中对象 SearchHits
SearchHits hits = searchResponse.getHits();
// 获取总记录数
Long total= hits.getTotalHits().value;
System.out.println("总数:"+total);
// 获取Hits数据 数组
SearchHit[] result = hits.getHits();
// 获取json字符串格式的数据
List<Product> products = new ArrayList<>();
for (SearchHit searchHit : result) {
String sourceAsString = searchHit.getSourceAsString();
//转为java对象
Product product = JSON.parseObject(sourceAsString, Product.class);
products.add(product);
}
System.out.println(items);
}
term 不分词查询
term查询 和 字段类型有关系,首先回顾一下ElasticSearch两个数据类型
ElasticSearch 两个数据类型:
- text:会分词,不支持聚合
- keyword:不会分词,将全部内容作为一个词条,支持聚合
term查询:不会对查询条件进行分词
- 但是注意,term查询,查询 text类型 字段时,文档中类型为 text类型 的字段本身仍然会分词
GET product/_search
{
"query": {
"term": {
"title": {
"value": "手机充电器"
}
}
}
}
@Test
public void testTerm() throws IOException {
// 构建查询请求对象,指定查询的索引名称
SearchRequest searchRequest = new SearchRequest("product");
// 创建查询条件构建器SearchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "手机充电器");
// 指定查询条件
searchSourceBuilder.query(termQueryBuilder);
// 添加查询条件构建器 SearchSourceBuilder
searchRequest.source(searchSourceBuilder);
// 查询,获取查询结果
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取命中对象 SearchHits
SearchHits hits = search.getHits();
// 获取总记录数
long value = hits.getTotalHits().value;
ArrayList<Product> items = new ArrayList<>();
SearchHit[] h = hits.getHits();
for (int i = 0; i < value; i++) {
Product product = JSON.parseObject(h[i].getSourceAsString(), Item.class);
items.add(product);
}
System.out.println(items);
}
match 分词查询
match查询的特征:
- 会对查询条件进行分词。
- 然后将分词后的查询条件和目标字段分词后的词条进行等值匹配
- 默认取并集(OR),即只要查询条件中的一个分词和目标字段值的一个分词(词条)匹配,即认为匹配查询条件
# match查询
GET product/_search
{
"query": {
"match": {
"title": "手机充电器"
}
},
"size": 500
}
# AND 运算符:分词后同时存在才算匹配
GET product/_search
{
"query": {
"match": {
"title": {
"query": "华为手机",
"operator": "and"
}
}
},
"size": 500
}
@Test
public void testMatch() throws IOException {
// 构建查询请求对象,指定查询的索引名称
SearchRequest searchRequest = new SearchRequest("product");
// 创建查询条件构建器SearchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 查询条件
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "手机充电器");
// 设置关键字查询的运算符
matchQueryBuilder.operator(Operator.AND);
// 指定查询条件
searchSourceBuilder.query(matchQueryBuilder);
// 添加查询条件构建器 SearchSourceBuilder
searchRequest.source(searchSourceBuilder);
// 查询,获取查询结果
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取命中对象 SearchHits
SearchHits hits = search.getHits();
// 获取总记录数
long value = hits.getTotalHits().value;
ArrayList<Product> items = new ArrayList<>();
SearchHit[] h = hits.getHits();
for (int i = 0; i < value; i++) {
Product product = JSON.parseObject(h[i].getSourceAsString(), Product.class);
items.add(product);
}
System.out.println(items);
}
querystring 多字段查询
queryString 多条件查询
- 会对查询条件进行分词。
- 然后将分词后的查询条件和词条进行等值匹配
- 默认取并集(OR)
- 可以指定多个查询字段
query_string:可以识别 query 中的连接符(or 、and)
# queryString
GET product/_search
{
"query": {
"query_string": {
"fields": ["title","sellPoint"],
"query": "耳机 AND 充电器"
}
}
}
@Test
public void querystringQuery() throws IOException {
// 构造查询请求
SearchRequest searchRequest = new SearchRequest("product");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构造查询条件
QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("原装 AND 充电器").field("title").field("sellPoint");
// 设置具体的查询条件
searchSourceBuilder.query(queryStringQueryBuilder);
// 设置具体的请求参数
searchRequest.source(searchSourceBuilder);
// 发起查询请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 解析响应,获取查询到的文档数据
List<Item> items = new ArrayList<>();
SearchHits searchHits = searchResponse.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("total:" + total);
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 获取 hit对象的 soutce属性值 (原始文档对应的json字符串)
String docJson = hit.getSourceAsString();
// 将json转化为对象
Item item = JSON.parseObject(docJson, Item.class);
// 添加到结果集
items.add(item);
}
System.out.println(items);
}
range & sort 范围排序查询
GET product/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 1000
}
}
},
"sort": [
{
"price": {
"order": "desc" # 降序
} } ] }
@Test
public void rangeAndSort() throws IOException {
// 构造查询请求
SearchRequest searchRequest = new SearchRequest("product");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构造查询条件
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price").gte(50).lte(1000);
// 设置排序
searchSourceBuilder.sort("price", SortOrder.DESC);
// 设置具体的查询条件
searchSourceBuilder.query(rangeQueryBuilder);
// 设置具体的请求参数
searchRequest.source(searchSourceBuilder);
// 发起查询请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 解析响应,获取查询到的文档数据
List<Item> items = new ArrayList<>();
SearchHits searchHits = searchResponse.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("total:" + total);
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 获取 hit对象的 soutce属性值 (原始文档对应的json字符串)
String docJson = hit.getSourceAsString();
// 将json转化为对象
Item item = JSON.parseObject(docJson, Item.class);
// 添加到结果集
items.add(item);
}
System.out.println(items);
}
bool 复合查询
boolQuery:对多个查询条件连接。其组成主要分为如下四个部分:
- must(and):条件必须成立
- must_not(not):条件必须不成立
- should(or):条件可以成立
- filter:条件必须成立,性能比must高。不会计算得分
# boolquery 包含多个部分
GET product/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"title": {
"value": "充电器"
}
}
}
],
"filter":[
{
"term": {
"title": "原装"
}
},
{
"range":{
"price": {
"gte": 40,
"lte": 100
} } } ] } } }
@Test
public void boolQuery() throws IOException {
// 构造查询请求
SearchRequest searchRequest = new SearchRequest("product");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构造查询条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 设置查询条件
MatchQueryBuilder matchQueryBuilder1 = QueryBuilders.matchQuery("title", "充电器");
boolQueryBuilder.must(matchQueryBuilder1);
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price").gte(10).lte(100);
boolQueryBuilder.must(rangeQueryBuilder);
MatchQueryBuilder matchQueryBuilder2 = QueryBuilders.matchQuery("title", "原装");
boolQueryBuilder.should(matchQueryBuilder2);
// 设置具体的查询条件
searchSourceBuilder.query(boolQueryBuilder);
// 设置具体的请求参数
searchRequest.source(searchSourceBuilder);
// 发起查询请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 解析响应,获取查询到的文档数据
List<Item> items = new ArrayList<>();
SearchHits searchHits = searchResponse.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("total:" + total);
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 获取 hit对象的 soutce属性值 (原始文档对应的json字符串)
String docJson = hit.getSourceAsString();
// 将json转化为对象
Item item = JSON.parseObject(docJson, Item.class);
// 添加到结果集
items.add(item);
}
System.out.println(items);
}
注意事项:
- must 和 filter 区别是:must 会计算近似度得分
- 当 must 和 filter 同时存在时 会计算近似度得分,且整体性能更好,因为 filter 会过滤掉一些
- bool 查询 ,四个部分可以同时存在一个或多个
- should 和 must/filter 部分共存时可能会失效
aggs 聚合查询
聚合可以理解为增强,相当于给查询的结果再增加一个效果
聚合查询分为两种类型:
- 指标聚合:相当于 MySQL 的聚合函数。max、min、avg、sum等
- 桶聚合:相当于 MySQL 的 group by 操作
- 不要对 text 类型的数据进行分组,会失败
# 聚合查询
# 指标聚合 聚合函数
GET product/_search
{
"query": {
"match": {
"title": "耳机"
}
},
"aggs": {
"max_price": {
"max": {
"field": "price"
} } } }
# 桶聚合 分组
GET product/_search
{
"query": {
"match": {
"title": "充电器"
}
},
"aggs": {
"price_bucket": {
"terms": {
"field": "price",
"size": 100
} } } }
@Test
public void testAggQuery() throws IOException {
SearchRequest searchRequest=new SearchRequest("product");
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "充电器");
sourceBuilder.query(queryBuilder);
// 查询价格列表 只展示前100条
AggregationBuilder aggregation=AggregationBuilders.terms("price_bucket").field("price").size(100);
sourceBuilder.aggregation(aggregation);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取命中对象 SearchHits
SearchHits hits = searchResponse.getHits();
// 获取总记录数
Long total= hits.getTotalHits().value;
System.out.println("总数:"+total);
// aggregations 对象
Aggregations aggregations = searchResponse.getAggregations();
// 将aggregations 转化为map
Map<String, Aggregation> aggregationMap = aggregations.asMap();
// 通过key获取price_bucket 对象 使用Aggregation的子类接收 buckets属性在Terms接口中体现
// Aggregation price_bucket = aggregationMap.get("price_bucket");
Terms price_bucket =(Terms) aggregationMap.get("price_bucket");
// 获取buckets 数组集合
List<? extends Terms.Bucket> buckets = goods_brands.getBuckets();
Map<String,Object>map=new HashMap<>();
// 遍历buckets key 属性名,doc_count 统计聚合数
for (Terms.Bucket bucket : buckets) {
System.out.println(bucket.getKey());
map.put(bucket.getKeyAsString(),bucket.getDocCount());
}
System.out.println(map);
}
- 嵌套聚合
@Test
public void innerBucket() throws IOException {
// 构造查询请求
SearchRequest searchRequest = new SearchRequest("product");
// 构建搜索查询的对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构造查询条件
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price").gte(100).lte(1000);
// 设置具体的查询条件
searchSourceBuilder.query(rangeQueryBuilder);
// 构造聚合参数
// 1. 外层聚合
TermsAggregationBuilder outerAggregationsBuilder = AggregationBuilders.terms("price_bucket").field("price");
// 2. 内层聚合
TermsAggregationBuilder innerAggregationsBuilder = AggregationBuilders.terms("tm_bucket").field("tmName");
// 3. 给外层聚合设置内层聚合
outerAggregationsBuilder.subAggregation(innerAggregationsBuilder);
// 设置聚合参数
searchSourceBuilder.aggregation(outerAggregationsBuilder);
// 设置具体的请求参数
searchRequest.source(searchSourceBuilder);
// 发起查询请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取aggregations
Aggregations aggregations = searchResponse.getAggregations();
// 根据聚合名称获取聚合结果
Terms priceBucket = aggregations.get("price_bucket");
// 获取所有分桶
List<? extends Terms.Bucket> outerBuckets = priceBucket.getBuckets();
// 最终结果集
List<PriceAggregationInfo> result = new ArrayList<>();
// 遍历外层分桶
for (Terms.Bucket outerBucket : outerBuckets) {
// 定义品牌集合
PriceAggregationInfo priceAggregationInfo = new PriceAggregationInfo();
double key = outerBucket.getKeyAsNumber().doubleValue();
priceAggregationInfo.setPrice(key);
long outerDocCount = outerBucket.getDocCount();
System.out.println("outer: key:" + key + ",value:" + outerDocCount);
// 从外层分桶中获取内层分桶聚合的结果
Aggregations innerAggregations = outerBucket.getAggregations();
Terms innerTerms = innerAggregations.get("tm_bucket");
List<String> tmList = new ArrayList<>();
List<? extends Terms.Bucket> innerBuckets = innerTerms.getBuckets();
for (Terms.Bucket innerBucket : innerBuckets) {
// 获取内层分桶的key,即品牌
String tmKey = innerBucket.getKeyAsString();
long innerDocCount = innerBucket.getDocCount();
// 添加到品牌集合中
tmList.add(tmKey);
}
priceAggregationInfo.setTrademarkNames(tmList);
result.add(priceAggregationInfo);
}
System.out.println(result);
}
highlight 高亮查询
高亮三要素:
- 高亮字段
- 前缀
- 后缀
默认前后缀 :em
GET product/_search
{
"query": {
"match": {
"title": "充电器"
}
},
"highlight": {
"fields": {
"title": {
"pre_tags": "<font color='red'>",
"post_tags": "</font>"
} } } }
@Test
public void highlightQuery() throws IOException {
// 构造查询请求
SearchRequest searchRequest = new SearchRequest("product");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构造查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "充电器");
// 设置具体的查询条件
searchSourceBuilder.query(termQueryBuilder);
// 设置高亮参数
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title").preTags("<font color='red'>").postTags("</font>");
searchSourceBuilder.highlighter(highlightBuilder);
// 设置具体的请求参数
searchRequest.source(searchSourceBuilder);
// 发起查询请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 解析响应,获取查询到的文档数据
List<Item> items = new ArrayList<>();
SearchHits searchHits = searchResponse.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("total:" + total);
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 获取 hit对象的 soutce属性值 (原始文档对应的json字符串)
String docJson = hit.getSourceAsString();
// 将json转化为对象
Item item = JSON.parseObject(docJson, Item.class);
// 获取高亮字符串
// key:字段名称 value:高亮的字符串
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField highlightField = highlightFields.get("title");
// 获取高亮的字符串
String highlightString = highlightField.getFragments()[0].toString();
// System.out.println("highlightString:" + highlightString);
// 用高亮字符串替换原始值
item.setTitle(highlightString);
// 添加到结果集
items.add(item);
}
System.out.println(items);
}