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 的同学!