Elasticsearch Java 客户端(9.x)


一、概述

1. 简述

完成 Elasticsearch 9.x 官方 Java 客户端的安装、实例化,并演示如何通过客户端执行核心操作。


2. 环境要求

  • Java 17 或更高版本

🔍 深度分析:为什么必须是 Java 17?

  1. LTS 版本稳定性:Java 17 是长期支持版本,生产环境兼容性与安全性有保障。

  2. 新特性依赖 :客户端内部使用了 Java 17 的记录类(Record)模式匹配等特性,简化代码并提升性能。

  3. 底层依赖约束 :客户端依赖的 jakarta.json 等库仅支持 Java 17+。


3. 安装

3.1 Gradle 项目

Groovy 复制代码
dependencies {
    implementation 'co.elastic.clients:elasticsearch-java:9.3.0'
}

3.2 Maven 项目

XML 复制代码
<dependencies>
    <dependency>
        <groupId>co.elastic.clients</groupId>
        <artifactId>elasticsearch-java</artifactId>
        <version>9.3.0</version>
    </dependency>
</dependencies>

🔧 补充

  1. 版本对齐 :客户端版本需与 Elasticsearch 服务端大版本一致(如服务端是 9.4.x,客户端也用 9.4.x)。

  2. Maven 仓库配置:若中央仓库下载失败,添加 Elastic 官方仓库:

    XML 复制代码
    <repositories>
        <repository>
            <id>elastic-co</id>
            <url>https://artifacts.elastic.co/maven</url>
        </repository>
    </repositories>

4. 连接客户端

连接到 Elasticsearch

4.1 基础连接示例

Java 复制代码
// 1. 配置连接信息
String serverUrl = "https://localhost:9200";
String apiKey = "VnVhQ2ZHY0JDZGJrU...";

// 2. 创建客户端
ElasticsearchClient esClient = ElasticsearchClient.of(b -> b
    .host(serverUrl)
    .apiKey(apiKey)
);

// 3. 使用客户端...

// 4. 关闭资源
esClient.close();

🔧 生产级连接优化

(1)HTTPS 与自签名证书处理

Elasticsearch 9.x 默认启用 HTTPS,开发环境需配置信任自签名证书:

Java 复制代码
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.ssl.SSLContextBuilder;
import org.elasticsearch.client.RestClient;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;

// 1. 构建 SSL 上下文(信任自签名证书,仅用于开发!)
SSLContext sslContext = SSLContextBuilder.create()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();

// 2. 构建底层 RestClient
RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200, "https"))
    .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
        .setSSLContext(sslContext)
        .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) // 跳过主机名验证
    )
    .build();

// 3. 构建传输层与客户端
ElasticsearchTransport transport = new RestClientTransport(
    restClient, new JacksonJsonpMapper()
);
ElasticsearchClient esClient = new ElasticsearchClient(transport);
(2)连接超时与重试
Java 复制代码
RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200, "https"))
    .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder
        .setConnectTimeout(5000)    // 连接超时:5秒
        .setSocketTimeout(60000)    // 套接字超时:60秒
    )
    .build();
(3)资源管理最佳实践

使用 try-with-resources 自动关闭客户端:

Java 复制代码
try (ElasticsearchClient esClient = new ElasticsearchClient(transport)) {
    // 执行操作
} // 自动关闭,无需手动调用 close()

5. 核心操作

5.1 创建索引

简化版:
Java 复制代码
esClient.indices().create(c -> c.index("products"));
🔧 生产级优化(含 Settings & Mappings)
Java 复制代码
import co.elastic.clients.elasticsearch._types.mapping.*;
import java.util.HashMap;
import java.util.Map;

// 1. 定义字段映射(Mappings)
Map<String, Property> properties = new HashMap<>();
properties.put("sku", Property.of(p -> p.keyword(KeywordProperty.of(k -> k)))); // 精确匹配
properties.put("name", Property.of(p -> p.text(TextProperty.of(t -> t))));       // 全文搜索
properties.put("price", Property.of(p -> p.double_(DoubleProperty.of(d -> d)))); // 数字类型

// 2. 创建索引(含 Settings)
esClient.indices().create(c -> c
    .index("products")
    .settings(s -> s
        .numberOfShards("3")    // 3个主分片
        .numberOfReplicas("1")  // 1个副本分片
    )
    .mappings(m -> m.properties(properties))
);
🔍 深度分析
  • 主分片数:创建后不可修改,需根据数据量提前规划(建议单分片数据量 < 50GB)。

  • 副本分片:可动态调整,用于高可用(生产环境建议至少 1 个副本)。


5.2 索引文档

简化版:
Java 复制代码
Product product = new Product("bk-1", "City bike", 123.0);
esClient.index(i -> i.index("products").id(product.getSku()).document(product));
🔧 优化与补充
(1)Product 类完整定义
Java 复制代码
public class Product {
    private String sku;
    private String name;
    private Double price;

    // 必须有无参构造函数(用于 JSON 反序列化)
    public Product() {}

    public Product(String sku, String name, Double price) {
        this.sku = sku;
        this.name = name;
        this.price = price;
    }

