【SpringBoot】+【Elasticsearch------->DSL分类查询】

1. DSL分类查询

Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all

  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

    • match_query
    • multi_match_query
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:

    • ids
    • range
    • term
  • 地理(geo)查询:根据经纬度查询。例如:

    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

    • bool
    • function_score

2. 深度分页问题

针对深度分页,ES提供了两种解决方案,官方文档

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  • scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。

分页查询的常见实现方案以及优缺点:

  • from + size

    • 优点:支持随机翻页
    • 缺点:深度分页问题,默认查询上限(from + size)是10000
    • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
  • search after

    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:只能向后逐页查询,不支持随机翻页
    • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
  • scroll

    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:会有额外内存消耗,并且搜索结果是非实时的
    • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用search after方案。

3. DSL分类查询代码实现

  • 数组遍历使用stream流
List<HotelDoc> hotelDocList = Stream.of(hits).map(hit -> {
            //得到每条记录对应的json串
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            System.out.println("hotelDoc = " + hotelDoc);
            return hotelDoc;
        }).collect(Collectors.toList());
  • DSL分类查询代码实现
@Autowired
    private RestHighLevelClient client;

    /**
     * 查询所有
     * match_all
     *
     * @throws Exception
     */
    @Test
    public void matchAllTest() throws Exception {
        //1.创建请求对象
        SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
        //2..设置条件,准备DSL
        searchRequest.source().query(
                QueryBuilders.matchAllQuery()
        );
        //3.执行查询, search
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        //4.解析得到的结果
        //4.1得到的总条数
        Long count = searchResponse.getHits().getTotalHits().value;
        System.out.println("count = " + count);
        //4.2得到hits数组(记录列表)
        SearchHit[] hits = searchResponse.getHits().getHits();
        List<HotelDoc> hotelDocList = Stream.of(hits).map(hit -> {
            //得到每条记录对应的json串
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            System.out.println("hotelDoc = " + hotelDoc);
            return hotelDoc;
        }).collect(Collectors.toList());
        /*for (SearchHit hit : hits) {
            //得到每条记录对应的json串
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            System.out.println("hotelDoc = " + hotelDoc);
        }*/
    }

    /**
     * match
     * multi_match
     *
     * @throws Exception
     */
    @Test
    public void matchTest() throws Exception {
        //1.创建请求对象
        SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
        //2.设置请求条件,DSL
        searchRequest.source().query(
                //QueryBuilders.matchQuery("all","外滩如家")
                QueryBuilders.multiMatchQuery("外滩如家", "name", "brand", "business")
        );
        //3.执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        //4.解析得到的结果
        Long count = response.getHits().getTotalHits().value;
        System.out.println("count = " + count);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            System.out.println("json = " + json);
        }
    }

    /**
     * term精确查询
     *
     * @throws Exception
     */
    @Test
    public void termTest() throws Exception {
        //1.创建请求对象
        SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
        //2.设置请求条件,DSL
        searchRequest.source()
                .query(QueryBuilders.termQuery("city", "上海"));
        //3.执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        //4.解析得到的结果
        Long count = response.getHits().getTotalHits().value;
        System.out.println("count = " + count);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            System.out.println("json = " + json);
        }
    }

    /**
     * range
     * 范围查询
     *
     * @throws Exception
     */
    @Test
    public void rangeTest() throws Exception {
        //1.创建请求对象
        SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
        //2.设置请求条件,DSL
        searchRequest.source().query(QueryBuilders.rangeQuery("price").gte(1000).lt(3000));
        //3.执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        //4.解析得到的结果
        Long count = response.getHits().getTotalHits().value;
        System.out.println("count = " + count);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            System.out.println("json = " + json);
        }
    }

    /**
     * 经纬度查询
     *
     * @throws Exception
     */
    @Test
    public void geoDistanceTest() throws Exception {
        //1.创建请求对象
        SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
        //2.设置请求条件,DSL
        searchRequest.source()
                .query(QueryBuilders.geoDistanceQuery("location")
                        //.point(new GeoPoint(31.242201,121.509106))
                        .point(31.242201, 121.509106)
                        .distance(15, DistanceUnit.KILOMETERS));
        //3.执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        //4.解析得到的结果
        Long count = response.getHits().getTotalHits().value;
        System.out.println("count = " + count);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            System.out.println("json = " + json);
        }
    }

    /**
     * bool查询
     *
     * @throws Exception
     */
    @Test
    public void Test() throws Exception {
        //1.创建请求对象
        SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
        //2.设置请求条件,DSL
        searchRequest.source()
                .query(QueryBuilders.boolQuery()
                        .must(QueryBuilders.matchQuery("name", "如家"))
                        .mustNot(QueryBuilders.rangeQuery("price").gt(400))
                        .filter(QueryBuilders.geoDistanceQuery("location")
                                .point(31.21, 121.5)
                                .distance(15, DistanceUnit.KILOMETERS)));
        //3.执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        //4.解析得到的结果
        Long count = response.getHits().getTotalHits().value;
        System.out.println("count = " + count);
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            System.out.println("json = " + json);
        }
    }

