让 Elasticsearch 像 MyBatis-Plus 一样好用:开源 Es-Plus 实战指南

95 阅读4分钟

MyBatis-Plus 风格的 Elasticsearch ORM?来试试开源项目 Es-Plus

原生 Elasticsearch Java Client API 冗长又难维护?DSL 字符串写起来像拼魔方?这篇文章带你上手一个国产开源项目 —— Es-Plus,让 ES 查询、聚合、批量写都能像 MyBatis-Plus 一样链式搞定。

1. 项目背景:为什么需要它?

我们团队大量使用 Elasticsearch 做搜索、推荐、实时统计,但原生 RestHighLevelClient / Elasticsearch Java Client 有不少痛点:

  • 模板代码成堆:条件、分页、排序、聚合重复堆,维护成本爆炸。
  • DSL 字符串难扩展:Bool/Nested 聚合一个字段名写错就要查半天。
  • Bulk / Scroll / 聚合容易踩坑:线程池、重试、吞吐都得自己兜底。
  • 团队成员写法不统一,新人上手慢。

于是我们封装了 Es-Plus

  • 零侵入增强:在官方客户端上覆盖 MyBatis-Plus 风格链式 API。
  • 多版本兼容:一个 Starter 支持 ES 6.x / 7.x / 8.x。
  • 常见场景覆盖:条件、排序、分页、Nested、聚合、Bulk、Scroll、SQL 翻译。
  • 注解映射:POJO、索引、分词、别名、数据源 clientInstance 全部声明式配置。
  • 配套控制台es-plus-console + es-plus-web,调试 DSL 很顺手。

GitHub 地址(目前 Star 才 38,真心希望你能点个 ⭐️):github.com/zhaohaoh/es…

2. 核心亮点一览

  • 链式查询:Es.chainLambdaQuery(FastTestDTO.class).match(...).search()
  • 链式写入:Es.chainUpdate(FastTestDTO.class).saveBatch(list)
  • 索引管理:Es.chainIndex().createIndex(Entity.class)
  • 聚合封装:esAggWrapper().terms(...).subAgg(...)
  • BulkProcessor 管理器:线程池、重试、日志全内置
  • 多数据源:注解里写 clientInstance = "local"
  • toDsl() / toSql():链式 Wrapper 直接导出调试

3. 10 分钟上手

3.1 引入依赖

<dependency>
    <groupId>io.github.zhaohaoh</groupId>
    <artifactId>es-plus-spring-boot-starter</artifactId>
    <version><!-- 这里填最新版本  0.4.91--></version>
</dependency> 

如果目标集群是 ES 8.x,把 artifact 改成 es8-plus-spring-boot-starter

3.2 配置 Elasticsearch 连接

es-plus:
  address: 192.168.1.10:9200
  username: elastic
  password: changeme
  global-config:
    version: 7

多数据源可继续追加 client-properties.xxx,实体注解里填 clientInstance = "local" 即可。

3.3 声明索引实体

import com.es.plus.annotation.EsField;
import com.es.plus.annotation.EsId;
import com.es.plus.annotation.EsIndex;
import com.es.plus.constant.EsFieldType;
import lombok.Data;

@EsIndex(
    index = {"fast_test_new_v6"},
    alias = {"fast_test_new_alias"},
    tryReindex = true
)
@Data
public class FastTestDTO {

    @EsId
    private Long id;

    @EsField(type = EsFieldType.KEYWORD)
    private String username;

    @EsField(type = EsFieldType.TEXT, analyzer = "ik_max_word")
    private String text;

    private Long age;

    @EsField(type = EsFieldType.DATE)
    private Date createTime;
}

3.4 链式查询示例

import com.es.plus.common.params.EsResponse;
import com.es.plus.core.statics.Es;

EsResponse<FastTestDTO> response = Es.chainLambdaQuery(FastTestDTO.class)
        .match(FastTestDTO::getText, "苹果")
        .ge(FastTestDTO::getAge, 18L)
        .sortByDesc(FastTestDTO::getCreateTime)
        .search();

response.getList().forEach(System.out::println);

需要看 DSL 时,可以 .toDsl() 直接输出 JSON(仓库里的 FastTestDslTest 就是这样写的单测)。

3.5 链式写入 / 更新

List<FastTestDTO> batch = ...;
Es.chainUpdate(FastTestDTO.class).saveBatch(batch);

