spring data elasticsearch:复杂查询指南

本文涉及的产品
Elasticsearch Serverless通用抵扣包,测试体验金 200元
简介: 因为es非关系型数据库的特性,我们常常需要在实际业务中实现复杂查询,从而来查询到我们想要的数据。很多同学刚接触java client不知道如何实现各类复杂查询操作。今天我们就来讲讲一些常见的复杂查询如何实现

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"
          }
        }
      }
    }
  }
}

测试数据,供大家跟练

POST order_test/_bulk
{"index":{}}
{"status":0,"no":"DD202205280001","create_time":"2022-05-01 12:00:00","amount":100.0,"creator":"张三","address":"北京市上海路11号","point":{"lon":116.23128,"lat":40.22077},"product":[{"id":"1","name":"苹果","price":20.0,"quantity":5}]}
{"index":{}}
{"status":0,"no":"DD202205280002","create_time":"2022-05-01 12:00:00","amount":100.0,"creator":"李四","address":"北京市市源路5号","point":{"lon":116.23128,"lat":40.22077},"product":[{"id":"2","name":"香蕉","price":20.0,"quantity":5}]}
{"index":{}}
{"status":1,"no":"DD202205280003","create_time":"2022-05-02 12:00:00","amount":100.0,"creator":"张三","address":"贵阳市北京路1号","point":{"lon":106.62298,"lat":26.67865},"product":[{"id":"2","name":"香蕉","price":20.0,"quantity":5}]}
{"index":{}}
{"status":2,"no":"DD202205280004","create_time":"2022-05-01 12:00:00","amount":150.0,"creator":"王二","address":"贵阳市太平街1号","point":{"lon":106.62298,"lat":26.67865},"product":[{"id":"1","name":"苹果","price":30.0,"quantity":5}]}
{"index":{}}
{"status":2,"no":"DD202205280005","create_time":"2022-05-03 12:00:00","amount":100.0,"creator":"55555","address":"贵阳市北京路12号","point":{"lon":106.62298,"lat":26.67865},"product":[{"id":"2","name":"香蕉","price":20.0,"quantity":5}]}
{"index":{}}
{"status":3,"no":"DD202205280006","create_time":"2022-05-04 12:00:00","amount":150.0,"creator":"李四","address":"上海市宝山路1号","point":{"lon":121.48941,"lat":31.40527},"product":[{"id":"3","name":"榴莲","price":150.0,"quantity":1}]}
{"index":{}}
{"status":4,"no":"DD202205280007","create_time":"2022-05-04 12:00:00","amount":100.0,"creator":"张三","address":"贵阳市观山湖区12号","point":{"lon":106.62298,"lat":26.67865},"product":[{"id":"2","name":"香蕉","price":20.0,"quantity":5}]}
{"index":{}}
{"status":3,"no":"DD202205280008","create_time":"2022-05-01 12:00:00","amount":200.0,"creator":"王二","address":"上海市宝山路11号","point":{"lon":121.48941,"lat":31.40527},"product":[{"id":"1","name":"苹果","price":40.0,"quantity":5}]}
{"index":{}}
{"status":4,"no":"DD202205280009","create_time":"2022-05-03 12:00:00","amount":100.0,"creator":"55555","address":"贵阳市北京路21号","point":{"lon":106.62298,"lat":26.67865},"product":[{"id":"2","name":"香蕉","price":20.0,"quantity":5}]}

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

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

实体类

@Data
@Document(indexName = "order_test")
@Setting(replicas = 0)
public class Order {

    @Id
    private String id;

    // 订单状态 0未付款 1未发货 2运输中 3待签收 4已签收
    @Field(type = FieldType.Integer, name = "status")
    private Integer status;

    @Field(type = FieldType.Keyword, name = "no")
    private String no;

    @Field(type = FieldType.Date, name = "create_time", pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    @Field(type = FieldType.Double, name = "amount")
    private Double amount;

    @Field(type = FieldType.Keyword, name = "creator")
    private String creator;

    @GeoPointField
    @Field(name = "point")
    private GeoPoint point;

    @Field(type = FieldType.Text, name = "address", analyzer = "ik_max_word")
    private String address;
    
    @Field(type = FieldType.Nested, name = "creator")
    private List<Product> product;

}

@Data
public class Product implements Serializable {

    @Field(type = FieldType.Long, name = "id")
    private Long id;

    @Field(type = FieldType.Keyword, name = "name")
    private String name;

    @Field(type = FieldType.Double, name = "price")
    private Double price;

    @Field(type = FieldType.Integer, name = "quantity")
    private Double quantity;

}

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();
    }

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

关注公众号Elasticsearch之家,回复 ‘复杂查询源码’ 获取文中项目源码

