elasticsearch(ES)在SpringBoot中的复杂查询(多条件分页查询以及聚合查询)

一、回顾

上回简单写了ES在springboot项目中的简单使用,elasticsearch(ES)在SpringBoot中的基本使用 ,介绍了es的基本概念以及基本的查询等,但是针对多条件分页查询或者聚合查询等,使用ElasticSearchRepository可能就有些力不从心了,需要借助更为强大的elasticsearchTemplate,本文首先介绍多条件的分页查询,然后介绍项目中常用的聚合统计查询等。

二、ES复杂查询

1)多条件分页查询

由于是多条件的查询,再使用之前的TermQueryBuilder/MatchQueryBuilder等都不能满足要求了,需要使用多个条件混合的BoolQueryBuiler 进行查询,搜索查询也使用原生查询NativeSearchQuery进行查询。

//    多条件分页查询
    public  PageModel<Book> multiConditionQuery(BookDto bookDto){
        String name = bookDto.getName();
        String mainType = bookDto.getMainType();
        List<Integer> pageNumList = bookDto.getPageNumList();
        Date startTime = bookDto.getStartTime();
        Date endTime = bookDto.getEndTime();
//        分页
        Pageable page = PageRequest.of(bookDto.getPage()-1,bookDto.getPageSize());
//      bool-查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(StringUtils.isNotBlank(name)){
            boolQueryBuilder.filter(QueryBuilders.termQuery("name",name));
        }
        if(StringUtils.isNotBlank(mainType)){
            boolQueryBuilder.filter(QueryBuilders.termQuery("mainType",mainType));
        }
        if(CollectionUtils.isNotEmpty(pageNumList)){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("pageNum",pageNumList));
        }
        if(startTime != null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("createTime").gte(DateUtil.formatESDate(startTime)));
        }
        if(endTime != null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("createTime").lte(DateUtil.formatESDate(endTime)));
        }
//        排序
        FieldSortBuilder createTimeSort = SortBuilders.fieldSort("createTime").order(SortOrder.DESC);
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        NativeSearchQuery build = nativeSearchQueryBuilder.withQuery(boolQueryBuilder)
                                                          .withPageable(page)
                                                          .withSort(createTimeSort)
                                                           .build();
        AggregatedPage<Book> booksAgg = elasticsearchTemplate.queryForPage(build, Book.class);
        PageModel<Book> bookPageModel =
                new PageModel<>(booksAgg.getContent(),bookDto.getPage(),bookDto.getPageSize(),booksAgg.getTotalElements());
        return bookPageModel;
    }

2)聚合查询-带子聚合

项目中,我们经常有统计一个大类以及该大类下面的小类数量的需求,比如以我们的Book实体为例,我们可能需要统计库中"文学” 大类下面的“小说”、“散文”、“诗歌”等子类各有多少本。为了更好地应用该功能,我们模拟“造”一些数据存入到es中,插入数据的示例如下,我们使用random随机插入一些数据,只不过每一项的枚举我们事前固定在数组中。

 @ApiOperation(value = "批量保存book数据")
    @RequestMapping(value = "/batchSave", method = RequestMethod.PUT)
    public String testSaveBook(@RequestParam Integer n) {
        Random random = new Random();
        String[] names = {"三国志", "三国演义", "三国", "红楼梦", "红楼", "西游记", "三国演义"};
        Integer[] pageNums = {100, 200, 300, 400, 500};
        String[] mainTypes = {"文学", "数学", "编程学", "法律学"};
        String[] subType0s = {"小说", "散文", "诗歌", "话剧"};
        String[] subType1s = {"代数", "几何"};
        String[] subType2s = {"Java", "c++", "python", "go"};
        String[] subType3s = {"民法", "刑法", "商法"};
        String[] introductions = {"这是一本很好的故事", "这里面有非常动人的故事", "孙悟空三大白骨精"};
        Double[] prices = {38.6, 35.8, 50.9, 40.7};
        for (int i = 0; i < n; i++) {
            Book b = new Book();
            String id = UUID.randomUUID().toString().replace("_", "").substring(0, 10);
            b.setId(id);
            b.setName(names[random.nextInt(names.length)]);
            b.setPageNum(pageNums[random.nextInt(pageNums.length)]);
            b.setPrice(prices[random.nextInt(prices.length)]);
            b.setIntroduction(introductions[random.nextInt(introductions.length)]);
            Integer mainTypeIndex = random.nextInt(mainTypes.length);
            b.setMainType(mainTypes[mainTypeIndex]);
//          子类型数据插入
            switch (mainTypeIndex) {
                case 0:
                    b.setSubType(subType0s[random.nextInt(subType0s.length)]);
                    break;
                case 1:
                    b.setSubType(subType1s[random.nextInt(subType1s.length)]);
                    break;
                case 2:
                    b.setSubType(subType2s[random.nextInt(subType2s.length)]);
                    break;
                case 3:
                    b.setSubType(subType3s[random.nextInt(subType3s.length)]);
                    break;
                default:
                    break;
            }
            b.setCreateTime(DateUtil.getOneDayOnset(random.nextInt(90), new Date()));
            System.out.println("========save book ======= " + i + " ok!");
            bookRepository.save(b);
        }
        return "ok";
    }