    // Getter & Setter
    public String getSku() { return sku; }
    public void setSku(String sku) { this.sku = sku; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Double getPrice() { return price; }
    public void setPrice(Double price) { this.price = price; }
}
(2)带参数的索引操作
Java 复制代码
import co.elastic.clients.elasticsearch.core.IndexResponse;
import co.elastic.clients.elasticsearch._types.Refresh;

Product product = new Product("bk-1", "City bike", 123.0);

IndexResponse response = esClient.index(i -> i
    .index("products")
    .id(product.getSku())
    .document(product)
    .refresh(Refresh.WaitFor) // 等待刷新后返回(保证搜索可见性)
);

System.out.println("文档版本:" + response.version()); // 乐观锁版本号

5.3 获取文档

简化版:
Java 复制代码
GetResponse<Product> response = esClient.get(g -> g.index("products").id("bk-1"), Product.class);
🔧 优化:字段过滤与路由
Java 复制代码
GetResponse<Product> response = esClient.get(g -> g
    .index("products")
    .id("bk-1")
    .source(s -> s.filter(f -> f.includes("name", "price"))) // 只返回指定字段
    .routing("custom-routing") // 若索引时使用了路由,获取时需指定
, Product.class);

if (response.found()) {
    Product product = response.source();
    System.out.println("产品名称:" + product.getName());
}

5.4 搜索文档

简化版:
Java 复制代码
SearchResponse<Product> response = esClient.search(s -> s
    .index("products")
    .query(q -> q.match(t -> t.field("name").query("bike")))
, Product.class);
🔧 生产级搜索(组合查询 + 分页 + 高亮)
Java 复制代码
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.search.Hit;

// 1. 构建组合查询(Bool Query)
Query matchQuery = Query.of(q -> q.match(m -> m.field("name").query("bike")));
Query rangeQuery = Query.of(q -> q.range(r -> r.field("price").gte(JsonData.of(100)).lte(JsonData.of(200))));

Query boolQuery = Query.of(q -> q.bool(BoolQuery.of(b -> b
    .must(matchQuery)   // 必须匹配
    .filter(rangeQuery) // 过滤条件(不影响评分)
)));

// 2. 执行搜索
SearchResponse<Product> response = esClient.search(s -> s
    .index("products")
    .query(boolQuery)
    .from(0)          // 分页起始位置
    .size(10)         // 每页大小
    .sort(sort -> sort.field(f -> f.field("price").order(SortOrder.Asc))) // 按价格排序
    .highlight(h -> h.fields("name", f -> f)) // 高亮匹配字段
, Product.class);

// 3. 解析结果
System.out.println("命中总数:" + response.hits().total().value());
for (Hit<Product> hit : response.hits().hits()) {
    Product product = hit.source();
    System.out.println("产品:" + product.getName() + ",价格:" + product.getPrice());
    // 高亮结果
    if (hit.highlight() != null) {
        System.out.println("高亮名称:" + hit.highlight().get("name").get(0));
    }
}

5.5 更新文档

简化版:
Java 复制代码
esClient.update(u -> u.index("products").id("bk-1").upsert(product), Product.class);
🔧 优化:部分更新与脚本更新
(1)部分更新(仅修改指定字段)
Java 复制代码
Map<String, Object> updateFields = new HashMap<>();
updateFields.put("price", 150.0); // 更新价格
updateFields.put("description", "A comfortable city bike"); // 新增字段

esClient.update(u -> u
    .index("products")
    .id("bk-1")
    .doc(updateFields) // 部分更新
, Product.class);
(2)脚本更新(复杂逻辑)
Java 复制代码
String script = "ctx._source.price *= 1.1"; // 价格上涨 10%

esClient.update(u -> u
    .index("products")
    .id("bk-1")
    .script(s -> s.inline(i -> i.lang("painless").source(script)))
, Product.class);

5.6 删除文档与索引

简化版:
Java 复制代码
esClient.delete(d -> d.index("products").id("bk-1"));
esClient.indices().delete(d -> d.index("products"));
🔧 优化:安全删除
Java 复制代码
// 删除文档(带结果判断)
DeleteResponse deleteResponse = esClient.delete(d -> d.index("products").id("bk-1"));
System.out.println("删除结果:" + deleteResponse.result()); // "Deleted" 或 "Not_Found"

// 删除索引(先判断是否存在)
String indexName = "products";
if (esClient.indices().exists(e -> e.index(indexName)).value()) {
    esClient.indices().delete(d -> d.index(indexName));
    System.out.println("索引已删除");
}

二、最佳实践总结

  1. 异常处理 :捕获 ElasticsearchExceptionIOException

  2. 异步客户端 :高并发场景使用 ElasticsearchAsyncClient

  3. 连接池配置 :调整 MaxConnTotalMaxConnPerRoute 提升性能。

  4. 日志调试 :配置 co.elastic.clients 日志级别为 DEBUG

相关推荐
JavaGuide1 分钟前
鹅厂面试:SELECT * 一定导致索引失效?常见索引失效场景有哪些?
java·数据库·后端·mysql·大厂面试
QuZero12 分钟前
Java `volatile` and Memory Model
java·jvm
me83219 分钟前
【Java】解决Maven多模块父POM加载失败+IDEA无法新建Java类问题
java·maven·intellij-idea
亚马逊云开发者28 分钟前
RAG 向量存储月费 800 刀?S3 Vectors 直接砍到 100 出头
java
2401_8955213438 分钟前
springboot集成onlyoffice(部署+开发)
java·spring boot·后端
zlpzlpzyd39 分钟前
groovy学习
java·jvm·学习
程序员小假1 小时前
你分得清 Prompt、Agent、Function Call、Skill、MCP 吗?
java·后端
xuboyok21 小时前
【Spring Boot】统一数据返回
java·spring boot·后端
亚马逊云开发者1 小时前
你的 AI Agent 只有鱼的记忆?聊聊 Agent 记忆管理的正确姿势
java
燕山罗成1 小时前
JAVA多线程基础
java·开发语言