在这里插入图片描述

相关实践学习
以电商场景为例搭建AI语义搜索应用
本实验旨在通过阿里云Elasticsearch结合阿里云搜索开发工作台AI模型服务,构建一个高效、精准的语义搜索系统,模拟电商场景,深入理解AI搜索技术原理并掌握其实现过程。
ElasticSearch 最新快速入门教程
本课程由千锋教育提供。全文搜索的需求非常大。而开源的解决办法Elasricsearch(Elastic)就是一个非常好的工具。目前是全文搜索引擎的首选。本系列教程由浅入深讲解了在CentOS7系统下如何搭建ElasticSearch,如何使用Kibana实现各种方式的搜索并详细分析了搜索的原理,最后讲解了在Java应用中如何集成ElasticSearch并实现搜索。 &nbsp;
目录
相关文章
|
5月前
|
NoSQL 安全 Java
深入理解 RedisConnectionFactory:Spring Data Redis 的核心组件
在 Spring Data Redis 中,`RedisConnectionFactory` 是核心组件,负责创建和管理与 Redis 的连接。它支持单机、集群及哨兵等多种模式,为上层组件(如 `RedisTemplate`)提供连接抽象。Spring 提供了 Lettuce 和 Jedis 两种主要实现,其中 Lettuce 因其线程安全和高性能特性被广泛推荐。通过手动配置或 Spring Boot 自动化配置,开发者可轻松集成 Redis,提升应用性能与扩展性。本文深入解析其作用、实现方式及常见问题解决方法,助你高效使用 Redis。
504 4
|
2月前
|
NoSQL Java Redis
Redis基本数据类型及Spring Data Redis应用
Redis 是开源高性能键值对数据库,支持 String、Hash、List、Set、Sorted Set 等数据结构,适用于缓存、消息队列、排行榜等场景。具备高性能、原子操作及丰富功能,是分布式系统核心组件。
293 2
|
4月前
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
161 32
|
3月前
|
SQL Java 数据库
解决Java Spring Boot应用中MyBatis-Plus查询问题的策略。
保持技能更新是侦探的重要素质。定期回顾最佳实践和新技术。比如,定期查看MyBatis-Plus的更新和社区的最佳做法,这样才能不断提升查询效率和性能。
137 1
|
6月前
|
数据采集 JSON 数据挖掘
Elasticsearch 的DSL查询,聚合查询与多维度数据统计
Elasticsearch的DSL查询与聚合查询提供了强大的数据检索和统计分析能力。通过合理构建DSL查询,用户可以高效地搜索数据,并使用聚合查询对数据进行多维度统计分析。在实际应用中,灵活运用这些工具不仅能提高查询效率,还能为数据分析提供深入洞察。理解并掌握这些技术,将显著提升在大数据场景中的分析和处理能力。
257 20
|
5月前
|
SQL Java 编译器
深入理解 Spring Data JPA 的导入与使用:以 UserRepository为例
本文深入解析了 Spring Data JPA 中 `UserRepository` 的导入与使用。通过示例代码,详细说明了为何需要导入 `User` 实体类、`JpaRepository` 接口及 `@Repository` 注解。这些导入语句分别用于定义操作实体、提供数据库交互方法和标识数据访问组件。文章还探讨了未导入时的编译问题,并展示了实际应用场景,如用户保存、查询与删除操作。合理使用导入语句,可让代码更简洁高效,充分发挥 Spring Data JPA 的优势。
310 0
|
8月前
|
存储 NoSQL Java
使用Java和Spring Data构建数据访问层
本文介绍了如何使用 Java 和 Spring Data 构建数据访问层的完整过程。通过创建实体类、存储库接口、服务类和控制器类,实现了对数据库的基本操作。这种方法不仅简化了数据访问层的开发,还提高了代码的可维护性和可读性。通过合理使用 Spring Data 提供的功能,可以大幅提升开发效率。
177 21
|
9月前
|
SQL Java 数据库连接
spring和Mybatis的各种查询
Spring 和 MyBatis 的结合使得数据访问层的开发变得更加简洁和高效。通过以上各种查询操作的详细讲解,我们可以看到 MyBatis 在处理简单查询、条件查询、分页查询、联合查询和动态 SQL 查询方面的强大功能。熟练掌握这些操作,可以极大提升开发效率和代码质量。
370 3
|
11月前
|
存储 Java API
如何使用 Java 记录简化 Spring Data 中的数据实体
如何使用 Java 记录简化 Spring Data 中的数据实体
109 9
|
11月前
|
存储 JSON 监控
大数据-167 ELK Elasticsearch 详细介绍 特点 分片 查询
大数据-167 ELK Elasticsearch 详细介绍 特点 分片 查询
388 4