Elasticsearch查询实战:DSL与JavaRestClient一学就会

简介: Elasticsearch查询离不开DSL语法和JavaRestClient:match、term、bool、分页、聚合一次讲透,看完就能写代码。

大家好,我是晚安code。

Elasticsearch查询从头到尾就一套逻辑,DSL 是它的 JSON 面孔,JavaRestClient 是它的 Java 面孔,同一个查询模型,两种写法而已。这篇就把这层窗户纸捅破,先学会 DSL 语法,再看它怎么翻译成 Java 代码。点个收藏,我们开始。

一、Elasticsearch查询的 DSL 骨架:先把 JSON 看懂

ES 的查询只有一个入口,就是 DSL——不管你用官方客户端还是第三方工具,最后递到集群面前的都是这份 JSON。

DSL(Domain Specific Language):Elasticsearch 的领域查询语言,用 JSON 结构表达查询条件。你可以把它理解成「用 JSON 写的 SQL」。

只按 id 查 GET /books/_doc/1 远远不够。业务里的搜索往往是「书名含某个词、价格在某区间、出版社是某家」这种组合条件,这时候就得走 _search 接口,把条件塞进 query 对象。DSL 查询在结构上分两大类,先认清这个分类,后面看任何查询都不慌:

叶子查询(Leaf Query):在单个字段上查特定值的查询,是 ES 查询的最小单元,负责「一个字段、一个条件」。

复合查询(Compound Query):用逻辑运算把多个叶子查询组合起来,或者改变查询的算分方式,负责「把条件捏在一起」。

Elasticsearch查询的DSL查询分类结构图,叶子查询与复合查询两大分支

这张 ES 查询家族谱。叶子查询里日常最常用的是三种:全文检索(match、multi_match)、精确查询(term、range)、地理查询(geo_distance 这类,做「附近的书店」功能才用得上)。复合查询这边,九成场景都在 bool 上打转,另一小类是 function_score、dis_max 这种调整算分的,平时用得少,知道存在就行。

二、叶子查询:match 和 term 什么时候用

match 走分词、term 走精确,这是全文检索和精确查询的分水岭,字段类型选错就是白查。

全文检索这一卦的核心就是 match 和 multi_match。它俩都会先对用户输入做分词,再去倒排索引里匹配词条,区别只在一个字段还是多个字段:

GET /books/_search
{
   
  "query": {
   
    "match": {
   
      "name": "程序员修炼之道"
    }
  }
}

查「程序员修炼之道」,match 会把它拆成「程序员」「修炼」「之道」等词条去索引里匹配,命中越多分数越高,排名越靠前。multi_match 是同样的逻辑,只是允许一次查多个字段——比如书名加简介一起搜。注意一点:参与查询的字段越多,性能越差,够用就行,别把整个 mapping 都填进去。

精确查询这卦是 term 和 range。它俩都不分词,拿你给的词条直接和字段值做比对:

GET /books/_search
{
   
  "query": {
   
    "term": {
   
      "publisher": {
   
        "value": "人民邮电出版社"
      }
    }
  }
}
GET /books/_search
{
   
  "query": {
   
    "range": {
   
      "price": {
   
        "gte": 30,
        "lte": 80
      }
    }
  }
}

term 适合查 keyword、数值、日期、布尔这些「整体才有意义」的字段——出版社名、ISBN、是否上架都算。range 就一句话:给字段画个区间,价格、库存、评分都能用。

当初我拿 term 去查书名(text 类型),结果一本都查不出来,盯着空结果看了半天才反应过来:书名存进索引时早被分词拆开了,term 拿整句去比,哪比得上。

可能有人会问:match 和 term 到底怎么选?

一句话:用户输入是「一段话」,用 match(要分词去搜);字段是 keyword/数值/日期/布尔这类定死的值,用 term。反过来用——用 term 搜用户输入、用 match 搜 ID——都是坑。

左边 match 把一句话拆成词条去查,右边 term 拿着完整标签精确比对

三、bool查询:把多个条件拼成复合查询

bool 是 Elasticsearch 复杂查询的地基,九成组合条件都是靠它拼出来的。

真实业务里很少只用一个叶子查询。比如要查「书名含 Java、出版社是老牌社、价格别超过 100」——三个条件,怎么塞进一个 query?答案就是 bool,它内部有四种子句:

  • must:必须满足,参与算分,类似「与」
  • should:满足其一即可,参与算分,类似「或」
  • must_not:必须不满足,不参与算分
  • filter:必须满足,但不参与算分,还能被缓存
GET /books/_search
{
   
  "query": {
   
    "bool": {
   
      "must": [
        {
    "match": {
    "name": "Java" } }
      ],
      "should": [
        {
    "term": {
    "publisher": "人民邮电出版社" } },
        {
    "term": {
    "publisher": "机械工业出版社" } }
      ],
      "must_not": [
        {
    "range": {
    "price": {
    "gte": 150 } } }
      ],
      "filter": [
        {
    "range": {
    "price": {
    "lte": 100 } } }
      ]
    }
  }
}

