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:满足某个条件(聚合)的文档集合