【Elasticsearch入门到落地】17、手把手教你玩转Term、Range、Bool查询与优雅封装​

接上篇《16、RestClient查询文档-快速入门
上一篇我们讲解了使用Java程序实现ElasticSearch查询文档的各项功能。本篇我们来继续讲解,使用RestClient进行term、range、bool查询,以及封装公用方法简化查询代码。

一、 从基础到进阶,构建可维护的ES查询

在上一篇博客中,我们成功地​​搭建了ElasticSearch基于RestHighLevelClient的Java集成环境​​,并掌握了使用MatchQuery进行基础全文检索的方法。我们通过一个简单的示例,查询了酒店名称中包含“希尔顿”的文档,迈出了使用Java代码操作ES的第一步。
然而,在实际的业务场景中,简单的全文检索远远不够。我们面临的往往是错综复杂的查询需求,如:
●“​​精确查找​​”所有品牌为“万豪”的酒店。(而不是名字里带“万豪”的)
●“​​范围筛选​​”出价格在500元到1000元之间的高性价比酒店。
●“​​组合条件​​”查询:找到位于“上海”、评分“高于45分”、并且品牌“不是速8”的所有酒店。

面对这些需求,如果继续拼接原始的JSON请求体,代码将变得难以编写且极易出错。而直接使用基础的QueryBuilders,虽然类型安全,但构建多层嵌套的Bool查询时,代码会显得臃肿且逻辑分散,可读性和可维护性都会急剧下降。
​​因此,本篇博客将聚焦三个核心目标:​​
​​1.深入核心查询语法​​:详解如何运用 QueryBuilders构建​​Term​​(精确匹配)、​​Range​​(范围查询)和功能强大的Bool​​(组合查询),以解决上述复杂的业务筛选问题。
​​2.实践与演示​​:结合hotel索引库中的真实数据(如brand,city,price,score等字段),提供可直接运行的Java代码示例,让大家看到每种查询的具体应用。
​​3.封装与升华​​:针对复杂查询代码冗余的问题,我们​​将封装一个通用的查询工具类​​。目的是将冗长的构建过程简化为清晰的链式调用,最终实现 ​​“一行代码找到答案”​​ 的效果,极大提升开发效率和代码质量。
通过本篇的学习,大家将掌握如何将ES中最常用的查询类型优雅地集成到Java应用中,写出更专业、更易维护的数据检索代码。

二、核心查询类型实战详解​

1.Term查询:精确值匹配

(1)概念​​:用于匹配确切的值,​​不会​​对搜索词进行分词。比如搜索brand为"希尔顿"的酒店,只会匹配字段值​​精确等于"希尔顿" 的文档。
​​(2)核心API​​:QueryBuilders.termQuery(String field, Object value)
​​(3)示例场景​​:查询所有品牌brand为 ​​"希尔顿"​​ 的酒店。
​​(4)代码演示​​:

package com.example;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;

public class TermQueryExample {
    public static void main(String[] args) throws Exception {
        // 通过提前写好的工具类,获取 Elasticsearch 客户端
        RestHighLevelClient client = ElasticsearchClient.getClient();

        // 构建查询请求
        // 1.指定索引名
        //创建 SearchRequest 对象,并指定要查询的索引名为hotel。
        SearchRequest request = new SearchRequest("hotel");
        // 2.构建查询条件
        // SearchSourceBuilder:用于定义查询的详细参数(如查询内容、分页、排序等)。
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //构建一个匹配查询:
        //字段名:name(表示搜索 hotel 索引中的 name 字段)。
        //搜索词:"希尔顿"(匹配字段值精确等于"希尔顿" 的文档。)。将查询条件设置到 sourceBuilder 中
        sourceBuilder.query(QueryBuilders.termQuery("brand", "希尔顿"));
        // 3.将查询条件绑定到请求
        request.source(sourceBuilder);

        // 执行查询
        // 调用 client.search() 方法发送请求:
        // 参数1:request(封装了索引名和查询条件)。
        // 参数2:RequestOptions.DEFAULT(使用默认请求配置)。
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理结果
        // response.getHits(): 获取所有匹配的文档(返回 SearchHits 对象)。
        SearchHits searchHits = response.getHits();
        // 获取查询的总数
        long total = searchHits.getTotalHits().value;
        // 获取查询的结果数组
        SearchHit[] hits = searchHits.getHits();
        // 遍历每个匹配的文档(SearchHit 对象)。
        for(SearchHit hit : hits){
            // hit.getSourceAsString(): 将文档的 JSON 源数据转为字符串并打印。
            String hitJson = hit.getSourceAsString();
            System.out.println(hitJson);
        }
        ElasticsearchClient.close();
    }
}

