从零到一聊 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
(模糊),匹配字段和场景。 - 演进路径:从单一排序到过滤+模糊,再到多因子+动态优化,逐步逼近主流。