ES-Java:一网打尽SearchRequest/SearchSourceBuilder/BoolQueryBuilder/QueryBuilders

从零到一聊 ElasticSearch 的 Java API:从简单查询到复杂优化

在用 Java 写 ElasticSearch(简称 ES)查询时,尤其是电商订单搜索这种场景,咱们经常会跟一堆 API 打交道,像 SearchRequestSearchSourceBuilderBoolQueryBuilder 还有 QueryBuilders 下的各种方法(matchQuerytermQueryrangeQuery 等等)。代码里还经常嵌套好几层,先 filtermustshould,看着有点晕乎。今天咱们就从这些 API 的结构入手,先鸟瞰全局,再拆解细节,最后结合实战例子,从最朴素的查询聊到复杂优化的思路,顺便看看有哪些坑和改进空间。


一、鸟瞰全局:ES Java API 的继承结构

先搞清楚这些 API 的关系,省得用的时候稀里糊涂。ES 的 Java 客户端(High-Level REST Client)提供了一套对象化的查询构建方式,大致可以分成几层:

  1. SearchRequest

    • 角色:最外层的请求对象,相当于整个查询的"信封"。
    • 功能 :指定索引(比如 orders)、超时、路由等,把查询内容装进去发给 ES。
    • 关系 :里面嵌着 SearchSourceBuilder,是顶层容器。
  2. SearchSourceBuilder

    • 角色:查询的核心配置器,负责拼装查询、排序、分页等。
    • 功能 :可以塞 queryBoolQueryBuilder)、sort(排序字段)、from/size(分页)等。
    • 关系 :它是 SearchRequest 的"灵魂",直接决定查询逻辑。
  3. QueryBuilder

    • 角色 :查询条件的抽象基类,所有具体查询(BoolQueryBuilderMatchQueryBuilder 等)的爹。
    • 功能 :定义了查询的基本行为,比如 toXContent(转成 JSON)。
    • 关系 :是个接口,具体实现有 BoolQueryBuilderTermQueryBuilder 等。
  4. BoolQueryBuilder

    • 角色:布尔查询的构建器,负责组合多个子查询。
    • 功能 :支持 must(且)、should(或)、filter(无评分过滤)、mustNot(非)。
    • 关系 :继承自 QueryBuilder,可以嵌套其他 QueryBuilder
  5. QueryBuilders

    • 角色 :工具类,提供静态方法生成各种 QueryBuilder 实例。
    • 功能 :比如 QueryBuilders.termQuery()QueryBuilders.matchQuery(),方便快速构造查询。
    • 关系 :不继承谁,就是个"工厂",输出具体的 QueryBuilder 子类。

从代码看,典型流程是:

  • SearchRequest 包住整个请求。
  • SearchSourceBuilder 配置查询和排序。
  • BoolQueryBuilder 组装条件,里面塞 filtermustshould,这些条件靠 QueryBuilders 生成。

比如代码中有描述:

  • SearchSourceBuilder 调用 sortquery
  • BoolQueryBuilder 先加 filtertermQueryrangeQuery),再加 must(嵌套 shouldmatchQuery)。

二、拆解单个概念

1. mustshould 平级的 API

BoolQueryBuilder 里,跟 mustshould 平级的有:

  • filter:无评分过滤,条件必须满足。
  • mustNot:排除条件,匹配的文档不返回。
  • 区别
    • must:必须满足,有评分,影响排序。
    • should:可选满足,有评分,默认至少匹配一个(可调 minimum_should_match)。
    • filter:必须满足,无评分,纯筛选。
    • mustNot:必须不满足,无评分,纯排除。
  • 应用场景
    • must:核心条件,比如"订单状态必须是已支付"。
    • should:可选条件,比如"店铺名含'小米'或'华为'"。
    • filter:高频筛选,比如"店铺 ID = 123"。
    • mustNot:黑名单,比如"排除已取消订单"。
2. termQueryrangeQuery 平级的 API

QueryBuilders 提供了很多具体查询,跟 termQueryrangeQuery 平级的有:

  • matchQuery:模糊匹配,分词后查。
  • prefixQuery:前缀匹配,比如"shopName"以"mi"开头。
  • wildcardQuery:通配符匹配,比如"shop*"。
  • existsQuery:字段是否存在。
  • 区别
    • termQuery:精确匹配,不分词,适合 keyword 类型。
    • rangeQuery:范围匹配,适合数值或时间。
    • matchQuery:模糊匹配,分词,适合 text 类型。
    • prefixQuery:前缀匹配,不分词,适合快速补全。
    • wildcardQuery:通配符匹配,灵活但慢。
    • existsQuery:检查字段非空。
  • 应用场景
    • termQuery:查具体 ID 或状态。
    • rangeQuery:查时间段或价格范围。
    • matchQuery:搜店铺名或商品名。
    • prefixQuery:实时输入提示。
    • wildcardQuery:复杂模糊搜索。
    • existsQuery:过滤缺失数据的文档。

三、从朴素到复杂:实战演进

1. 朴素策略:简单字段排序

假设刚开始做订单搜索,最直观的办法是按时间排序:

java 复制代码
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.sort("createTime", SortOrder.DESC);
SearchRequest request = new SearchRequest("orders").source(builder);