(5)结果分析​​:这条查询会精准命中brand字段值为"希尔顿"的文档,而不会匹配"希尔顿酒店"或"北京希尔顿"。这要求索引映射中brand字段的类型最好是keyword:

2.Range查询:范围匹配​​

​​(1)概念​​:用于匹配字段值在某个范围内的文档,常用于数字、日期等类型的字段。
​​(2)核心API​​:QueryBuilders.rangeQuery(String field)
​​(3)关键方法​​:

●gte(Object from): 大于等于 (Greater-than or equal to)
●lte(Object to): 小于等于 (Less-than or equal to)
●gt(Object from): 大于 (Greater-than)
●lt(Object to): 小于 (Less-than)

​​(4)示例场景​​:查询所有价格price在​​300到800元之间​​(包含300和800),且评分score​​大于45的酒店。
​​代码演示​​:

package com.example;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;

public class RangeQueryExample {
    public static void main(String[] args) throws Exception {
        // 通过提前写好的工具类,获取 Elasticsearch 客户端
        RestHighLevelClient client = ElasticsearchClient.getClient();

        // 构建查询请求
        // 1.指定索引名
        //创建 SearchRequest 对象,并指定要查询的索引名为hotel。
        SearchRequest request = new SearchRequest("hotel");
        // 2.构建查询条件
        // SearchSourceBuilder:用于定义查询的详细参数(如查询内容、分页、排序等)。
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 构建一个匹配查询:
        // 构建价格范围查询
        RangeQueryBuilder priceRangeQuery = QueryBuilders.rangeQuery("price")
                .gte(300)
                .lte(800);
        // 构建评分范围查询
        RangeQueryBuilder scoreRangeQuery = QueryBuilders.rangeQuery("score")
                .gt(45);
        // 组合查询:使用 Bool Query 的 must,表示必须同时满足
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(priceRangeQuery);
        boolQuery.must(scoreRangeQuery);

        sourceBuilder.query(boolQuery); // 将组合好的布尔查询设置进去
        // 3.将查询条件绑定到请求
        request.source(sourceBuilder);

        // 执行查询
        // 调用 client.search() 方法发送请求:
        // 参数1:request(封装了索引名和查询条件)。
        // 参数2:RequestOptions.DEFAULT(使用默认请求配置)。
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理结果
        // response.getHits(): 获取所有匹配的文档(返回 SearchHits 对象)。
        SearchHits searchHits = response.getHits();
        // 获取查询的总数
        long total = searchHits.getTotalHits().value;
        // 获取查询的结果数组
        SearchHit[] hits = searchHits.getHits();
        // 遍历每个匹配的文档(SearchHit 对象)。
        for(SearchHit hit : hits){
            // hit.getSourceAsString(): 将文档的 JSON 源数据转为字符串并打印。
            String hitJson = hit.getSourceAsString();
            System.out.println(hitJson);
        }
        ElasticsearchClient.close();
    }
}

(5)测试结果​​:

​3. Bool查询:逻辑组合(重中之重)​​

​​(1)概念​​:Bool查询是Elasticsearch中最强大的查询之一,它可以将多个查询子句以逻辑(mustAND,shouldOR,must_notNOT,filter)的方式组合起来,实现非常复杂的查询逻辑。
​​​​(2)核心API​​:QueryBuilders.boolQuery()
​​​​(3)四种子句详解​​:

