package com.fdkankan.elasticsearch.service.impl; import com.alibaba.fastjson.JSON; import com.fdkankan.elasticsearch.service.QueryDataService; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.text.Text; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.ParsedAvg; import org.elasticsearch.search.aggregations.metrics.ParsedMax; import org.elasticsearch.search.aggregations.metrics.ParsedMin; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.elasticsearch.search.sort.SortOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * @author yanfengzhang * @description * @date 2022/12/8 23:37 */ @Service @Log4j2 public class QueryDataServiceImpl implements QueryDataService { @Autowired private RestHighLevelClient restHighLevelClient; /** * 精确查询(termQuery) * * @param indexName 索引名 * @param columnName 列名或字段名 * @param value 查询内容 * @param classz 数据结构 * @param 数据结构 * @return 精确查询内容数据 */ @Override public List termQuery(String indexName, String columnName, Object value, Class classz) { /* 查询的数据列表 */ List list = new ArrayList<>(); try { /*构建查询条件(注意:termQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.termQuery(columnName, value)); /*执行查询es数据*/ queryEsData(indexName, classz, list, searchSourceBuilder); } catch (IOException e) { log.error("精确查询数据失败,错误信息:", e); } return list; } /** * terms:多个查询内容在一个字段中进行查询 * * @param indexName 索引名 * @param columnName 列名或字段名 * @param dataArgs 查询内容集合 * @param classz 数据结构 * @param 数据结构 * @return 多个查询内容在一个字段中进行查询对应结果 */ @Override public List termsQuery(String indexName, String columnName, Object[] dataArgs, Class classz) { /*查询的数据列表*/ List list = new ArrayList<>(); try { /* 构建查询条件(注意:termsQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.termsQuery(columnName, dataArgs)); /*展示100条,默认只展示10条记录*/ searchSourceBuilder.size(100); /*执行查询es数据*/ queryEsData(indexName, classz, list, searchSourceBuilder); } catch (IOException e) { log.error("单字段多内容查询数据失败,错误信息:", e); } return list; } /** * 匹配查询符合条件的所有数据,并设置分页 * * @param indexName 索引名 * @param classz 数据结构 * @param startIndex 起始下标 * @param pageSize 页大小 * @param orderList 设置排序 * @param columnName 列名或字段名 * @param value 列名或字段名指定内容 * @param 数据结构 * @return 符合条件的所有数据 */ @Override public List matchAllQuery(String indexName, Class classz, int startIndex, int pageSize, List orderList, String columnName, Object value) { /*查询的数据列表*/ List list = new ArrayList<>(); try { /*创建查询源构造器*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); /*构建查询条件*/ if (StringUtils.isNotBlank(columnName) && Objects.nonNull(value)) { MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(columnName, value); searchSourceBuilder.query(matchQueryBuilder); } else { MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); searchSourceBuilder.query(matchAllQueryBuilder); } /*设置分页*/ searchSourceBuilder.from(startIndex); searchSourceBuilder.size(pageSize); /*设置排序*/ if (orderList != null) { for (String order : orderList) { /*开头代表:倒序*/ boolean flag = order.startsWith("-"); SortOrder sort = flag ? SortOrder.DESC : SortOrder.ASC; order = flag ? order.substring(1) : order; searchSourceBuilder.sort(order, sort); } } /*执行查询es数据*/ queryEsData(indexName, classz, list, searchSourceBuilder); } catch (IOException e) { log.error("查询所有数据失败,错误信息:", e); } return list; } /** * 词语匹配查询 * * @param indexName 索引名 * @param classz 数据结构 * @param columnName 列名或字段名 * @param value 指定内容 * @param 数据结构 * @return 词语匹配查询结果 */ @Override public List matchPhraseQuery(String indexName, Class classz, String columnName, Object value) { /*查询的数据列表*/ List list = new ArrayList<>(); try { /*构建查询条件*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.matchPhraseQuery(columnName, value)); /*执行查询es数据*/ queryEsData(indexName, classz, list, searchSourceBuilder); } catch (IOException e) { log.error("词语匹配查询失败,错误信息:", e); } return list; } /** * 内容在多字段中进行查询 * * @param indexName 索引名 * @param classz 数据结构 * @param fields 列名或字段名集合 * @param text 指定内容 * @param 数据结构 * @return 查询结果 */ @Override public List matchMultiQuery(String indexName, Class classz, String[] fields, Object text) { /*查询的数据列表*/ List list = new ArrayList<>(); try { /*构建查询条件*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); /*设置查询条件*/ searchSourceBuilder.query(QueryBuilders.multiMatchQuery(text, fields)); /*执行查询es数据*/ queryEsData(indexName, classz, list, searchSourceBuilder); } catch (IOException e) { log.error("词语匹配查询失败,错误信息:", e); } return list; } /** * 通配符查询(wildcard):会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符) * * @param indexName 索引名 * @param classz 数据结构 * @param field 列名或字段名集合 * @param text 指定内容 * @param 数据结构 * @return 查询结果 */ @Override public List wildcardQuery(String indexName, Class classz, String field, String text) { /*查询的数据列表*/ List list = new ArrayList<>(); try { /*构建查询条件*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.wildcardQuery(field, text)); /*执行查询es数据*/ queryEsData(indexName, classz, list, searchSourceBuilder); } catch (IOException e) { log.error("通配符查询失败,错误信息:", e); } return list; } /** * 模糊查询商品信息 * * @param indexName 索引名 * @param classz 数据结构 * @param field 列名或字段名集合 * @param text 指定内容 * @param 数据结构 * @return 查询结果 */ @Override public List fuzzyQuery(String indexName, Class classz, String field, String text) { /*查询的数据列表*/ List list = new ArrayList<>(); try { /*构建查询条件*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.fuzzyQuery(field, text).fuzziness(Fuzziness.AUTO)); /*执行查询es数据*/ queryEsData(indexName, classz, list, searchSourceBuilder); } catch (IOException e) { log.error("通配符查询失败,错误信息:", e); } return list; } /** * boolQuery 查询 * 高亮展示标题搜索字段 * 设置出参返回字段 * 案例:查询从2018-2022年间标题含 三星 的商品信息 * * @param indexName 索引名 * @param beanClass 数据结构 * @param 数据结构 * @return 查询结果 */ @Override public List boolQuery(String indexName, Class beanClass) { /*查询的数据列表*/ List list = new ArrayList<>(); try { /*创建 Bool 查询构建器*/ BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); /*构建查询条件*/ /*构建查询源构建器*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(boolQueryBuilder); searchSourceBuilder.size(100); /*甚至返回字段 如果查询的属性很少,那就使用includes,而excludes设置为空数组 如果排序的属性很少,那就使用excludes,而includes设置为空数组*/ /*创建查询请求对象,将查询对象配置到其中*/ SearchRequest searchRequest = new SearchRequest(indexName); searchRequest.source(searchSourceBuilder); /*执行查询,然后处理响应结果*/ SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); /*根据状态和数据条数验证是否返回了数据*/ if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { /* 将 JSON 转换成对象*/ T bean = JSON.parseObject(hit.getSourceAsString(), beanClass); /*获取高亮的数据*/ HighlightField highlightField = hit.getHighlightFields().get("title"); log.info("高亮名称:{}", highlightField.getFragments()[0].string()); /*替换掉原来的数据*/ Text[] fragments = highlightField.getFragments(); if (fragments != null && fragments.length > 0) { StringBuilder title = new StringBuilder(); for (Text fragment : fragments) { title.append(fragment); } /* 获取method对象,其中包含方法名称和参数列表*/ Method setTitle = beanClass.getMethod("setTitle", String.class); if (setTitle != null) { /*执行method,bean为实例对象,后面是方法参数列表;setTitle没有返回值*/ setTitle.invoke(bean, title.toString()); } } list.add(bean); } } } catch (Exception e) { log.error("布尔查询失败,错误信息:", e); } return list; } /** * 聚合查询 : 聚合查询一定是【先查出结果】,然后对【结果使用聚合函数】做处理. * Metric 指标聚合分析。常用的操作有:avg:求平均、max:最大值、min:最小值、sum:求和等 * 案例:分别获取最贵的商品和获取最便宜的商品 * * @param indexName 索引名 */ @Override public void metricQuery(String indexName) { try { /* 构建查询条件*/ MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); /*创建查询源构造器*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder); /*获取最贵的商品*/ AggregationBuilder maxPrice = AggregationBuilders.max("maxPrice").field("price"); searchSourceBuilder.aggregation(maxPrice); /*获取最便宜的商品*/ AggregationBuilder minPrice = AggregationBuilders.min("minPrice").field("price"); searchSourceBuilder.aggregation(minPrice); /*创建查询请求对象,将查询对象配置到其中*/ SearchRequest searchRequest = new SearchRequest(indexName); searchRequest.source(searchSourceBuilder); /*执行查询,然后处理响应结果*/ SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); Aggregations aggregations = searchResponse.getAggregations(); ParsedMax max = aggregations.get("maxPrice"); log.info("最贵的价格:" + max.getValue()); ParsedMin min = aggregations.get("minPrice"); log.info("最便宜的价格:" + min.getValue()); } catch (Exception e) { log.error("指标聚合分析查询失败,错误信息:", e); } } /** * 聚合查询: 聚合查询一定是【先查出结果】,然后对【结果使用聚合函数】做处理 * Bucket 分桶聚合分析 : 对查询出的数据进行分组group by,再在组上进行游标聚合 * 案例:根据品牌进行聚合查询 * * @param indexName 索引名 * @param bucketField * @param bucketFieldAlias */ @Override public void bucketQuery(String indexName, String bucketField, String bucketFieldAlias) { try { /*构建查询条件*/ MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); /*创建查询源构造器*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder); /*根据bucketField进行分组查询*/ TermsAggregationBuilder aggBrandName = AggregationBuilders.terms(bucketFieldAlias).field(bucketField); searchSourceBuilder.aggregation(aggBrandName); /*创建查询请求对象,将查询对象配置到其中*/ SearchRequest searchRequest = new SearchRequest(indexName); searchRequest.source(searchSourceBuilder); /*执行查询,然后处理响应结果*/ SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); Aggregations aggregations = searchResponse.getAggregations(); /*分组结果数据*/ ParsedStringTerms aggBrandName1 = aggregations.get(bucketFieldAlias); for (Terms.Bucket bucket : aggBrandName1.getBuckets()) { log.info(bucket.getKeyAsString() + "====" + bucket.getDocCount()); } } catch (IOException e) { log.error("分桶聚合分析查询失败,错误信息:", e); } } /** * 子聚合聚合查询 Bucket 分桶聚合分析 *

* 案例:根据商品分类进行分组查询,并且获取分类商品中的平均价格 * * @param indexName 索引名 * @param bucketField * @param bucketFieldAlias * @param avgFiled * @param avgFiledAlias */ @Override public void subBucketQuery(String indexName, String bucketField, String bucketFieldAlias, String avgFiled, String avgFiledAlias) { try { /*构建查询条件*/ MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); /*创建查询源构造器*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder); /* 根据 bucketField进行分组查询,并且获取分类信息中 指定字段的平均值*/ TermsAggregationBuilder subAggregation = AggregationBuilders.terms(bucketFieldAlias).field(bucketField) .subAggregation(AggregationBuilders.avg(avgFiledAlias).field(avgFiled)); searchSourceBuilder.aggregation(subAggregation); /* 创建查询请求对象,将查询对象配置到其中*/ SearchRequest searchRequest = new SearchRequest(indexName); searchRequest.source(searchSourceBuilder); /*执行查询,然后处理响应结果*/ SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); Aggregations aggregations = searchResponse.getAggregations(); ParsedStringTerms aggBrandName1 = aggregations.get(bucketFieldAlias); for (Terms.Bucket bucket : aggBrandName1.getBuckets()) { /*获取聚合后的 组内字段平均值,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象*/ ParsedAvg avgPrice = bucket.getAggregations().get(avgFiledAlias); log.info(bucket.getKeyAsString() + "====" + avgPrice.getValueAsString()); } } catch (IOException e) { log.error("分桶聚合分析查询失败,错误信息:", e); } } /** * 综合聚合查询 *

* 根据商品分类聚合,获取每个商品类的平均价格,并且在商品分类聚合之上子聚合每个品牌的平均价格 * * @param indexName 索引名 */ @Override public void subSubAgg(String indexName) { try { /*构建查询条件*/ MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); /*创建查询源构造器*/ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder); /*注意这里聚合写的位置不要写错,很容易搞混,错一个括号就不对了*/ TermsAggregationBuilder subAggregation = AggregationBuilders.terms("categoryNameAgg").field("categoryName") .subAggregation(AggregationBuilders.avg("categoryNameAvgPrice").field("price")) .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName") .subAggregation(AggregationBuilders.avg("brandNameAvgPrice").field("price"))); searchSourceBuilder.aggregation(subAggregation); /*创建查询请求对象,将查询对象配置到其中*/ SearchRequest searchRequest = new SearchRequest(indexName); searchRequest.source(searchSourceBuilder); /*执行查询,然后处理响应结果*/ SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); /*获取总记录数*/ log.info("totalHits = " + searchResponse.getHits().getTotalHits().value); /*获取聚合信息*/ Aggregations aggregations = searchResponse.getAggregations(); ParsedStringTerms categoryNameAgg = aggregations.get("categoryNameAgg"); /*获取值返回*/ for (Terms.Bucket bucket : categoryNameAgg.getBuckets()) { /*获取聚合后的分类名称*/ String categoryName = bucket.getKeyAsString(); /*获取聚合命中的文档数量*/ long docCount = bucket.getDocCount(); /*获取聚合后的分类的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象*/ ParsedAvg avgPrice = bucket.getAggregations().get("categoryNameAvgPrice"); log.info(categoryName + "======平均价:" + avgPrice.getValue() + "======数量:" + docCount); ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg"); for (Terms.Bucket brandeNameAggBucket : brandNameAgg.getBuckets()) { /*获取聚合后的品牌名称*/ String brandName = brandeNameAggBucket.getKeyAsString(); /*获取聚合后的品牌的平均价格,注意返回值不是Aggregation对象,而是指定的ParsedAvg对象*/ ParsedAvg brandNameAvgPrice = brandeNameAggBucket.getAggregations().get("brandNameAvgPrice"); log.info(" " + brandName + "======" + brandNameAvgPrice.getValue()); } } } catch (IOException e) { log.error("综合聚合查询失败,错误信息:", e); } } /** * 执行es查询 * * @param indexName * @param beanClass * @param list * @param searchSourceBuilder * @param * @throws IOException */ private void queryEsData(String indexName, Class beanClass, List list, SearchSourceBuilder searchSourceBuilder) throws IOException { /*创建查询请求对象,将查询对象配置到其中*/ SearchRequest searchRequest = new SearchRequest(indexName); searchRequest.source(searchSourceBuilder); /*执行查询,然后处理响应结果*/ SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); /*根据状态和数据条数验证是否返回了数据*/ if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) { SearchHits hits = searchResponse.getHits(); for (SearchHit hit : hits) { /*将 JSON 转换成对象*/ T bean = JSON.parseObject(hit.getSourceAsString(), beanClass); list.add(bean); } } } }