Elasticsearch 几种常用的客户端介绍
1. Rest High Level Client (RestHLClient)
简介
这是 Elasticsearch 官方早期推出的高级 REST 客户端,基于底层低级 REST 客户端封装,支持同步和异步操作,封装了常用的 ES API。- 优点
- 官方支持,文档丰富,生态完善
- 封装了大部分 ES REST API,调用方便
- 支持异步请求
- Spring Data Elasticsearch 2.x - 4.x 版本都基于它
- 缺点
- 只能用到 ES 7.x,8.x 以后官方宣布弃用
- API 不够现代化,使用 Builder 模式不够优雅
- 不支持 Elasticsearch 8.x 的新特性(比如新的安全模型)
- 适用场景
- 仍在用 Elasticsearch 7.x 或之前版本
- 需要快速集成且依赖成熟的生态
2. Spring Data Elasticsearch
简介
Spring 社区基于 Rest High Level Client 封装的 Spring Data 项目,提供 Repository 风格的数据访问,支持注解实体映射,方便 Spring Boot 集成。5.x版本及以后基于Elasticsearch-java,5.x版本以前基于RestHLClient。- 优点
- 极大简化 Elasticsearch 的数据访问
- 支持声明式 Repository,熟悉 Spring Data JPA 的开发者易上手
- 自动处理 POJO 与 ES 映射的转换
- 集成 Spring 生态,方便事务管理、依赖注入等
- 缺点
- 功能不及原生客户端灵活,复杂查询可能不方便
- 更新节奏较慢,追赶 ES 新版本滞后
- 适用场景
- Spring Boot 项目
- 业务逻辑不复杂,想快速做 CRUD 和简单查询
3. Elasticsearch Java API Client (elasticsearch-java)
简介
这是 Elasticsearch 官方从 8.0 版本开始推出的官方客户端,完全重写,使用更现代的 Builder 模式和 JSON-P 映射(默认 Jackson),设计更加现代和类型安全。- 优点
- 官方最新客户端,持续更新和支持
- API 设计现代,支持异步和响应式编程(配合 Reactor)
- 支持 Elasticsearch 8.x 所有新特性,包括安全认证等
- 基于 JSON-P,默认集成 Jackson,序列化效率更高
- 缺点
- 只支持 ES 8.x 及以上版本
- 对新手来说,API 需要一定学习成本
- 生态和社区相较 RestHLClient 还在逐步完善
- 适用场景
- 使用 ES 8.x 版本或准备升级
- 希望用官方推荐的最新客户端
本文集成官方最新推出的新版客户端 elasticsearch-java
pom.xml 添加依赖
<!-- 官方 Elasticsearch Java API Client -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>9.1.1</version>
</dependency>
<!-- 需要包含 Elasticsearch Transport 依赖(HTTP 传输依赖) -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>9.1.1</version>
</dependency>
<!-- json 时间类型处理 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
配置 elasticsearch Bean
elasticsearch9 已经默认开启了 SSL/TLS 证书,下面这个类是跳过证书验证的配置(测试环境使用)
package com.xxl.elasticsearch.config;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* @author 7号
* @version 1.0
* @since 2025-08-11 20:37
*/
@Configuration
public class ElasticsearchConfig {
private static final String HOSTNAME = "localhost";
private static final String PORT = "9200";
private static final String SCHEME = "https";
/**
* 创建了一个 RestClientBuilder,用来构造 RestClient 实例
* destroyMethod = "close":表示在 Spring 容器关闭时,会调用该 Bean 的 close() 方法进行资源释放。
*/
@Bean(destroyMethod = "close")
public RestClient restClient() throws Exception {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("es账号", "es密码"));
// 创建绕过证书验证的 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
// TrustManager,跳过证书校验
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
sslContext.init(null, trustAllCerts, new SecureRandom());
return RestClient.builder(new HttpHost(HOSTNAME, Integer.parseInt(PORT), SCHEME))
.setHttpClientConfigCallback(httpClientBuilder -> //
httpClientBuilder //
.setDefaultCredentialsProvider(credentialsProvider) //
// 设置自定义 SSLContext 并跳过 hostname 校验
.setSSLContext(sslContext) //
.setSSLHostnameVerifier((hostname, session) -> true)
) //
.setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder //
.setConnectTimeout(5000) //
.setSocketTimeout(60000) //
.setConnectionRequestTimeout(0)
)
.build();
}
@Bean
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
// 创建 ObjectMapper 并注册 JavaTimeModule,支持 LocalDate/LocalDateTime 序列化
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
// 禁用序列化为时间戳(数组形式)
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 用自定义 ObjectMapper 创建 JacksonJsonpMapper
JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper(objectMapper);
// RestClientTransport 是 elasticsearch-java 客户端用来封装 HTTP 传输的类,底层用的就是前面那个 RestClient(Apache HTTP 客户端)。
RestClientTransport transport = new RestClientTransport(restClient, jsonpMapper);
// 构造 ElasticsearchClient 对象。
// ElasticsearchClient 是官方提供的高级客户端,负责调用 Elasticsearch API,处理请求和响应。
// 这个客户端依赖 transport 来执行实际的 HTTP 通信。
return new ElasticsearchClient(transport);
}
}
如果不要跳过证书,就使用这个 bean 配置(生产环境使用,还未验证)
@Configuration
public class ElasticsearchConfig {
/**
* 创建了一个 RestClientBuilder,用来构造 RestClient 实例
* destroyMethod = "close":表示在 Spring 容器关闭时,会调用该 Bean 的 close() 方法进行资源释放。
*/
@Bean(destroyMethod = "close")
public RestClient restClient() {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("es账号", "es密码"));
return RestClient.builder(new HttpHost("localhost", 9200, "http")) //
.setHttpClientConfigCallback(httpClientBuilder -> //
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)) //
.setRequestConfigCallback(requestConfigBuilder -> //
requestConfigBuilder //
.setConnectTimeout(5000) //
.setSocketTimeout(60000) //
.setConnectionRequestTimeout(0)) //
.build();
}
@Bean
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
// RestClientTransport 是 elasticsearch-java 客户端用来封装 HTTP 传输的类,底层用的就是前面那个 RestClient(Apache HTTP 客户端)。
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
// 构造 ElasticsearchClient 对象。
// ElasticsearchClient 是官方提供的高级客户端,负责调用 Elasticsearch API,处理请求和响应。
// 这个客户端依赖 transport 来执行实际的 HTTP 通信。
return new ElasticsearchClient(transport);
}
}
SpringBoot 使用 Elasticsearch API
实体类 // 注意 Mysql 的实体是Mysql 的,es 的实体是es的,不要用同一个实体。使用之前先在 Elasticsearch 中创建好索引
import lombok.Data;
@Data
public class ProductDocument {
private String id;
private String name;
private String description;
}
Service 层
@Service
public class ProductDocumentService {
@Autowired
private ElasticsearchClient client;
// 添加一条数据
public String indexProduct(ProductDocument product) throws IOException {
IndexResponse response = client.index(i -> i
.index("products") // 索引名
.id(product.getId())
.document(product)
);
return response.id();
}
}
Controller 层
@RestController
@RequestMapping("/products")
public class ProductDocumentController {
@Autowired
private ProductDocumentService service;
@PostMapping
public String addProduct(@RequestBody ProductDocument product) throws IOException {
return service.indexProduct(product);
}
}
结果,调用成功
添加数据
添加数据,索引不存在时,自动创建索引。推荐提前在 es 服务中建好索引,因为自动映射类型不够精准。
@Autowired
private ElasticsearchClient client;
// 添加一条数据
public String indexProduct(ProductDocument product) throws IOException {
IndexResponse response = client.index(i -> i
.index("products") // 索引名
.id(product.getId())
.document(product)
);
return response.id();
}
查询数据
通过 id 查询
@Autowired
private ElasticsearchClient client;
/**
* 根据 id 查询数据
*/
public ProductDocument getProductById(String id) throws IOException {
GetResponse<ProductDocument> response = client.get(g -> g
.index("products")
.id(id),
ProductDocument.class
);
if (response.found()) {
return response.source();
} else {
return null; // 或抛异常,文档不存在
}
}
多条件,组合查询
@Autowired
private ElasticsearchClient client;
public List<ProductDocument> searchByNameAndPriceRange(String nameValue, Double minPrice, Double maxPrice) throws IOException {
SearchResponse<ProductDocument> response = client.search(s -> s
.index("products")
.query(q -> q
.bool(b -> b
.must(m -> m
.match(t -> t
.field("name") // 查询的字段
.query(nameValue) // 查询的值
)
).filter(f -> f
.range(r -> r
.number(nr -> nr
.field("price") // 查询的字段
.gte(minPrice) // 大于等于
.lte(maxPrice) // 小于等于
)
)
)
)
),
ProductDocument.class
);
return response.hits().hits().stream()
.map(hit -> hit.source())
.collect(Collectors.toList());
}
更新数据
更新部分字段,对象的方式,没传的字段不会被删除,也不会丢失,依然存在原文档里
@Autowired
private ElasticsearchClient client;
public String updateDocumentFields(ProductDocument product) throws IOException {
UpdateResponse<ProductDocument> response = client.update(u -> u
.index("products")
.id(product.getId())
.doc(product),
ProductDocument.class);
return response.id();
}
删除数据
根据 id 删除数据
@Autowired
private ElasticsearchClient client;
public String deleteDocumentById(String id) throws IOException {
DeleteResponse response = client.delete(d -> d
.index("products")
.id(id)
);
return response.id();
}