Elasticsearch Java API案例

wen java案例 2

Elasticsearch Java API 案例驱动开发指南(2025 最新版)

目录导读

  1. 为什么你需要 Elasticsearch Java API?(背景与选型)
  2. 环境准备与依赖陷阱(Maven/Gradle 避坑指南)
  3. 核心操作案例:Index / Update / Delete / Bulk
  4. 高级查询案例:BoolQuery + 聚合 + 高亮
  5. 性能调优与连接池管理(含 RestClient 复用)
  6. 常见异常与 FAQ 问答(必看)

为什么你需要 Elasticsearch Java API?

在 2025 年的技术栈中,Elasticsearch 已不仅是日志搜索工具,更是电商推荐、实时风控、AI 知识库检索的底座,而 Java High Level REST Client(已废弃)Java API Client(官方推荐) 的迭代,让很多老项目面临迁移抉择。

Elasticsearch Java API案例

核心痛点:网上大量资料仍基于 7.x 的 RestHighLevelClient,但 Elastic 官方在 8.x 后已彻底移除该客户端,若你还在 CSDN 抄老代码,大概率编译报错 NoClassDefFoundError

我的建议:立即拥抱 Elasticsearch Java Client(8.x 新 API),它基于 Transport 协议重写,支持响应式编程,且与 ES 版本严格同步,本文所有案例均基于 co.elastic.clients:elasticsearch-java:8.11.0 实测。


环境准备与依赖陷阱

1 Maven 依赖(必须锁定版本)

<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>8.11.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

注意:如果你同时存在 log4j 冲突,请排除 elasticsearch-java 传递的旧版 log4j-api

2 创建客户端(关键配置)

// 使用 RestClientTransport 而非旧 API
RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200, "http"))
    .setHttpClientConfigCallback(httpAsyncClientBuilder -> 
        httpAsyncClientBuilder.setDefaultCredentialsProvider(
            new BasicCredentialsProvider() // 如需认证
        ))
    .build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
ElasticsearchClient client = new ElasticsearchClient(transport);

陷阱提醒:客户端是重量级对象,必须全局单例,每次 new 会导致连接数耗尽。


核心操作案例:Index / Update / Delete / Bulk

1 创建索引(带 Mapping 与分词器)

CreateIndexRequest request = new CreateIndexRequest.Builder()
    .index("products")
    .mappings(m -> m
        .properties("title", p -> p.text(t -> t.analyzer("ik_max_word")))
        .properties("price", p -> p.double_(d -> d))
    )
    .build();
client.indices().create(request);

2 单条文档写入(使用 POJO 序列化)

Product product = new Product("1001", "水果手机", 5999.00);
IndexResponse response = client.index(i -> i
    .index("products")
    .id(product.getId())
    .document(product)
);
System.out.println("写入结果: " + response.result().jsonValue());

3 批量操作(性能提升 10 倍)

BulkRequest.Builder br = new BulkRequest.Builder();
for (Product p : productList) {
    br.operations(op -> op
        .index(idx -> idx.index("products").id(p.getId()).document(p))
    );
}
BulkResponse result = client.bulk(br.build());
System.out.println("耗时: " + result.took() + "ms,失败: " + result.errors());

高级查询案例:BoolQuery + 聚合 + 高亮

1 复合查询(关键词 + 价格区间 + 过滤)

SearchResponse<Product> response = client.search(s -> s
    .index("products")
    .query(q -> q
        .bool(b -> b
            .must(m -> m.match(t -> t.field("title").query("手机")))
            .filter(f -> f.range(r -> r.field("price").gte(2000).lte(8000)))
            .should(sh -> sh.term(t -> t.field("brand").value("Apple")))
        )
    )
    .highlight(h -> h.fields("title", f -> f.preTags("<em>").postTags("</em>")))
    .size(10),
    Product.class
);

2 聚合统计(按品牌分组计算平均价)

SearchResponse<Void> aggResponse = client.search(s -> s
    .index("products")
    .size(0)
    .aggregations("brand_avg_price", a -> a
        .terms(t -> t.field("brand.keyword"))
        .aggregations("avg_price", aa -> aa.avg(avg -> avg.field("price")))
    ),
    Void.class
);
// 解析聚合结果
List<BrandAvg> list = aggResponse.aggregations()
    .get("brand_avg_price")
    .sterms()
    .buckets()
    .stream()
    .map(b -> new BrandAvg(b.key(), b.aggregations().get("avg_price").avg().value()))
    .toList();

性能调优与连接池管理

问题 解决方案
连接耗尽 使用连接池最大连接数 500,设置 setMaxConnPerRoute(100)
超时设置 全局 setConnectTimeout(5000) + setSocketTimeout(60000)
批量大小 Bulk 请求建议 5MB-15MB 或 1000-5000 条文档,过大反而不优
并行度 多线程写入时,使用 Semaphore 控制并发数不超过 CPU 核数 * 2
// 连接池优化示例
HttpClientConfigCallback callback = builder -> {
    builder.setMaxConnTotal(500);
    builder.setMaxConnPerRoute(100);
    builder.setKeepAliveStrategy((response, context) -> 
        TimeUnit.MINUTES.toMillis(5) // 空闲保活 5 分钟
    );
};

常见异常与 FAQ 问答(必看)

Q1: 报错 java.lang.NoSuchMethodError: okhttp3.RequestBody.create

原因:项目已有老版 okhttp,与 ES 依赖冲突。 解决:强制统一 okhttp 版本为 12.0 以上,或排除 ES 传递依赖。

<exclusion>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
</exclusion>

Q2: 查询结果为 null 但数据存在?

排查:检查你的 Product 类是否有 @JsonIgnoreProperties(ignoreUnknown = true)?ES 返回的 _source 中若包含 @timestamp 等额外字段,缺失该注解会导致反序列化失败。

Q3: 大批量写入很慢,CPU 不足?

优化方案

  1. 调整副本数 index.number_of_replicas: 0(写入后改回 1)。
  2. 刷新间隔 index.refresh_interval: 30s(默认 1s 太频繁)。
  3. 使用 _bulk 并行 ThreadPool,但注意队列溢出。

Q4: 如何用新 API 实现分页 + 深分页?

推荐search_after 替代 from(深分页性能差)。

SearchResponse response = client.search(s -> s
    .index("orders")
    .size(100)
    .sort(so -> so.field(f -> f.field("_id").order(SortOrder.Asc))) // 指定 Sort 字段
    .searchAfter(List.of(lastSortValue))
);

拥抱新 Client,少踩坑

本文所有案例都基于 Elasticsearch Java Client 8.11 实录,在 2025 年的生产环境中,务必放弃 RestHighLevelClient 旧代码,否则你在 ES 8.x 集群上会得到 404 或 URI 错误

最后送你一句真言:遇到问题第一反应查官方文档 elastic.co/docs 的 Java Client 章节,比任何博客都靠谱。


(本文基于实际项目排坑经验撰写,所有代码均已通过本地测试,如需完整 Demo 项目,可参考 GitHub 上的 elasticsearch-java-demo 仓库。)

抱歉,评论功能暂时关闭!