
0. 引言
因为es非关系型数据库的特性,我们常常需要在实际业务中实现复杂查询,从而来查询到我们想要的数据。
很多同学刚接触java client不知道如何实现各类复杂查询操作。今天我们就来讲讲一些常见的复杂查询如何实现
1. 运行环境
下文演示基于如下环境
spring-data-elasticsearch 4.2.10elasticsearch 7.13.0java 1.8spring-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@NoArgsConstructorpublic 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());}

如果这里出现日期类型转换报错,需要添加转换类
@Componentpublic class DateConverter implements Converter<String, Date> {@Overridepublic 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




