ES在项目中的使用
1、导入依赖
<!--spring boot 与es 结合包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
使用ElasticsearchTemplate驱动使用
常使用的查询ES数据的方法
import java.util.Map;
/**
* @author zyf
* @Date 2020/2/26
*/
public interface SearchService {
//按照查询条件进行数据查询
Map search(Map<String,String> searchMap)
}
业务代码
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.http.impl.client.HttpClientBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.*;
/**
* @author zyf
* @Date 2020/2/26
*/
@Service
public class SearchServiceImpl implements SearchService {
//可以当成是查询器驱动
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Override
public Map search(Map<String, String> searchMap) {
//将返回结果对象创建
Map<String, Object> resultMap = new HashMap<>();
//传参的非空判断
if (searchMap!=null){
//2.构建条件对象,查询器,封装了cilent
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//将请求条件放入SearchQueryBuilder,多条件查询使用布尔查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//按照搜索框输入的值进行模糊查询
if (!StringUtils.isEmpty(searchMap.get("keywords"))){
//条件查询,第一个参数为索引库中字段,第二个参数为传参,就是根据输入值去查询哪个字段
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", searchMap.get("keywords"));
//operator默认取并集(OR)还有(AND)对sql查询中的与、或,对搜索框输入词条不同操作
matchQueryBuilder.operator(Operator.AND);
//将条件查询放入布尔查询中,must表示必须被执行
boolQueryBuilder.must(matchQueryBuilder);
}
//按照品牌进行查询
if (!StringUtils.isEmpty(searchMap.get("brand"))){
//term查询,精确查询,不对词条进行分词,参数解释和matchquery一样
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName", searchMap.get("brand"));
//term和rang等查询一般都放入filter中,也表示必须被执行,filter表示过滤条件
boolQueryBuilder.filter(termQueryBuilder);
}
//按照规格进行查询
for (String key : searchMap.keySet()) {
if (key.startsWith("spec_")){
//"+"号http协议不识别,controller传参中需要编码,service需要解码
String value = searchMap.get(key).replace("%2B","+" );
//词条精确查询,这里还用到字符串拼接
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(
"specMap." + key.substring(5) + ".keyword", value);
//放入filter中,必须执行该条件
boolQueryBuilder.filter(termQueryBuilder);
}
}
//按照价格区间查询
if (!StringUtils.isEmpty(searchMap.get("price"))){
//rang查询,范围查询,多用于int查询,数值查询
RangeQueryBuilder priceBuild;
String[] split = searchMap.get("price").split("-");
if (split.length==2){
//两个参数时,gte为起始值,lte为结束值,查询区间中信息
priceBuild = QueryBuilders.rangeQuery("price").gte(split[0]).lte(split[1]);
}else {
//一个参数时,只传入gte起始值即可
priceBuild = QueryBuilders.rangeQuery("price").gte(split[0]);
}
//filter必须执行该条件
boolQueryBuilder.filter(priceBuild);
}
//以上为布尔查询的区间
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
//按照品牌聚合查询,和boolquery同级。terms为自定义聚合列名称,唯一;field为索引库中字段,可多个
TermsAggregationBuilder skuBrandBuilder = AggregationBuilders.terms("skuBrand").field("brandName").size(10);
//条件对象聚合
nativeSearchQueryBuilder.addAggregation(skuBrandBuilder);
//按照规格聚合查询,直接作用于nativeSearchQueryBuilder
TermsAggregationBuilder specBuilder = AggregationBuilders.terms("skuSpec").field("spec.keyword").size(10);
nativeSearchQueryBuilder.addAggregation(specBuilder);
//分页,直接作用于nativeSearchQueryBuilder
String pageNum = searchMap.get("pageNum");//页数,第几页
String pageSize = searchMap.get("pageSize");//每页条数,默认十条
if (StringUtils.isEmpty(pageNum)){
pageNum="1";
}
if (StringUtils.isEmpty(pageSize)){
pageSize="30";
}
//withPageable分页条件对象,两个参数,第几页、每页显示多少条
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum)-1,Integer.parseInt(pageSize) ));
//排序,withSort排序对象,order排序方法,传参为SortOrder,和查询同级,直接作用于
if (!StringUtils.isEmpty(searchMap.get("sortField"))&&!StringUtils.isEmpty(searchMap.get("sortRule"))){
if ("ASc".equals(searchMap.get("sortRule"))){
//升序
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
}else {
//降序
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
}
}
//字段设置高亮,直接作用于nativeSearchQueryBuilder,field为索引库中被查询字段,preTags前缀,postTags后缀,多以html语法添加
HighlightBuilder.Field field = new HighlightBuilder.Field("name").preTags("<span style='color:red'>").postTags("</span>");
nativeSearchQueryBuilder.withHighlightFields(field);
/**
*1.用templete查询 执行操作
* searchQuery query 查询条件
* class<T> clazz, skuinfo 查询结果封装
* searchResultMapper mapper,查询结果对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(
nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
/**
*
* @param searchResponse 返回结果
* @param aClass 结果泛型类
* @param pageable 分页
* @param <T>
* @return
*/
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//定义返回对象
List<T> skulist = new ArrayList<>();
//获取结果
SearchHits hits = searchResponse.getHits();
if (hits!=null){
for (SearchHit hit : hits) {
//获取Json格式的string串
//元字符串
String sourceAsString = hit.getSourceAsString();
//转换为java对象
SkuInfo skuInfo = JSON.parseObject(sourceAsString, SkuInfo.class);
//获取所有高亮字段
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField highlightField = highlightFields.get("name");
if (highlightField!=null){
Text[] fragments = highlightField.getFragments();
StringBuffer sb = new StringBuffer();
for (Text fragment : fragments) {
sb.append(fragment);
}
String highName = sb.toString();
skuInfo.setName(highName);
}
skulist.add((T)skuInfo);
}
}
//封装返回结果
return new AggregatedPageImpl<>(skulist,pageable , hits.getTotalHits(), searchResponse.getAggregations());
}
});
//根据和前端讨论决定返回结果
//总记录数
resultMap.put("total",resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages",resultInfo.getTotalPages());
//数据集合
resultMap.put("rows",resultInfo.getContent());
//品牌聚合
List<String> brandList = new ArrayList<>();
//获取聚合结果
Aggregations aggregations = resultInfo.getAggregations();
//转换为map
Map<String, Aggregation> asMap = aggregations.getAsMap();
//生成词条对象
Terms aggregation = (Terms) asMap.get("skuBrand");
//桶聚合,集合
List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
for (Terms.Bucket bucket : buckets) {
//遍历桶,转换为String
String keyAsString = bucket.getKeyAsString();
brandList.add(keyAsString);
}
resultMap.put("brandList", brandList);
//封装规格分组结果
List<String> specList = new ArrayList<>();
StringTerms specTerms = (StringTerms) resultInfo.getAggregation("skuSpec");
List<StringTerms.Bucket> specBuckets = specTerms.getBuckets();
for (StringTerms.Bucket specBucket : specBuckets) {
String asString = specBucket.getKeyAsString();
specList.add(asString);
}
Map<String, Set<String>> specMap = this.formartSpec(specList);
resultMap.put("specList", specMap);
//封装分页信息
resultMap.put("pageNum",pageNum );
return resultMap;
}
return null;
}
//list转换成map
public Map<String, Set<String>> formartSpec(List<String> specList) {
Map<String,Set<String>> resultMap = new HashMap<>();
if (specList!=null){
for (String spec : specList) {
Map<String, String> map = JSON.parseObject(spec, Map.class);
for (String key : map.keySet()) {
Set<String> specValue = resultMap.get(key);
if (specValue==null){
specValue = new HashSet<>();
}
specValue.add(map.get(key));
resultMap.put(key, specValue);
}
}
}
//最终返回结果
return resultMap;
}
}
总结:
- 词条查询:term ,不会分析查询条件,只有当词条和查询字符串完全匹配时才匹配搜索
- 全文查询:match ,会分析查询条件,先将查询条件进行分词,然后查询,求并集。将搜索条件进行分词
- matchAll ,全文查询,查询所有,不需要传参
- matchQuery,将分词后的查询条件和词条进行等值匹配,默认取并集(OR)
- range 范围查询:查找指定字段在指定范围内包含值。gte前缀,lte后缀
- sort排序,order参数,ASC、DESC
- queryString查询,对查询条件进行分词,将分词后的查询条件和词条进行等值匹配 ,可以指定多个查询字段(field)
- bool 布尔查询,对多个查询条件连接 。must(and):条件必须成立 。must_not(not):条件必须不成立 。should(or):条件可以成立 。filter:条件必须成立,性能比must高。不会计算得分。
- aggs 聚合查询,指标聚合:相当于MySQL的聚合函数。max、min、avg、sum等 。桶聚合:相当于MySQL的group by 操作。不要对text类型的数据进行分组,会失败。
- highlight 高亮查询,三要素:高亮字段(fields),前缀(pre_tags),后缀(post_tags)。
查询,聚合,排序,分页 为同级别条件
脚本返回结果
{
"took":4, 请求花了多少时间
"time_out":false, 有没有超时
"_shards":{ 执行请求时查询的分片信息
"total":5, 查询的分片数量
"successful":5, 成功返回结果的分片数量
"failed":0 失败的分片数量
},
"hits":{
"total":2, 查询返回的文档总数
"max_score":0.625, 计算所得的最高分
"hits":[ 返回文档的hits数组
{
"_index":"books", 索引
"_type":"es", 属性
"_id":"1", 标志符
"_score":0.625, 得分
"_source":{ 发送到索引的JSON对象
"title":"Elasticsearch Server",
"publish":2013
}
},
{
"_index":"books",
"_type":"es",
"_id":"2",
"_score":0.19178301,
"_source":{
"title":"Mastering Elasticsearch",
"published":2013
}
}
]
}
}```
term是代表完全匹配,也就是精确查询,搜索前不会再对搜索词进行分词,所以我们的搜索词必须是文档分词集合中的一个
TermsBuilder:构造聚合函数
AggregationBuilders:创建聚合函数工具类
BoolQueryBuilder:拼装连接(查询)条件
QueryBuilders:简单的静态工厂”导入静态”使用。主要作用是查询条件(关系),如区间\精确\多值等条件
NativeSearchQueryBuilder:将连接条件和聚合函数等组合
SearchQuery:生成查询
elasticsearchTemplate.query:进行查询
Aggregations:Represents a set of computed addAggregation.代表一组添加聚合函数统计后的数据
Bucket:满足某个条件(聚合)的文档集合