后端必看!ES 搜索从入门到装 X,这篇帮你打通任督二脉

Java教程 2025-09-07

一、先搞懂: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 教程不迷路