Easy-Es Easy-Es
💋首页
  • v3.0.0(当前版本)
  • What's New

    • What' s New In Easy-Es v3.0.0?
  • 历史版本

    • v1.x.x
    • v2.x.x
  • 升级指南

    • 升级到3.x.x说明
💖赞助
  • 开源社区
  • 周边好物
  • 项目PPT (opens new window)
  • 项目介绍
  • 项目成员
  • 参与贡献
加入社区讨论
谁在使用
  • Doc-Apis (opens new window)
  • 健身计划一键生成系统 (opens new window)
  • 极氪汽车
  • Vuepress-theme-vdoing (opens new window)
  • Gitee (opens new window)
  • GitCode (opens new window)
  • Github (opens new window)
  • 简体中文 (opens new window)
  • English (opens new window)

广告采用随机轮播方式显示 ❤️成为赞助商
💋首页
  • v3.0.0(当前版本)
  • What's New

    • What' s New In Easy-Es v3.0.0?
  • 历史版本

    • v1.x.x
    • v2.x.x
  • 升级指南

    • 升级到3.x.x说明
💖赞助
  • 开源社区
  • 周边好物
  • 项目PPT (opens new window)
  • 项目介绍
  • 项目成员
  • 参与贡献
加入社区讨论
谁在使用
  • Doc-Apis (opens new window)
  • 健身计划一键生成系统 (opens new window)
  • 极氪汽车
  • Vuepress-theme-vdoing (opens new window)
  • Gitee (opens new window)
  • GitCode (opens new window)
  • Github (opens new window)
  • 简体中文 (opens new window)
  • English (opens new window)
  • 快速入门

    • 简介
    • 适用场景
    • 顾虑粉碎
    • 避坑指南
    • 快速开始
    • springboot集成demo
    • spring集成指南
    • solon集成指南
    • 配置
    • 注解
  • 核心功能

    • 条件构造器

      • 条件构造器介绍
      • 索引条件构造器
      • 查询条件构造器
      • 更新条件构造器
    • 索引CRUD

      • 索引托管模式
      • 索引CRUD
    • 数据CRUD

      • 数据同步方案
      • 数据CRUD
    • 多数据源支持
    • 动态索引支持
    • 四大嵌套查询
    • 链式调用
  • 拓展功能

    • 混合查询
      • 何为混合查询?
      • 为什么要有混合查询?
      • 使用混合查询与直接使用原生查询有什么优势?
      • 如何使用混合查询?
      • 混合查询的几种正确使用姿势
      • 混合查询错误的使用姿势
      • 结语
    • 原生查询
    • 分页查询
    • 嵌套类型
    • Join父子类型
    • 获取DSL语句
    • 执行DSL语句
    • 执行SQL语句
    • 自定义RequestOptions
    • 自定义default方法
  • 高阶语法

    • 查询字段过滤
    • 排序
    • 聚合查询
    • 分词&模糊匹配
    • 权重
    • 高亮查询
    • GEO地理位置查询
    • IP查询
  • 插件

    • 插件
    • 领域模型生成插件
  • 其它

    • 问答
    • 与MP差异
    • MySQL和EE语法对比
    • 更新日志
    • 更新计划
    • 版权
    • 鸣谢
  • v2.x文档
  • 拓展功能
老汉
2023-03-18
目录

混合查询

# 何为混合查询?

简单理解,就是一半采用EE的语法,一半采用RestHighLevelClient的语法,类似"油电混动",相信你会爱上这种"油电混动"模式,因为它结合了两种模式的优点!

# 为什么要有混合查询?