FastTestDTO dto = new FastTestDTO();
dto.setId(1L);
dto.setText("更新后的内容");
Es.chainUpdate(FastTestDTO.class).update(dto);

Es.chainUpdate(FastTestDTO.class)
        .terms(FastTestDTO::getId, 10L, 11L)
        .set(FastTestDTO::getText, "批量更新内容")
        .updateByQuery();

更 Spring 的姿势是继承 EsServiceImpl<T>

@Service
public class FastTestService extends EsServiceImpl<FastTestDTO> {

    public EsResponse<FastTestDTO> searchApple() {
        return esChainQueryWrapper()
                .match(FastTestDTO::getText, "苹果")
                .search();
    }
}

3.6 聚合与 Nested

EsChainLambdaQueryWrapper<FastTestDTO> wrapper = Es.chainLambdaQuery(FastTestDTO.class);

EsAggWrapper<FastTestDTO> agg = wrapper.esAggWrapper();
agg.terms("username", term -> term.size(10))
   .subAgg(sub -> sub.max("id"));

EsAggResponse<FastTestDTO> aggResp = wrapper.aggregations();
System.out.println(aggResp.getAggregations());

EsAggWrapper 支持 Terms、DateHistogram、Sum、Nested 等组合,内部基于 Elasticsearch AggregationBuilder。

3.7 BulkProcessor 实战

RestHighLevelClient client = ...;

BulkProcessorParam param = new BulkProcessorParam();
param.setBulkActions(1000);
param.setBulkSize(5);          // MB
param.setFlushInterval(5000);  // ms
param.setConcurrent(2);

BulkProcessor processor = BulkProcessorConfig
        .getBulkProcessor(client, param, "fast_test_new_v6");

BulkRequest bulkRequest = new BulkRequest();
// 这里把实体转换成 IndexRequest
processor.add(bulkRequest);

链式 API 内部也封装了异步批处理:Es.chainUpdate(FastTestDTO.class).saveBatchAsyncProcessor(list)

3.8 索引管理

Es.chainIndex().createIndex(FastTestDTO.class).putMapping(FastTestDTO.class);
Es.chainIndex().index("fast_test_new_v6")
        .forceMerge(1, true, true, "fast_test_new_v6");

4. 实际收益

  • 搜索模块重构:原本 600+ 行 DSL 改成链式调用,代码量砍半。
  • 聚合报表上线:terms + subAgg 快速搭建 TopN 报表,配管道排序输出排名。
  • 批量写入提速:BulkProcessorConfig + 异步批处理,导入效率提升 3 倍,失败重试全部日志化。

需求迭代平均开发时间下降约 35%

5. 当前状态与路线图

  • GitHub Actions 覆盖 Java 8/17/21 + ES 6.8/7.8/8.17。
  • CHANGELOG.md 已上线,后续补 CONTRIBUTING.md、Issue/PR 模板。

6. 如何支持这个开源项目?

  • GitHub 仓库:github.com/zhaohaoh/es…
  • ⭐ 欢迎点 Star(当前只有 38 个,小伙伴们请伸出援手)
  • Issue:附最小复现 & 环境信息(Java/Spring Boot/ES 版本),我们会积极响应。
  • PR:欢迎补测试、文档、功能;贡献指南和模板即将补齐。
  • 使用案例:在 Issue 分享你的实践,后续计划做“谁在用”专题。
  • 更多用法请前往github的es-plus详细案例.md查询 也欢迎把这篇文章转发给团队、群聊、朋友圈,一起帮这个国产开源项目多一点曝光。

7. FAQ

Q:支持阿里云、腾讯云等托管版 Elasticsearch 吗?
A:完全支持,只要能配置地址和认证信息。注意启用 HTTPS 时的证书配置即可。

Q:链式查询会有性能损耗吗?
A:不会,链式 API 最终还是调用官方客户端执行 DSL。

Q:怎么调试最终 DSL?
A:任意链式查询可 .toDsl() 输出; 查询也会打印日志

Q:多套 ES 集群如何切换?
A:Es.chainLambdaQuery(ClientContext.getClient("local"), Entity.class),或在实体注解里设置 clientInstance = "local"


如果你已经在业务里使用了 Es-Plus,欢迎留言交流,也期待你来提 Issue、补文档、甚至成为共建者。别忘了给仓库点个 ⭐️,分享给更多需要 ES ORM 的同学!