然后我们就可以使用聚合查询统计出这些数据了,这里主要是在NativeSearchQuery中添加聚合查询,聚合查询类似MySQL中的group by 分组查询。

/**
     * 聚合查询
     */
    public void aggQueryByMainType(BookDto bookDto){
        Date startTime = bookDto.getStartTime();
        Date endTime = bookDto.getEndTime();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(startTime != null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("createTime").gte(startTime));
        }
        if(endTime != null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("createTime").lte(endTime));
        }
//      聚合查询再聚合查询,双次聚合
        TermsAggregationBuilder fieldAgg = AggregationBuilders.terms("mainType").field("mainType");
        fieldAgg.subAggregation(AggregationBuilders.terms("subType").field("subType"));
//       构建原生查询query
        NativeSearchQuery build = new NativeSearchQueryBuilder()
                .addAggregation(fieldAgg)
                .withQuery(boolQueryBuilder)
                .withPageable(PageRequest.of(0, 1))
                .build();
        AggregatedPage<Book> booksAgg = elasticsearchTemplate.queryForPage(build, Book.class);
        Aggregations aggregations = booksAgg.getAggregations();
        Terms terms = (Terms)aggregations.asMap().get("mainType");
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
//      取出桶内的数据
        for(Terms.Bucket bucket :buckets){
            String mainType = bucket.getKeyAsString();
            System.out.println("mainType是:"+mainType+" 总数量是:"+bucket.getDocCount());
            Aggregations subAgg = bucket.getAggregations();
            Terms subTerms = (Terms)subAgg.asMap().get("subType");
            List<? extends Terms.Bucket> subBbuckets = subTerms.getBuckets();
//            取出子桶中的内容
            for (Terms.Bucket bucket1 : subBbuckets){
                String subType = bucket1.getKeyAsString();
                long subCount = bucket1.getDocCount();
                System.out.println("=======对应的子级聚合的子类是:"+subType+" 子级数量是:"+subCount);
            }
        }
    }

向es es_book_index 索引中插入500条数据,然后再统计,最后可以得到相应的统计结果:

mainType是:法律学mainType的总数量是:128
=======对应的子级聚合的子类是:民法 子级数量是:45
=======对应的子级聚合的子类是:刑法 子级数量是:43
=======对应的子级聚合的子类是:商法 子级数量是:40
mainType是:编程学mainType的总数量是:128
=======对应的子级聚合的子类是:python 子级数量是:42
=======对应的子级聚合的子类是:Java 子级数量是:31
=======对应的子级聚合的子类是:c++ 子级数量是:29
=======对应的子级聚合的子类是:go 子级数量是:26
mainType是:文学mainType的总数量是:127
=======对应的子级聚合的子类是:小说 子级数量是:40
=======对应的子级聚合的子类是:话剧 子级数量是:37
=======对应的子级聚合的子类是:散文 子级数量是:28
=======对应的子级聚合的子类是:诗歌 子级数量是:22
mainType是:数学mainType的总数量是:117
=======对应的子级聚合的子类是:几何 子级数量是:62
=======对应的子级聚合的子类是:代数 子级数量是:55

三、小结

 借助elasticsearchTemplate 可以为我们提供强大的搜索统计功能,项目中我们经常以时间为横坐标,做各种增长趋势图等,这些需求我们都可以借助es的聚合查询轻松实现。后续将继续探讨es其他的强大功能。。。

欢迎关注博主公众号,后台回复  207  可领取2021最新Java全栈视频教程

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值