暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

spring data elasticsearch复杂查询指南

Elastic之家 2022-07-16
1187

0. 引言

因为es非关系型数据库的特性,我们常常需要在实际业务中实现复杂查询,从而来查询到我们想要的数据。

很多同学刚接触java client不知道如何实现各类复杂查询操作。今天我们就来讲讲一些常见的复杂查询如何实现

1. 运行环境

下文演示基于如下环境

    spring-data-elasticsearch 4.2.10
    elasticsearch 7.13.0
    java 1.8
    spring-boot 2.3.7.RELEASE

    开始讲解之前,先声明我们的索引结构,方便大家后续理解我们的案例

      # 订单索引,一个订单下有多个商品
      PUT order_test
      {
      "mappings": {
      "properties": {
      // 订单状态 0未付款 1未发货 2运输中 3待签收 4已签收
      "status": {
      "type": "integer"
      },
      // 订单编号
      "no": {
      "type": "keyword"
      },
      // 下单时间
      "create_time": {
      "type": "date",
      "format": "yyyy-MM-dd HH:mm:ss"
      },
      // 订单金额
      "amount": {
      "type": "double"
      },
      // 创建人
      "creator":{
      "type": "keyword"
      },
      // 地址
      "address":{
      "type": "text",
      "analyzer": "ik_max_word"
      },
      // 地址坐标
      "point":{
      "type": "geo_point"
      },
      // 商品信息
      "product":{
      "type": "nested",
      "properties": {
      // 商品ID
      "id": {
      "type": "keyword"
      },
      // 商品名称
      "name":{
      "type": "keyword"
      },
      // 商品价格
      "price": {
      "type": "double"
      },
      // 商品数量
      "quantity": {
      "type": "integer"
      }
      }
      }
      }
      }
      }


      文中演示数据可点击阅读原文获取


      如果不知道springboot如何整合spring-data-elasticsearch的,可以参考我之前的文章

      从零搭建springboot+spring data elasticsearch4.2.x环境[1]

      实体类

      2. 查询指南

      2.1 精确查询 Term

      案例:

      查询编号为DD202205280003的订单

      DSL:

        GET order_test/_search
        {
        "query": {
        "term": {
        "no": {
        "value": "DD202205280003"
        }
        }
        }
        }

        java:

           @GetMapping("getByNo")
          public Order getByNo(String no){
          NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
          queryBuilder.withQuery(QueryBuilders.termsQuery("no",no));
          SearchHit<Order> searchRes = restTemplate.searchOne(queryBuilder.build(), Order.class);
          return searchRes == null ? null : searchRes.getContent();
              }



          2.2 多值查询 Terms

          案例:

          查询未付款、未发货的订单

          DSL:

            GET order_test/_search
            {
            "query": {
            "terms": {
            "status": [
            0,
            1
            ]
            }
            }
            }

            java:

              @GetMapping("pageByStatus")
              public PageResult<Order> pageByStatus(int page,int size,int ...status){
              NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
              queryBuilder.withQuery(QueryBuilders.termsQuery("status",status)).withPageable(PageRequest.of(page, size));
              SearchHits<Order> search = restTemplate.search(queryBuilder.build(), Order.class);
              List<Order> collect = search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
              return PageResult.data(collect,search.getTotalHits());
              }


              @Data
              @AllArgsConstructor
              @NoArgsConstructor
              public class PageResult<T> implements Serializable {


              private List<T> data;


              private long total;


              public static <T> PageResult<T> data(List<T> data,long count){
              return new PageResult<>(data,count);
              }


              }



              2.3 范围查询 Range

              案例:

              查询金额大于100的订单

              DSL:

                GET order_test/_search
                {
                "query": {
                "range": {
                "amount": {
                "gt": 100
                }
                }
                }
                }

                java:

                  @GetMapping("listGreaterThanAmount")
                  public List<Order> listGreaterThanAmount(int page,int size,Double amount){
                  NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
                  queryBuilder.withQuery(QueryBuilders.rangeQuery("amount").gt(amount)).withPageable(PageRequest.of(page, size));
                  SearchHits<Order> search = restTemplate.search(queryBuilder.build(), Order.class);
                  return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
                  }



                  2.4 模糊查询 Match

                  案例:

                  插叙地址中包含‘北京‘的订单

                  DSL:

                    GET order_test/_search
                    {
                    "query": {
                    "match": {
                    "address": "北京"
                    }
                    }
                    }

                    java:

                      @GetMapping("listMatchAddress")
                      public List<Order> listMatchAddress(int page,int size,String address){
                      NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
                      queryBuilder.withQuery(QueryBuilders.matchQuery("address",address)).withPageable(PageRequest.of(page, size));
                      SearchHits<Order> search = restTemplate.search(queryBuilder.build(), Order.class);
                      return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
                      }



                      2.5 嵌套查询 Boolean

                      案例:

                      查询5月份下单的待签收的订单

                      DSL:

                        GET order_test/_search
                        {
                        "query": {
                        "bool": {
                        "must": [
                        {
                        "term": {
                        "status": {
                        "value": 3
                        }
                        }
                        },
                        {
                        "range": {
                        "create_time": {
                        "gte": "2022-05-01 00:00:00",
                        "lt": "2022-06-01 00:00:00"
                        }
                        }
                        }
                        ]
                        }
                        }
                        }

                        java:

                          @GetMapping("listRangeTimeAndStatus")
                          public List<Order> listRangeTimeAndStatus(int page, int size, Date startTime,Date endTime, Integer status){
                          BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
                          boolQueryBuilder.must(QueryBuilders.termsQuery("status",status))
                          .must(QueryBuilders.rangeQuery("create_time").gte(startTime).gt(endTime));
                          NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
                          queryBuilder.withQuery(boolQueryBuilder).withPageable(PageRequest.of(page, size));
                          SearchHits<Order> search = restTemplate.search(queryBuilder.build(), Order.class);
                          return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
                          }



                          如果这里出现日期类型转换报错,需要添加转换类

                            @Component
                            public class DateConverter implements Converter<String, Date> {


                            @Override
                            public Date convert(@NonNull String source) {
                            try {
                            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(source);
                            } catch (ParseException e) {
                            try {
                            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'+08:00'").parse(source);
                            } catch (ParseException ex) {
                            try {
                            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(source);
                            } catch (ParseException ex2) {
                            ex2.printStackTrace();
                            }
                            }
                            }
                            return null;
                            }
                            }

                            2.6 json数组查询 Nested

                            案例:

                            查询购买了香蕉的订单

                            DSL:

                              GET order_test/_search
                              {
                              "query": {
                              "nested": {
                              "path": "product",
                              "query": {
                              "term": {
                              "product.name": {
                              "value": "香蕉"
                              }
                              }
                              }
                              }
                              }
                              }

                              java:

                                @GetMapping("listByProductName")
                                public List<Order> listByProductName(int page,int size,String name){
                                NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
                                queryBuilder.withQuery(QueryBuilders.nestedQuery("product",
                                QueryBuilders.termQuery("product.name",name),ScoreMode.Max))
                                .withPageable(PageRequest.of(page, size));
                                SearchHits<Order> search = restTemplate.search(queryBuilder.build(), Order.class);
                                return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
                                }



                                2.7 坐标查询 Geo

                                案例:

                                查询地址在北京20km范围内的订单

                                DSL:

                                  GET order_test/_search
                                  {
                                  "query": {
                                  "geo_distance":{
                                  "distance": "20km",
                                  "point":{
                                  "lon": 116.23128,
                                  "lat": 40.22077
                                  }
                                  }
                                  }
                                  }

                                  java:

                                     @GetMapping("listByPointAndDistance")
                                    public List<Order> listByPointAndDistance(int page, int size, Double lat,Double lon, String distance){
                                    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
                                    GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder("point");
                                    distanceQueryBuilder.point(new GeoPoint(lat,lon)).distance(distance);
                                    queryBuilder.withQuery(distanceQueryBuilder).withPageable(PageRequest.of(page, size));
                                    SearchHits<Order> search = restTemplate.search(queryBuilder.build(), Order.class);
                                    return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
                                    }



                                    3. spring-data-elasticsearch3.x版本实现

                                    我们上述的演示都是在4.2.x版本完成的,但很多公司还在使用3.x版本,两者之间实现略有区别。

                                    两个版本之间构建queryBuilder是相同的,只是ElasticsearchRestTemplate
                                    的调用接口不一样。

                                    在3.x版本下要执行查询需要调用queryXXX方法,可以看到支持多种,根据方法名我们可以知道有支持分页的、别名查询的、ID查询的、集合查询的。可以根据具体的业务选择不同的方法



                                    比如我们上述的案例:

                                    查询购买了香蕉的订单

                                    3.2.12.RELEASE版本
                                    的实现如下:

                                      @GetMapping("listByProductName")
                                      public List<Order> listByProductName(int page, int size, String name){
                                      NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
                                      queryBuilder.withQuery(QueryBuilders.nestedQuery("product",
                                      QueryBuilders.termQuery("product.name",name), ScoreMode.Max))
                                      .withPageable(PageRequest.of(page, size));
                                      AggregatedPage<Order> orders = restTemplate.queryForPage(queryBuilder.build(), Order.class);
                                      return orders.getContent();
                                      }

                                      针对复杂查询的介绍就到此为止了,只是列举了其中比较常用的,如果有你不知道怎么实现的,可以留言讨论

                                      回复 ‘复杂查询源码’ 获取文中项目源码


                                      Elastic  


                                      ~

                                      References

                                      [1]
                                       从零搭建springboot+spring data elasticsearch4.2.x环境: https://blog.csdn.net/qq_24950043/article/details/125576967

                                      文章转载自Elastic之家,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                      评论