因为EE目前还没有做到对RestHighLevelClient的功能100%覆盖,目前经过一年多的发展,我们覆盖了RestHighLevelClient约90%左右的API,和99.9%的核心高频使用功能,如此就不可避免的会出现个别场景下,EE不能满足某个特殊需求,此时对EE框架进行二次开发或直接将该需求提给EE作者,在时间上都无法满足开发者需求,有些需求可能产品经理要的比较紧,那么此时,您就可以通过混合查询来解决窘境. 其次,在API设计上其实支持的功能越多越复杂,用户使用起来就越难,这两者是鱼与熊掌,很难兼得,RestHighLevelClient并非有意把API设计的如此难用,他们也非常不容易,复杂的地方光是参数就有上千种排列组合方式,所以我们也无法通过方法重载来简化API使用,即便我们支持了,届时API也会极度臃肿,找一个您想调用的方法更是难上加难,一样会被您无情吐槽,因此我们只简化了99.9%的高频核心API, 其它的低频API,需要您通过混合查询来实现.

# 使用混合查询与直接使用原生查询有什么优势?

混动车相比纯油车最大的优势就是省油,其实混合查询也是如此,能比直接使用原生查询省下不少代码,并且比直接使用原生查询更简单,兼具原生语法的灵活特性和EE的低代码优势,这点其实用过SpringData-Es的用户也清楚,它是混合查询的鼻祖,只不过青出于蓝而胜于蓝,我们后发制人,灵活度和"省油"程度更胜一筹,不信的官人请继续"往下看"!

# 如何使用混合查询?

在我没提供此篇文档时,尽管我提供了混合查询的API和简单介绍,但很多人还不知道有此功能,更不知道该如何使用,所以这里我以一个具体的案例,给大家演示如何使用混合查询,供大家参考,主公们别担心篇幅多,其实非常非常简单,只是我教程写的细.

背景

用户"向阳"微信向我反馈,说目前EE尚不支持查询按照距给定点的位置由近及远排序. 实际开发中,此场景可以被应用到打车时"乘客下单,要求优先派单给周围3公里内离我最近的司机",然后该乘客是个美女,担心自身安全问题,又多加了几个要求,比如司机必须是女性,驾龄大于3年,商务型车子等...

以上面打车的场景为例,我们来看下用EE怎么查询?上面查询可以分为两部分

  • EE支持的常规查询:如周围3公里内,司机性别为女,查询驾龄>=3年...
  • EE不支持的非常规查询:按照复杂的排序规则排序(写此篇文档时是不支持的,现已支持,但这不重要,本篇仅以此来演示混合查询的使用)

对于支持的部分,我们可以直接调用EE,由EE先构建一个SearchSourceBuilder出来

// 假设该乘客所在位置经纬度为 31.256224D, 121.462311D
LambdaEsQueryWrapper<Driver> wrapper = new LambdaEsQueryWrapper<>();
wrapper.geoDistance(Driver::getLocation, 3.0, DistanceUnit.KILOMETERS, new GeoPoint(31.256224D, 121.462311D))
       .eq(Driver::getGender,"女")
       .ge(Driver::getDriverAge,3)
       .eq(Driver::getCarModel,"商务车");
SearchSourceBuilder searchSourceBuilder = driverMapper.getSearchSourceBuilder(wrapper);
1
2
3
4
5
6
7

对于不支持的语句,可以继续用RestHighLevelClient的语法进行封装,封装好了,直接调用EE提供的混合查询接口,就可以完成整个查询.

// 此处的searchSourceBuilder由上面EE构建而来,我们继续对其追加排序参数
searchSourceBuilder.sort(
        new GeoDistanceSortBuilder("location", 31.256224D, 121.462311D)
                 .order(SortOrder.DESC)
                 .unit(DistanceUnit.KILOMETERS)
                 .geoDistance(GeoDistance.ARC)
);
wrapper.setSearchSourceBuilder(searchSourceBuilder);
List<Driver> drivers = driverMapper.selectList(wrapper);
1
2
3
4
5
6
7
8
9

如此您便可以既享受到了EE帮您生成好的基本查询,又可完成EE暂未支持的功能,只需要不太多的代码(相比直接RestHighLevelClient,仍能节省大量代码)就可以达成您的目标,和当下纯电动汽车尚未完全发展成熟下的一种折中方案---油电混动有着异曲同工之妙.

当然,如果您不习惯使用这种模式,您仍可以直接使用原生查询,所以您大可以无忧无虑的使用EE,我们已经为您想好了各种兜底的方案和退路,无忧售后!如果您也认可这种模式,不妨给我们点个star吧,为了让EE的用户爽,作者那糟老头子可谓是煞费苦心!