这简单粗暴,最近的订单排前面。但问题马上来了:

  • 单一维度:只看时间,用户想搜"小米"店铺的订单咋办?
  • 无过滤:已取消的订单也混进来,用户不想要。
  • 效率低:全量排序,没筛选,数据量大时卡。
2. 进阶:加过滤条件

改进一下,加点筛选:

java 复制代码
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.filter(QueryBuilders.termQuery("shopId", 123));
boolQuery.filter(QueryBuilders.termQuery("status", 1));
SearchSourceBuilder builder = new SearchSourceBuilder()
    .query(boolQuery)
    .sort("createTime", SortOrder.DESC);

filter 限定店铺和状态,效果好点了:

  • 优点:结果集小了,只剩店铺 123 的已支付订单。
  • 坑:还是没法搜"小米"或"张三",用户体验差。
3. 再进化:引入模糊搜索

再加 should 支持关键词:

java 复制代码
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.filter(QueryBuilders.termQuery("shopId", 123));
boolQuery.filter(QueryBuilders.termQuery("status", 1));

BoolQueryBuilder shouldQuery = QueryBuilders.boolQuery();
shouldQuery.should(QueryBuilders.matchQuery("shopName", "小米"));
shouldQuery.should(QueryBuilders.matchQuery("consignee", "张三"));
boolQuery.must(shouldQuery);

SearchSourceBuilder builder = new SearchSourceBuilder()
    .query(boolQuery)
    .sort("createTime", SortOrder.DESC);

filter 框范围,再 mustshould 找关键词:

  • 优点:支持模糊搜索,用户能输入"小米"或"张三"。
  • 坑:
    • 得分被忽略 :排序只看 createTimematchQuery 的相关性没用上。
    • 宽松过度 :没设 minimum_should_match,可能返回太多无关结果。
    • 性能隐患 :数据量大时,matchQuery 全量算分有点吃力。
4. 逼近主流:多维度优化

主流方案会更复杂,咱们往这几个方向靠:

  • 多因子排序:结合时间和相关性得分:

    java 复制代码
    builder.sort(SortBuilders.scoreSort().order(SortOrder.DESC))
         .sort("createTime", SortOrder.DESC);
    shouldQuery.minimumShouldMatch(1); // 至少匹配一个关键词

    让"小米"匹配度高的订单排前面,时间作为次级排序。

  • 动态权重 :根据场景调 matchQueryboost

    java 复制代码
    shouldQuery.should(QueryBuilders.matchQuery("shopName", "小米").boost(2.0f));
    shouldQuery.should(QueryBuilders.matchQuery("consignee", "张三").boost(1.0f));

    店铺名更重要,权重高点。

  • 嵌套优化 :处理数组字段(像 orderItems):

    java 复制代码
    BoolQueryBuilder nestedQuery = QueryBuilders.boolQuery()
        .must(QueryBuilders.matchQuery("orderItems.spuName", "手机"));
    shouldQuery.should(QueryBuilders.nestedQuery("orderItems", nestedQuery, ScoreMode.Avg));

    精确查订单项,嵌套得分更合理。

  • 性能提升filter 加缓存,matchQuery 加分页:

    java 复制代码
    builder.from(0).size(20); // 分页
5. 实战案例

假设有 3 个订单:

  1. 订单 A:shopId=123, shopName="小米之家", consignee="张三", createTime="2025-03-10", status=1
  2. 订单 B:shopId=123, shopName="华为店", consignee="李四", createTime="2025-03-09", status=1
  3. 订单 C:shopId=456, shopName="小米专卖", consignee="王五", createTime="2025-03-08", status=2

查询:shopId=123, status=1,关键词"小米"或"张三"。

  • 朴素版:只排序,全返回,C 混进来。
  • 进阶版filter 后剩 A 和 B,should 找"小米"或"张三",A 匹配两个(得分高),B 不匹配。
  • 优化版:A 排前面(得分 3.5,时间新),B 靠后(得分 0),结果精准且有序。

四、总结:结构化的知识体系

  • API 层级SearchRequest > SearchSourceBuilder > BoolQueryBuilder > QueryBuilderQueryBuilders 生成)。
  • 布尔组合must/should/filter/mustNot,控制逻辑和得分。
  • 查询类型term/range(精确) vs match/prefix(模糊),匹配字段和场景。
  • 演进路径:从单一排序到过滤+模糊,再到多因子+动态优化,逐步逼近主流。
相关推荐
Seven972 分钟前
【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
java·后端·设计模式
梦醒沉醉20 分钟前
Scala的初步使用
开发语言·后端·scala
重庆穿山甲23 分钟前
建造者模式实战指南:场景案例+实战代码,新手也能快速上手
后端
小安同学iter1 小时前
Spring(七)AOP-代理模式
java·后端·spring
Goboy1 小时前
老婆问我:“大模型的 Token 究竟是个啥?”
后端·程序员·架构
子洋2 小时前
Chroma+LangChain:让AI联网回答更精准
前端·人工智能·后端
追逐时光者2 小时前
基于 .NET Blazor 开源、低代码、易扩展的插件开发框架
后端·.net
MZWeiei5 小时前
Scala:解构声明(用例子通俗易懂)
开发语言·后端·scala
woniu_maggie8 小时前
SAP DOI EXCEL&宏的使用
后端·excel
二两小咸鱼儿9 小时前
Java Demo - JUnit :Unit Test(Assert Methods)
java·后端·junit