●must: 查询​​必须​​出现在匹配的文档中,并参与相关性算分。相当于逻辑AND​​。
●should:查询​​应该​​出现在匹配的文档中。如果bool查询位于查询上下文且没有must或filter子句,那么至少满足一个should条件。匹配的should子句越多,文档的相关性得分越高。相当于逻辑​​OR​​。
●must_not: 查询​​必须不​​出现在匹配的文档中。不参与算分,并且在filter上下文执行。相当于逻辑​​NOT​​。
●filter:查询​​必须​​出现在匹配的文档中。但以​​不评分、过滤模式​​进行,这些子句对评分没有影响,但效率极高,因为它会利用缓存。​​强烈推荐​​用于过滤(如范围、状态码、精确匹配等)。

​​综合示例场景​​:我们来构建一个复杂的业务查询:​​“搜索所有在上海,品牌不是速8,价格低于500元,并且满足以下条件之一的酒店:【评分高于45分或星级是四钻及以上】。”​​
​​(4)代码演示​​:

package com.example;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.json.JSONObject;

public class BoolQueryExample {
    public static void main(String[] args) throws Exception {
        // 通过提前写好的工具类,获取 Elasticsearch 客户端
        RestHighLevelClient client = ElasticsearchClient.getClient();

        // 构建查询请求
        // 1.指定索引名
        //创建 SearchRequest 对象,并指定要查询的索引名为hotel。
        SearchRequest request = new SearchRequest("hotel");
        // 2.构建查询条件
        // SearchSourceBuilder:用于定义查询的详细参数(如查询内容、分页、排序等)。
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 构建一个匹配查询:
        // 3. 首先,构建各个叶子查询
        // 城市是上海 (精确匹配,用 Term)
        TermQueryBuilder cityQuery = QueryBuilders.termQuery("city", "上海");
        // 品牌不是速8 (排除,用 must_not + Term)
        TermQueryBuilder brandToExcludeQuery = QueryBuilders.termQuery("brand", "速8");
        // 价格低于500 (范围,用 Filter 不参与算分)
        RangeQueryBuilder priceQuery = QueryBuilders.rangeQuery("price").lt(500);
        // 评分高于45 (范围)
        RangeQueryBuilder highScoreQuery = QueryBuilders.rangeQuery("score").gt(45);
        // 星级是四钻及以上 (由于star_name是字符串,我们用范围查询其"钻"或"星"级别,这里假设数据规整)
        // 例如: "四钻", "五钻", "五星级" 都满足条件。我们可以用 gte "四" 来匹配。
        // 注意:这种字符串范围查询需要确保映射正确,且查询结果可能不精确,仅作示例。
        RangeQueryBuilder highStarQuery = QueryBuilders.rangeQuery("star_name")
                .gte("四");
        // 4. 开始组合 Bool 查询
        // 主布尔查询:必须满足的条件
        BoolQueryBuilder mainBoolQuery = QueryBuilders.boolQuery();
        mainBoolQuery.filter(cityQuery);      // 城市=上海,过滤
        mainBoolQuery.mustNot(brandToExcludeQuery); // 品牌 != 速8,过滤
        mainBoolQuery.filter(priceQuery);     // 价格<500,过滤

        // 子布尔查询:评分>45 OR 星级>=四钻
        BoolQueryBuilder shouldBoolQuery = QueryBuilders.boolQuery();
        shouldBoolQuery.should(highScoreQuery);
        shouldBoolQuery.should(highStarQuery);
        // 将这个 OR 条件加入到主查询的 must 子句中
        // 这意味着整个查询是: (city=上海 AND brand!=速8 AND price<500) AND (score>45 OR star_name>=四)
        mainBoolQuery.must(shouldBoolQuery);

        // 5. 将最终组合好的查询设置到请求中
        sourceBuilder.query(mainBoolQuery); // 将组合好的布尔查询设置进去
        // 6.将查询条件绑定到请求
        request.source(sourceBuilder);

        // 执行查询
        // 调用 client.search() 方法发送请求:
        // 参数1:request(封装了索引名和查询条件)。
        // 参数2:RequestOptions.DEFAULT(使用默认请求配置)。
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理结果
        // response.getHits(): 获取所有匹配的文档(返回 SearchHits 对象)。
        SearchHits searchHits = response.getHits();
        // 获取查询的总数
        long total = searchHits.getTotalHits().value;
        // 获取查询的结果数组
        SearchHit[] hits = searchHits.getHits();

        // 遍历每个匹配的文档(SearchHit 对象)。
        for(SearchHit hit : hits){
            // hit.getSourceAsString(): 将文档的 JSON 源数据转为字符串并打印。
            String hitJson = hit.getSourceAsString();
            // json工具类,用于格式化json
            JSONObject jsonObj = new JSONObject(hitJson);
            System.out.println(jsonObj.toString(4));
        }
        ElasticsearchClient.close();
    }
}

