从零到一聊 ElasticSearch 的 Java API:从简单查询到复杂优化
在用 Java 写 ElasticSearch(简称 ES)查询时,尤其是电商订单搜索这种场景,咱们经常会跟一堆 API 打交道,像 SearchRequest、SearchSourceBuilder、BoolQueryBuilder 还有 QueryBuilders 下的各种方法(matchQuery、termQuery、rangeQuery 等等)。代码里还经常嵌套好几层,先 filter 再 must 套 should,看着有点晕乎。今天咱们就从这些 API 的结构入手,先鸟瞰全局,再拆解细节,最后结合实战例子,从最朴素的查询聊到复杂优化的思路,顺便看看有哪些坑和改进空间。
一、鸟瞰全局:ES Java API 的继承结构
先搞清楚这些 API 的关系,省得用的时候稀里糊涂。ES 的 Java 客户端(High-Level REST Client)提供了一套对象化的查询构建方式,大致可以分成几层:
-
SearchRequest- 角色:最外层的请求对象,相当于整个查询的"信封"。
- 功能 :指定索引(比如
orders)、超时、路由等,把查询内容装进去发给 ES。 - 关系 :里面嵌着
SearchSourceBuilder,是顶层容器。
-
SearchSourceBuilder- 角色:查询的核心配置器,负责拼装查询、排序、分页等。
- 功能 :可以塞
query(BoolQueryBuilder)、sort(排序字段)、from/size(分页)等。 - 关系 :它是
SearchRequest的"灵魂",直接决定查询逻辑。
-
QueryBuilder- 角色 :查询条件的抽象基类,所有具体查询(
BoolQueryBuilder、MatchQueryBuilder等)的爹。 - 功能 :定义了查询的基本行为,比如
toXContent(转成 JSON)。 - 关系 :是个接口,具体实现有
BoolQueryBuilder、TermQueryBuilder等。
- 角色 :查询条件的抽象基类,所有具体查询(
-
BoolQueryBuilder- 角色:布尔查询的构建器,负责组合多个子查询。
- 功能 :支持
must(且)、should(或)、filter(无评分过滤)、mustNot(非)。 - 关系 :继承自
QueryBuilder,可以嵌套其他QueryBuilder。
-
QueryBuilders- 角色 :工具类,提供静态方法生成各种
QueryBuilder实例。 - 功能 :比如
QueryBuilders.termQuery()、QueryBuilders.matchQuery(),方便快速构造查询。 - 关系 :不继承谁,就是个"工厂",输出具体的
QueryBuilder子类。
- 角色 :工具类,提供静态方法生成各种
从代码看,典型流程是:
- 用
SearchRequest包住整个请求。 - 用
SearchSourceBuilder配置查询和排序。 - 用
BoolQueryBuilder组装条件,里面塞filter、must、should,这些条件靠QueryBuilders生成。
比如代码中有描述:
SearchSourceBuilder调用sort和query。BoolQueryBuilder先加filter(termQuery、rangeQuery),再加must(嵌套should的matchQuery)。
二、拆解单个概念
1. must 和 should 平级的 API
在 BoolQueryBuilder 里,跟 must 和 should 平级的有:
filter:无评分过滤,条件必须满足。mustNot:排除条件,匹配的文档不返回。- 区别 :
must:必须满足,有评分,影响排序。should:可选满足,有评分,默认至少匹配一个(可调minimum_should_match)。filter:必须满足,无评分,纯筛选。mustNot:必须不满足,无评分,纯排除。
- 应用场景 :
must:核心条件,比如"订单状态必须是已支付"。should:可选条件,比如"店铺名含'小米'或'华为'"。filter:高频筛选,比如"店铺 ID = 123"。mustNot:黑名单,比如"排除已取消订单"。
2. termQuery 和 rangeQuery 平级的 API
QueryBuilders 提供了很多具体查询,跟 termQuery、rangeQuery 平级的有:
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 框范围,再 must 嵌 should 找关键词:
- 优点:支持模糊搜索,用户能输入"小米"或"张三"。
- 坑:
- 得分被忽略 :排序只看
createTime,matchQuery的相关性没用上。 - 宽松过度 :没设
minimum_should_match,可能返回太多无关结果。 - 性能隐患 :数据量大时,
matchQuery全量算分有点吃力。
- 得分被忽略 :排序只看
4. 逼近主流:多维度优化
主流方案会更复杂,咱们往这几个方向靠:
-
多因子排序:结合时间和相关性得分:
javabuilder.sort(SortBuilders.scoreSort().order(SortOrder.DESC)) .sort("createTime", SortOrder.DESC); shouldQuery.minimumShouldMatch(1); // 至少匹配一个关键词让"小米"匹配度高的订单排前面,时间作为次级排序。
-
动态权重 :根据场景调
matchQuery的boost:javashouldQuery.should(QueryBuilders.matchQuery("shopName", "小米").boost(2.0f)); shouldQuery.should(QueryBuilders.matchQuery("consignee", "张三").boost(1.0f));店铺名更重要,权重高点。
-
嵌套优化 :处理数组字段(像
orderItems):javaBoolQueryBuilder nestedQuery = QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("orderItems.spuName", "手机")); shouldQuery.should(QueryBuilders.nestedQuery("orderItems", nestedQuery, ScoreMode.Avg));精确查订单项,嵌套得分更合理。
-
性能提升 :
filter加缓存,matchQuery加分页:javabuilder.from(0).size(20); // 分页
5. 实战案例
假设有 3 个订单:
- 订单 A:
shopId=123, shopName="小米之家", consignee="张三", createTime="2025-03-10", status=1 - 订单 B:
shopId=123, shopName="华为店", consignee="李四", createTime="2025-03-09", status=1 - 订单 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>QueryBuilder(QueryBuilders生成)。 - 布尔组合 :
must/should/filter/mustNot,控制逻辑和得分。 - 查询类型 :
term/range(精确) vsmatch/prefix(模糊),匹配字段和场景。 - 演进路径:从单一排序到过滤+模糊,再到多因子+动态优化,逐步逼近主流。