一、概述
1. 简述
完成 Elasticsearch 9.x 官方 Java 客户端的安装、实例化,并演示如何通过客户端执行核心操作。
2. 环境要求
- Java 17 或更高版本
🔍 深度分析:为什么必须是 Java 17?
-
LTS 版本稳定性:Java 17 是长期支持版本,生产环境兼容性与安全性有保障。
-
新特性依赖 :客户端内部使用了 Java 17 的记录类(Record) 、模式匹配等特性,简化代码并提升性能。
-
底层依赖约束 :客户端依赖的
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>
🔧 补充
-
版本对齐 :客户端版本需与 Elasticsearch 服务端大版本一致(如服务端是 9.4.x,客户端也用 9.4.x)。
-
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("索引已删除");
}
二、最佳实践总结
-
异常处理 :捕获
ElasticsearchException和IOException。 -
异步客户端 :高并发场景使用
ElasticsearchAsyncClient。 -
连接池配置 :调整
MaxConnTotal和MaxConnPerRoute提升性能。 -
日志调试 :配置
co.elastic.clients日志级别为DEBUG。