must_not 和 filter 都不参与算分,所以我建议把「过滤型」条件尽量丢进 filter:不掺和排序,还能走查询缓存,同样的条件查一次命中一次。

bool 就像条件拼装厂,must/should/filter 进来,一个完整查询出去

四、排序、分页与高亮:深度分页为什么不能硬翻

分页不是想翻到哪翻到哪,ES 的 from+size 深翻页会翻不动,得提前换方案。

查询结果默认按相关度算分排序,也可以用 sort 显式指定字段排序。分页靠 from 和 size 两个参数:from 是跳过多少条,size 是取多少条。再叠一个高亮,把命中的关键词包上 <em> 标签返回给前端,页面就能直接渲染。这三件事在 DSL 里是平级的兄弟:

GET /books/_search
{
   
  "query": {
    "match": {
    "name": "设计模式" } },
  "from": 0,
  "size": 10,
  "sort": [
    {
    "price": {
    "order": "asc" } }
  ],
  "highlight": {
   
    "fields": {
   
      "name": {
   
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}

真正的坑在深翻页。ES 的数据是分片存的,一个索引拆成多份放在不同节点上。查第 100 页(from=990, size=10)时,协调节点得先让每个分片各自取前 1000 条,再汇总排序,最后只留下第 990~1000 名——前面那 990 条全白算。页码越深,白算的量越大,这就是深翻页慢的根源:

Elasticsearch深度分页原理图,协调节点汇总各分片大量数据后只取少量结果

更难受的是默认限制:from+size 查询默认最多只能翻到第 10000 条(max_result_window 的默认值)。我线上就被真实用户教育过——列表页有人一路点到第 200 页,接口直接 500。

可能有人会问:深度分页到底怎么解决?

官方给两个方案:search_after 和 scroll。search_after 记住上一次的排序值,从那个位置往后接着查,没有上限,官方推荐;scroll 是把排序结果在内存里做一份快照,适合数据迁移这类一次性任务,官方已经不推荐。它俩有个共同点:都只能往后翻,不能像 from+size 那样随机跳页。

方案 原理 上限 适用场景
from+size 从头偏移取 默认 10000 条封顶 前几页展示
search_after 从上次排序位置继续 无上限 无限滚动、深翻页
scroll 服务端内存存快照 内存占用大 数据迁移、全量导出

五、聚合查询:让 Elasticsearch 帮你算报表

聚合就是 ES 的 GROUP BY——按某个字段分组,再对每组做统计,一次请求把报表算出来。

聚合(Aggregation):对搜索结果做分组和统计的机制,对应 SQL 里的 GROUP BY 加聚合函数。

比如商城首页要显示「每个分类下有多少本书」,本质就是按 category 字段分组,这就是 terms 聚合(bucket 类):

GET /books/_search
{
   
  "size": 0,
  "aggs": {
   
    "category_agg": {
   
      "terms": {
   
        "field": "category",
        "size": 20
      }
    }
  }
}

size 设成 0,意思是结果里我不要文档,只要聚合结果。想更进一步,比如「编程」分类下各出版社的书,价格最低、最高、平均各是多少——在 terms 里再嵌一层 stats 聚合就行:

GET /books/_search
{
   
  "query": {
    "term": {
    "category": "编程" } },
  "size": 0,
  "aggs": {
   
    "publisher_agg": {
   
      "terms": {
    "field": "publisher", "size": 10 },
      "aggs": {
   
        "price_stats": {
   
          "stats": {
    "field": "price" }
        }
      }
    }
  }
}

stats 一次返回 min、max、avg、sum、count 五个值,做个价格分布报表连 SQL 都省了,聚合结果直接喂给前端画图就行。

左侧一摞乱书,右侧按分类排好,每格飘着 min/max/avg 数字气泡

六、JavaRestClient查询:把 DSL 翻译成 Java 代码

JavaRestClient 和 DSL 是同一套查询逻辑的两副面孔,DSL 调通了,Java 里就是照着翻译。

JavaRestClient:Elasticsearch 官方 Java 客户端(7.x 里叫 REST High Level Client),用 Java 对象构建查询请求,底层发给集群的还是那份 DSL。

先引依赖、初始化连接:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.12.1</version>
</dependency>
RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(new HttpHost("192.168.10.20", 9200, "http"))
);

这里有个版本坑要先说清楚:REST High Level Client 在 7.15 起被标记弃用,8.0 已被官方移除。你要是上了 8.x,就得换新的 Java API Client(co.elastic.clients:elasticsearch-java),类名写法变了,但「先 DSL 后 Java」的翻译思路一点没变。

查询的套路是固定的:new 一个 SearchRequest → 用 QueryBuilders 搭条件 → 塞进 request.source()client.search 发出去。前面 DSL 里每个查询,在这里都有对应方法:

SearchRequest request = new SearchRequest("books");
request.source().query(QueryBuilders.matchQuery("name", "设计模式"));

// 分页 + 排序 + 高亮
request.source().from(0).size(10);
request.source().sort("price", SortOrder.ASC);
request.source().highlighter(SearchSourceBuilder.highlight()
        .field("name").preTags("<em>").postTags("</em>"));

SearchResponse response = client.search(request, RequestOptions.DEFAULT);

组合条件用 QueryBuilders.boolQuery(),一个方法返回一个组装器,must、should、filter 挨个往里面加:

BoolQueryBuilder bool = QueryBuilders.boolQuery();
bool.must(QueryBuilders.matchQuery("name", "Java"));
bool.should(QueryBuilders.termQuery("publisher", "人民邮电出版社"));
bool.mustNot(QueryBuilders.rangeQuery("price").gte(150));
bool.filter(QueryBuilders.rangeQuery("price").lte(100));
request.source().query(bool);

响应的解析也是对照翻译:getHits() 拿结果数组,getTotalHits() 拿总数,getSourceAsString() 拿文档 JSON,高亮按字段名取出来拼回对象:

SearchHits hits = response.getHits();
for (SearchHit hit : hits.getHits()) {
   
    Book book = new ObjectMapper()
            .readValue(hit.getSourceAsString(), Book.class);
    HighlightField hf = hit.getHighlightFields().get("name");
    if (hf != null) {
   
        book.setName(hf.getFragments()[0].string());
    }
}

依赖、初始化、文档的增删改查在 JavaRestClient 里也是一套固定模板:IndexRequest 写、GetRequest 查、UpdateRequest 改,要批量就塞进 BulkRequest。这张对照表值得存下来,写 Java 时直接翻:

查询 DSL 写法 JavaRestClient 写法
全量 match_all QueryBuilders.matchAllQuery()
单字段分词 match QueryBuilders.matchQuery()
多字段分词 multi_match QueryBuilders.multiMatchQuery()
词条精确 term QueryBuilders.termQuery()
范围 range QueryBuilders.rangeQuery()
组合条件 bool QueryBuilders.boolQuery()
分组统计 aggs.terms AggregationBuilders.terms().field()

学 Elasticsearch查询,我最大的体会就是「别把 DSL 和 Java 客户端当两门课学」。先在 Dev Tools 里把 DSL 调通、看到真实返回,再照着对照表翻译成 JavaRestClient 代码,比直接啃 Java API 文档快得多。这套「DSL 先行、Java 翻译」的路数,用 7.x 的 High Level Client 和 8.x 的新客户端都成立。

如果你也刚开始接 ES,建议按这个顺序刷技能点:match 打底 → bool 组合 → 聚合出报表,三个点够应付绝大多数搜索需求。本文基于 Elasticsearch 7.12.1 实测


我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你在 Elasticsearch查询里用 from+size 还是 search_after 做分页?

目录
相关文章
|
6天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1910 6
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
4天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
640 110
|
14天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2524 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
14天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max-Preview深度全解析:2.4万亿参数旗舰MoE模型+Token Plan限时优惠完整落地指南
2026年7月,全新旗舰级混合专家大模型Qwen3.8-Max-Preview正式开放抢先体验,作为通义千问Qwen3系列规格最高、综合推理能力顶尖的新一代模型,该模型总参数量达到2.4万亿(2.4T),是当前线上可调用的原生多模态旗舰模型,综合推理水准对标海外顶级Fable 5模型,在复杂工程开发、长文档深度分析、多步骤智能体自治、跨境多语言创作、海量数据挖掘五大高难度业务场景实现跨越式性能提升。
1383 2
|
12天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
1334 2
|
16天前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
1427 54
|
12天前
|
自然语言处理 测试技术 API
通义千问Qwen3.8-Max-Preview全功能解析:2.4万亿参数旗舰模型深度使用指南
在大模型技术持续迭代的当下,通义千问推出的Qwen3.8-Max-Preview作为新一代旗舰预览版模型,凭借2.4万亿参数的超大规模、多模态融合能力与全场景适配特性,成为开发者与企业用户探索AI应用的核心工具。该模型采用稀疏混合专家(MoE)架构,是通义千问首个突破万亿参数的多模态模型,可同时处理文本、图像、视频与文档等多种数据形态,在全栈代码开发、复杂逻辑推理、长文档分析与多智能体协作等场景实现跨越式升级。本文将全面拆解Qwen3.8-Max-Preview的核心功能,详解API调用流程与配置方法,覆盖多场景实战技巧,帮助用户快速掌握这款旗舰模型的使用方法,充分释放其性能潜力。
675 2