(5)测试结果:

这个例子展示了Bool查询的强大之处:它可以无限嵌套,从而构建出极其复杂的查询逻辑。

三、 进阶:封装可复用的查询工具类​

看到上面的代码,你是否已经感到有些头晕?每个查询都要写一堆样板代码,SearchRequest, SearchSourceBuilder, 各种 QueryBuilders... 逻辑分散,难以维护。我们需要将他们进一步进行封装,来简化后续的开发流程。

​1. 封装的动机​​

●代码冗余​​:大量重复的样板代码。
●难以维护​​:查询逻辑分散在各个 Service 或 Dao 中。
●目标​​:实现链式调用,如EsQueryWrapper.eq("city", "上海").lt("price", 500).neq("brand", "速8")...。

​​2. 工具类设计:EsQueryBuilder​​

我们将创建一个模仿MyBatis-Plus的 QueryWrapper风格的类。

package com.example;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;

public class EsQueryBuilder {
    // 核心:内部维护一个 BoolQueryBuilder
    private BoolQueryBuilder boolQuery;

    public EsQueryBuilder() {
        this.boolQuery = QueryBuilders.boolQuery();
    }

    //------------------ MUST (AND) 相关 ------------------
    public EsQueryBuilder mustTerm(String field, Object value) {
        this.boolQuery.must(QueryBuilders.termQuery(field, value));
        return this;
    }

    public EsQueryBuilder mustMatch(String field, Object value) {
        this.boolQuery.must(QueryBuilders.matchQuery(field, value));
        return this;
    }
    // 可以继续扩展 mustRangeGt, mustRangeLt 等

    //------------------ FILTER 相关 (推荐用于精确匹配和范围) ------------------
    public EsQueryBuilder filterTerm(String field, Object value) {
        this.boolQuery.filter(QueryBuilders.termQuery(field, value));
        return this;
    }

    public EsQueryBuilder filterRangeGt(String field, Object value) {
        this.boolQuery.filter(QueryBuilders.rangeQuery(field).gt(value));
        return this;
    }

    public EsQueryBuilder filterRangeLt(String field, Object value) {
        this.boolQuery.filter(QueryBuilders.rangeQuery(field).lt(value));
        return this;
    }

    public EsQueryBuilder filterRange(String field, Object from, Object to) {
        this.boolQuery.filter(QueryBuilders.rangeQuery(field).gte(from).lte(to));
        return this;
    }

    //------------------ MUST_NOT (NOT) 相关 ------------------
    public EsQueryBuilder mustNotTerm(String field, Object value) {
        this.boolQuery.mustNot(QueryBuilders.termQuery(field, value));
        return this;
    }

    //------------------ SHOULD (OR) 相关 ------------------
    // 注意:直接添加 should 子句到主 boolQuery 需要谨慎使用。
    // 更常见的做法是构建一个子 boolQuery 用于 should,然后将其添加到主查询的 must 或 filter 中。
    // 这里提供一个获取内部 boolQuery 的方法,用于更复杂的嵌套组合
    public BoolQueryBuilder getBoolQuery() {
        return this.boolQuery;
    }

    //------------------ 最终执行方法 ------------------
    public SearchRequest buildSearchRequest(String... indices) {
        SearchRequest request = new SearchRequest(indices);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(this.boolQuery); // 将构建好的最终查询设置进去
        request.source(sourceBuilder);
        return request;
    }
}

