一、先搞懂:ES 搜索的 “基本兵种”—— 叶子查询 vs 复合查询
咱们先别急着写代码,先搞懂 ES 搜索的 “底层逻辑”。你可以把 ES 的查询想象成 “军队作战”:
- 叶子查询:就是 “单打选手”,只能针对单个字段做查询(比如查用户 ID、查价格范围),是最基础的作战单元;
- 复合查询:就是 “战队指挥官”,能把多个叶子查询组合起来(比如 “价格 > 100 且销量 > 1000”),还能控制每个查询的权重。
简单说:叶子查询是 “零件”,复合查询是 “组装好的机器”,咱们先从 “零件” 学起!
二、DSL 查询:写给 ES 看的 “搜索指令”(附实战案例)
DSL 是 ES 的 “官方语言”,用 JSON 格式写查询条件,咱们按 “精确查询” 和 “全文检索” 两类来拆,每个都给你贴能直接跑的代码!
1. 精确查询:我要的就是 “精准打击”
精确查询适合查结构化数据(比如 ID、手机号、日期),不会对关键词分词,输入啥就查啥,主打一个 “不跑偏”。
(1)term 查询:单个字段的 “精准狙击”
比如我要查 “用户 ID=1001” 的记录,用 term 就对了:
{
"query": {
"term": {
"user_id": { // 要查询的字段
"value": "1001" // 要匹配的值(注意:数字也能传字符串,ES会自动转换)
}
}
}
}
? 避坑提醒:term 不支持多值匹配!如果想查 “user_id=1001 或 1002”,得用terms(多了个 s),把 value 改成数组就行。
(2)range 查询:区间内的 “一网打尽”
比如查 “2024 年 1 月 1 日到 2024 年 12 月 31 日的订单”,或者 “价格在 100-500 之间的商品”,range 就是你的菜:
{
"query": {
"range": {
"order_time": { // 日期字段
"gte": "2024-01-01", // 大于等于(gte = greater than or equal)
"lte": "2024-12-31", // 小于等于(lte = less than or equal)
"format": "yyyy-MM-dd" // 日期格式(可选,ES能自动识别常见格式)
},
"product_price": { // 数值字段
"gt": 100, // 大于(gt = greater than)
"lt": 500 // 小于(lt = less than)
}
}
}
}
? 小技巧:range 还能查字符串区间(比如 “用户名从 a 到 z”),但实战中很少用,记数值和日期就够了。
2. 全文检索:模糊匹配也能 “命中目标”
全文检索适合查非结构化数据(比如商品描述、文章内容),会对关键词分词后再匹配(比如查 “手机”,会匹配 “智能手机”“手机壳”),主打一个 “灵活”。
(1)match 查询:单个字段的 “模糊搜索”
比如我要在 “商品描述” 里搜包含 “轻薄” 的商品,用 match 就行:
{
"query": {
"match": {
"product_desc": "轻薄" // 字段名: 搜索关键词(ES会自动分词)
}
}
}
? 进阶玩法:如果想查 “轻薄且长续航”,可以加operator: "and"(默认是 or):
{
"query": {
"match": {
"product_desc": {
"query": "轻薄 长续航",
"operator": "and" // 必须同时包含两个关键词
}
}
}
}
(2)multi_match 查询:多字段的 “批量搜索”
如果想同时在 “商品名称” 和 “商品描述” 里搜 “轻薄”,总不能写两个 match 吧?用 multi_match 一步搞定:
{
"query": {
"multi_match": {
"query": "轻薄", // 搜索关键词
"fields": ["product_name", "product_desc"] // 要查询的多个字段
}
}
}
? 权重调整:如果想让 “商品名称” 的匹配优先级更高,可以加^权重值(比如product_name^3,表示权重是其他字段的 3 倍)。
三、Java 查询:后端 er 的 “实战代码库”
光会写 DSL 还不够,咱们后端 er 最终要在代码里实现!这里用 ES 官方推荐的RestHighLevelClient,每个查询都给你贴完整代码(复制到项目里改改字段就能用)。
1. 精确查询:Java 版 “精准打击”
(1)term 查询(查用户 ID)
// 1. 创建term查询条件
TermQueryBuilder termQuery = QueryBuilders.termQuery("user_id", "1001");
// 2. 构建搜索请求
SearchRequest searchRequest = new SearchRequest("user_index"); // 索引名
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(termQuery);
searchRequest.source(sourceBuilder);
// 3. 执行请求并获取结果
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 4. 解析结果(这里简化处理,实际项目要遍历hits)
SearchHits hits = response.getHits();
System.out.println("匹配到的文档数:" + hits.getTotalHits().value);
(2)range 查询(查价格区间商品)
// 1. 创建range查询条件(价格100-500)
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("product_price")
.gt(100) // 大于100
.lt(500); // 小于500
// 2. 后续步骤和term一样:构建请求→执行→解析结果
SearchRequest searchRequest = new SearchRequest("product_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(rangeQuery);
searchRequest.source(sourceBuilder);
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
2. 全文检索:Java 版 “模糊匹配”
(1)match 查询(搜商品描述)
// 1. 创建match查询条件(搜“轻薄”,且必须包含)
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("product_desc", "轻薄")
.operator(Operator.AND); // 关键词必须同时包含
// 2. 构建请求(省略重复代码,和上面一样)
SearchRequest searchRequest = new SearchRequest("product_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(matchQuery);
searchRequest.source(sourceBuilder);
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
(2)multi_match 查询(多字段搜)
// 1. 创建multi_match查询条件(同时搜名称和描述,名称权重3倍)
MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery("轻薄")
.field("product_name", 3.0f) // 商品名称,权重3.0
.field("product_desc"); // 商品描述,默认权重1.0
// 2. 后续步骤同上,省略重复代码
四、进阶技能:排序、分页、高亮 —— 让搜索结果更 “好用”
光查出结果还不够,用户还需要 “按价格排序”“分页看结果”“关键词标红”,这些咱们也一起搞定!
1. 排序:DSL + Java 实现 “按规则排队”
DSL 版(按价格降序,销量升序)
{
"query": { "match_all": {} }, // 查所有数据(也可以换其他查询)
"sort": [
{ "product_price": { "order": "desc" } }, // 价格降序
{ "product_sales": { "order": "asc" } } // 销量升序
]
}
Java 版(对应上面的 DSL)
// 1. 构建查询(这里用match_all举例,可替换成其他查询)
MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
// 2. 添加排序条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(matchAllQuery);
// 价格降序
sourceBuilder.sort("product_price", SortOrder.DESC);
// 销量升序
sourceBuilder.sort("product_sales", SortOrder.ASC);
// 3. 构建请求并执行(省略重复代码)
2. 分页:DSL + Java 实现 “分批查看”
ES 分页有两种方式:from+size(适合小分页)和scroll(适合大数据量),咱们先讲最常用的from+size。
DSL 版(查第 2 页,每页 10 条)
{
"query": { "match_all": {} },
"from": 10, // 跳过前10条(from = (页码-1)*size)
"size": 10 // 每页显示10条
}
Java 版(对应上面的 DSL)
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchAllQuery());
sourceBuilder.from(10); // 跳过前10条
sourceBuilder.size(10); // 每页10条
? 避坑提醒:from+size不适合超过 1 万条的分页!如果要查 “第 1001 页”(from=10000),ES 会先把前 10000 条数据加载到内存,性能很差。这种情况建议用scroll或search_after(后续博客再讲,关注我不迷路https://images.downcodes.com/uploads/20250906/img_68bc4f8b9065a30.png)。
3. 结果高亮:让关键词 “闪闪发光”
用户搜 “轻薄”,结果里的 “轻薄” 两个字标红,体验直接拉满!这就是高亮的作用。
DSL 版(高亮 “商品描述” 中的关键词)
{
"query": {
"match": { "product_desc": "轻薄" }
},
"highlight": {
"fields": {
"product_desc": {} // 要高亮的字段(默认用标签包裹关键词)
},
"pre_tags": "", // 自定义前缀标签(比如红色span)
"post_tags": "" // 自定义后缀标签
}
}
Java 版(对应上面的 DSL)
// 1. 构建match查询
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("product_desc", "轻薄");
// 2. 构建高亮条件
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 高亮字段
HighlightBuilder.Field highlightField = new HighlightBuilder.Field("product_desc");
// 自定义高亮标签
highlightField.preTags("");
highlightField.postTags("");
highlightBuilder.field(highlightField);
// 3. 把查询和高亮添加到请求
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(matchQuery);
sourceBuilder.highlighter(highlightBuilder);
// 4. 执行请求并解析高亮结果
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 遍历hits,获取高亮内容
for (SearchHit hit : response.getHits()) {
// 原始数据
MapObject> sourceMap = hit.getSourceAsMap();
// 高亮数据(key是字段名,value是高亮后的内容数组)
Map highlightFields = hit.getHighlightFields();
HighlightField productDescHighlight = highlightFields.get("product_desc");
if (productDescHighlight != null) {
// 获取第一个高亮片段(通常只有一个)
String highlightedDesc = productDescHighlight.getFragments()[0].toString();
// 用高亮内容替换原始内容(返回给前端)
sourceMap.put("product_desc", highlightedDesc);
}
}
五、总结:ES 搜索 “通关 checklist”
看完这篇,你应该能搞定这些事了:
- ✅ 分清叶子查询(单打)和复合查询(组队);
- ✅ 用 DSL 写精确查询(term/range)和全文检索(match/multi_match);
- ✅ 用 Java 实现上述所有查询;
- ✅ 给结果加排序、分页、高亮,提升用户体验。
如果还有不会的地方,评论区扣 “ES 救我”,我后续再出进阶篇(比如复合查询、过滤查询、聚合分析)!觉得有用的话,点赞收藏关注三连,下次找 ES 教程不迷路