过滤+算分+排序+距离排序+高亮+分页

得到的距离数据和高亮数据需要特殊处理

 @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult<HotelDoc> searchHotel(SearchParam searchParam) {
        //1.校验参数
        //第几条数据
        Integer page = searchParam.getPage();
        //页面大小
        Integer size = searchParam.getSize();
        //搜索的内容
        String key = searchParam.getKey();
        //创建封装结果对象
        PageResult<HotelDoc> hotelDocPageResultge = new PageResult<>();
        //创建集合
        List<HotelDoc> hotelDocList = new ArrayList<>();
        try {
            if (page == null || size == null) {
                throw new RuntimeException("非法参数");
            }
            //2.业务处理
            //2.1创建请求对象
            SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
            //2.2准备查询条件
            //创建查询条件对象---->使用bool查询
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //------>搜索框 判断null和空串
            if (StringUtils.isBlank(key)) {
                //2.2.1如果是空,就查询所有
                boolQuery.must(QueryBuilders.matchAllQuery());
            } else {
                //2.2.2如果不是空,就根据条件查询
                boolQuery.must(QueryBuilders.matchQuery("all", key));
            }
            //------>城市
            String city = searchParam.getCity();
            if (StringUtils.isNoneBlank(city)) {
                boolQuery.filter(QueryBuilders.termQuery("city", city));
            }
            //------------>品牌
            String brand = searchParam.getBrand();
            if (StringUtils.isNoneBlank(brand)) {
                boolQuery.filter(QueryBuilders.termQuery("brand", brand));
            }
            //------------>星级
            String starName = searchParam.getStarName();
            if (StringUtils.isNoneBlank(starName)) {
                boolQuery.filter(QueryBuilders.termQuery("starName", starName));
            }
            //--------->价格
            Integer minPrice = searchParam.getMinPrice();
            Integer maxPrice = searchParam.getMaxPrice();
            if (null != minPrice && null != maxPrice) {
                boolQuery.filter(QueryBuilders
                        .rangeQuery("price")
                        .gte(minPrice)
                        .lte(maxPrice));
            }
            //-----------> 算分 isAD=true  加分用于广告
            FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(boolQuery,
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    QueryBuilders.termQuery("isAD", true),
                                    ScoreFunctionBuilders.weightFactorFunction(100F)
                            )}).boostMode(CombineFunction.SUM);

            //searchRequest.source().query(boolQuery);
            //================>排序 在分页之前,在条件之后
            String sortBy = searchParam.getSortBy();
            if ("score".equals(sortBy)) {
                searchRequest.source().sort("score", SortOrder.DESC);
            } else if ("price".equals(sortBy)) {
                searchRequest.source().sort("price", SortOrder.DESC);
            }

            //===============> 根据距离排序  -->得到查询的距离后还需要处理
            String location = searchParam.getLocation();
            if (StringUtils.isNoneBlank(location)) {
                searchRequest.source().sort(
                        SortBuilders.geoDistanceSort("location", new GeoPoint(location))
                                .unit(DistanceUnit.KILOMETERS));
            }
            //2.3准备分页参数
            //==========================>高亮显示 -->高亮数据也需要处理返回给前端
            //functionScoreQueryBuilder中封装的有boolQuery
            searchRequest.source()
                    .query(functionScoreQueryBuilder).highlighter(
                            new HighlightBuilder()
                                    .field("name")
                                    .requireFieldMatch(false)
                                    .preTags("<font style='color:red'>")
                                    .postTags("</font>")
                    )
                    .from((page - 1) * size)
                    .size(size);
            //2.4执行查询
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            //2.5解析结果并封装数据
            //2.5.1得到符合条件的总记录数
            long total = response.getHits().getTotalHits().value;
            //2.5.2得到符合条件的记录数
            for (SearchHit hit : response.getHits().getHits()) {
                String hotelDocJson = hit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(hotelDocJson, HotelDoc.class);
                //处理得到的距离数据
                Object[] sortValues = hit.getSortValues();
                if (sortValues != null && sortValues.length > 0) {
                    //得到当前位置与location的距离
                    Object distance = sortValues[0];
                    hotelDoc.setDistance(distance);
                }
                //处理得到的高亮的数据
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                //判断得到的高亮数据是否为空
                if (highlightFields!=null && highlightFields.size()>0) {
                    HighlightField highlightField = highlightFields.get("name");
                    Text[] fragments = highlightField.getFragments();
                    String name = fragments[0].toString();
                    hotelDoc.setName(name);
                }
                hotelDocList.add(hotelDoc);
            }
            //2.5.3封装数据
            hotelDocPageResultge.setTotal(total);
            hotelDocPageResultge.setHotels(hotelDocList);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return hotelDocPageResultge;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值