3. 使用封装后的工具类重写查询​​

现在,让我们用这个全新的EsQueryBuilder来重写上面那个复杂的Bool查询示例:

package com.example;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.json.JSONObject;

public class BoolQueryExample2 {
    public static void main(String[] args) throws Exception {
        // 通过提前写好的工具类,获取 Elasticsearch 客户端
        RestHighLevelClient client = ElasticsearchClient.getClient();

        // 初始化工具类
        EsQueryBuilder queryBuilder = new EsQueryBuilder();

        // 构建查询:链式调用,清晰易懂
        queryBuilder.filterTerm("city", "上海")      // 城市=上海
                .mustNotTerm("brand", "速8")     // 品牌 != 速8
                .filterRangeLt("price", 500)    // 价格<500
                .getBoolQuery()                  // 获取内部的BoolQueryBuilder
                .must(                          // 必须满足以下条件
                        QueryBuilders.boolQuery()   // 构建一个子BoolQuery(OR条件)
                                .should(QueryBuilders.rangeQuery("score").gt(45))      // 条件1: 评分>45
                                .should(QueryBuilders.rangeQuery("star_name").gte("四")) // 条件2: 星级>=四钻
                );

        // 构建请求
        SearchRequest request = queryBuilder.buildSearchRequest("hotel");

        // 执行查询
        // 调用 client.search() 方法发送请求:
        // 参数1:request(封装了索引名和查询条件)。
        // 参数2:RequestOptions.DEFAULT(使用默认请求配置)。
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理结果
        // response.getHits(): 获取所有匹配的文档(返回 SearchHits 对象)。
        SearchHits searchHits = response.getHits();
        // 获取查询的总数
        long total = searchHits.getTotalHits().value;
        // 获取查询的结果数组
        SearchHit[] hits = searchHits.getHits();

        // 遍历每个匹配的文档(SearchHit 对象)。
        for(SearchHit hit : hits){
            // hit.getSourceAsString(): 将文档的 JSON 源数据转为字符串并打印。
            String hitJson = hit.getSourceAsString();
            // json工具类,用于格式化json
            JSONObject jsonObj = new JSONObject(hitJson);
            System.out.println(jsonObj.toString(4));
        }
        ElasticsearchClient.close();
    }
}

代码对比与优势总结​​:
●可读性极大提升​​:链式调用让查询逻辑一目了然,几乎就像在阅读业务需求本身。
●维护性增强​​:所有查询构建逻辑通过工具类统一管理,修改查询方式只需改动工具类。
●代码更简洁​​:省去了大量创建和组装*QueryBuilder对象的样板代码。

四、总结与最佳实践​​

​​1.内容回顾​​:我们深入学习了 HLRC 对 Term(精确匹配)、Range(范围查询)和 Bool(逻辑组合)查询的支持。更重要的是,我们通过封装 EsQueryBuilder工具类,将复杂的 DSL 构建过程简化为了优雅的链式调用。
​​2.最佳实践建议​​:

​​●善用Filter Context​​:对于不关心相关度分数的条件(如:状态、时间范围、精确匹配标签),​​务必​​使用filter子句。它能利用缓存,极大提升查询性能。
​​●确保索引映射合理​​:Term查询依赖于keyword类型字段。请确保你的字段映射正确(例如,brand字段应为keyword,或者是一个包含keyword子字段的text类型)。
​​●理解Bool查询的算分​​:must和should影响得分,filter和must_not不影响。根据你的业务需求(是搜索排序还是精确过滤)合理选择子句。
​​●资源管理​​:记得在使用完毕后,调用client.close()关闭连接,释放资源。
​​展望​​:本章介绍的工具类只是一个起点。大家可以继续扩展它,支持聚合(Aggregations)、高亮(Highlighting)、排序(Sort)、分页(From/Size)等功能,使其成为一个真正强大的ES查询助手。

下一篇我们继续来学习ElasticSearch的高亮(Highlighting)、排序(Sort)、分页(From/Size)等功能的Java代码编写。

转载请注明出处:https://blog.csdn.net/acmman/article/details/152282921

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光仔December

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值