提示

上面的例子没看懂?没关系,其实上面的例子更多地是为了展示何为混合查询?我们在2.0.0-beta2版本后祭出了真正的大招,引入了全新的混合查询方案,使用更加简单,官人可以继续"往下看",下面有个案例"0"就是您想最渴望的!

# 混合查询的几种正确使用姿势

下面的案例虽然多,但用起来却非常简单,只是因为我教程写的细,看官们不要怕

    /**
     * 正确使用姿势0(最实用,最简单,最推荐的使用姿势):EE满足的语法,直接用,不满足的可以构造原生QueryBuilder,然后通过wrapper.mix传入QueryBuilder
     * @since 2.0.0-beta2 2.0.0-beta2才正式引入此方案,此方案为混合查询的最优解决方案,由于QueryBuilder涵盖了ES中全部的查询,所以通过此方案
     * 理论上可以处理任何复杂查询,并且可以和EE提供的四大嵌套类型无缝衔接,彻底简化查询,解放生产力!
     */
    @Test
    public void testMix0(){
        // 查询标题为老汉,内容匹配 推*,且最小匹配度不低于80%的数据
        // 当前我们提供的开箱即用match并不支持设置最小匹配度,此时就可以自己去构造一个matchQueryBuilder来实现
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        QueryBuilder queryBuilder = QueryBuilders.matchQuery("content", "推*").minimumShouldMatch("80%");
        wrapper.eq(Document::getTitle,"老汉").mix(queryBuilder);
        List<Document> documents = documentMapper.selectList(wrapper);
        System.out.println(documents);
    }

    /**
     * 混合查询正确使用姿势1: EE提供的功能不支持某些过细粒度的功能,所有查询条件通过原生语法构造,仅利用EE提供的数据解析功能
     */
    @Test
    public void testMix1() {
        // RestHighLevelClient原生语法
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("content", "推*").minimumShouldMatch("80%"));

        // 仅利用EE查询并解析数据功能
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.setSearchSourceBuilder(searchSourceBuilder);
        List<Document> documents = documentMapper.selectList(wrapper);
        System.out.println(documents);
    }


    /**
     * 混合查询正确使用姿势2: 其它都能支持,仅排序器不支持,这种情况可以只按ES原生语法构造所需排序器SortBuilder,其它用EE完成
     */
    @Test
    public void testMix2() {
        // EE满足的语法
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
                .match(Document::getContent, "推*");

        // RestHighLevelClient原生语法
        Script script = new Script("doc['star_num'].value");
        ScriptSortBuilder scriptSortBuilder = SortBuilders.scriptSort(script,ScriptSortBuilder.ScriptSortType.NUMBER).order(SortOrder.DESC);

        // 利用EE查询并解析数据
        wrapper.sort(scriptSortBuilder);
        List<Document> documents = documentMapper.selectList(wrapper);
        System.out.println(documents);
    }

    /**
     * 混合查询正确使用姿势3: 其它功能都能支持,但需要向SearchSourceBuilder中追加非query参数
     */
    @Test
    public void testMix3() {
        // EE满足的语法
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
                .match(Document::getContent, "推*");
        SearchSourceBuilder searchSourceBuilder = documentMapper.getSearchSourceBuilder(wrapper);

        // 追加或者设置一些SearchSourceBuilder支持但EE暂不支持的参数 不建议追加query参数,因为如果追加query参数会直接覆盖上面EE已经帮你生成好的query,以最后set的query为准
        searchSourceBuilder.timeout(TimeValue.timeValueSeconds(3L));
        wrapper.setSearchSourceBuilder(searchSourceBuilder);
        List<Document> documents = documentMapper.selectList(wrapper);
        System.out.println(documents);
    }


    /**
     * 查询条件中可以利用大多数基本查询,但EE提供的聚合功能不能满足需求的情况下,需要自定义聚合器
     */
    @Test
    public void textMix4() {
        // EE满足的语法
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
                .match(Document::getContent, "推*");
        SearchSourceBuilder searchSourceBuilder = documentMapper.getSearchSourceBuilder(wrapper);

        // RestHighLevelClient原生语法
        AggregationBuilder aggregation = AggregationBuilders.terms("titleAgg")
                .field("title");
        searchSourceBuilder.aggregation(aggregation);
        wrapper.setSearchSourceBuilder(searchSourceBuilder);
        SearchResponse searchResponse = documentMapper.search(wrapper);
        // TODO 聚合后的信息是动态的,框架无法解析,需要用户根据聚合器类型自行从桶中解析,参考RestHighLevelClient官方Aggregation解析文档
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

# 混合查询错误的使用姿势

讲个故事,从前有个懒汉,懒得抠脚,有一天有个勤奋老汉,看他居无定所,答应给他建造一套豪宅,让他住的爽一点,房子老汉都给他修好了,告诉他门在哪里,可这懒汉就是不听, 非要从天窗往进去爬,认为这样很酷,结果一不小心摔了下去,摔断了腿,去法院告老汉说他修的房子有问题,去大街上拉横幅骂老汉是个糟老头子...

EE支持的混合查询使用方案我已经全部列在上面正确的使用姿势中了,已经能涵盖任何一种使用场景了, 请勿凭空捏造凭空想象然后按自己认为没问题的方式来写,文档和源码都不带看,还非要自己YY使用方案,最后又来答疑群问我为什么不支持,还说代码里有坑,令码保国同志晚节不保...

下面就演示两种典型的不支持场景:

    /**
     * 不支持的混合查询1: 追加覆盖问题
     */
    @Test
    public void textNotSupportMix() {
        // EE满足的语法
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
                .match(Document::getContent, "推*");
        SearchSourceBuilder searchSourceBuilder = documentMapper.getSearchSourceBuilder(wrapper);

        // 用户又想在上面的基础上,再追加一些个性化的查询参数进去 但实际上此时执行查询时,查询条件仅仅是最后设置的title=隔壁老王,前面的老汉推*会被覆盖
        searchSourceBuilder.query(QueryBuilders.matchQuery("title", "隔壁老王"));
        wrapper.setSearchSourceBuilder(searchSourceBuilder);
        List<Document> documents = documentMapper.selectList(wrapper);
        System.out.println(documents);
        // 思考: 为什么会被覆盖? 因为目前技术上做不到,查询树已经建立好了,es底层并没有提供向树的指定层级上继续追加查询条件的API
    }

    /**
     * 不支持的混合查询2: 脱裤子放P 自欺欺人系列
     */
    @Test
    public void testNotSupportMix2() {
        // EE满足的语法
        LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.eq(Document::getTitle, "老汉")
                .match(Document::getContent, "推*");

        // SearchSourceBuilder的构造是自己new出来的,不是通过mapper.getSearchSourceBuilder(wrapper)构造 相当于脱裤子放P,那么上面的查询条件老汉推*自然不会生效
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.minScore(10.5f);
        wrapper.setSearchSourceBuilder(searchSourceBuilder);
        List<Document> documents = documentMapper.selectList(wrapper);
        System.out.println(documents);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 结语

因为ES官方提供的RestHighLevel支持的功能实在是过于繁多,尽管我目前仍在马不停蹄的集成各种新的功能,以及修复用户反馈的问题,优化既有代码性能,但仍不可避免地会出现有些许功能不能满足您当前需求,请各位主公们见谅,EE才诞生一年,不可能做到十全十美,请给我们一些时间,我们已经在没有任何回报的前提下狂奔了,这一年来取得的巨大进步大家有目共睹,这些所谓的不足,都会被解决的,就像新能源车在未来终会逐步取代燃油车,那些所谓的问题,假以时日都不是问题,乌拉!

帮助我们改善此文档 (opens new window)
上次更新: 2025/05/11
链式调用
原生查询

← 链式调用 原生查询→

Theme by Vdoing | Copyright © 2021-2025 老汉 | 浙ICP备2022020479号 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式