1. 为什么我们需要一个通用的ES数据访问层?
如果你正在开发一个需要搜索功能的SpringBoot应用,比如一个电商网站的商品搜索,或者一个日志分析平台,那么Elasticsearch(ES)大概率是你的首选。它强大的全文检索和聚合分析能力,确实能解决很多问题。但说实话,我刚开始在项目里用ES的时候,感觉挺“酸爽”的。每次写查询,都要面对一大堆DSL(领域特定语言)构建代码,BoolQueryBuilder、TermQueryBuilder这些类名又长又绕,业务代码里到处散落着ES的API调用,维护起来简直是噩梦。更别提版本升级了,从7.x换到8.x,官方直接弃用了RestHighLevelClient,改用全新的Java API Client,很多写法都变了,改起来那叫一个头疼。
所以,我就在想,能不能像我们用Spring Data JPA操作数据库那样,来操作Elasticsearch呢?我们定义一个实体类,然后写一个接口,基础的增删改查、分页查询就自动有了。这就是构建一个通用数据访问层(Repository) 的核心想法。它的好处太明显了:第一是省事,业务开发不用再关心ES复杂的DSL语法,专注业务逻辑就行;第二是统一,所有ES操作都通过这一层,代码风格一致,也便于做统一的日志、监控和异常处理;第三是好维护,ES客户端配置、版本升级的变动,都只需要在这一层调整,不会波及到上层业务代码。
这篇文章,我就带你从零开始,在一个SpringBoot 3.x项目里,集成最新的Elasticsearch 8.x Java客户端,并一步步设计、实现一个高度抽象、拿来即用的通用Repository。我会用一个“商品搜索”的场景贯穿始终,让你看到每一步代码是如何解决实际问题的。最终,你会得到一个封装良好的工具包,以后在任何SpringBoot项目里需要ES,直接把这个包引入,简单配置一下就能用,开发效率能提升好几倍。
2. 环境准备与项目初始化
万事开头难,但第一步往往是最简单的。我们先来把基础环境搭好。
2.1 本地运行Elasticsearch和Kibana
虽然生产环境通常是集群部署,但我们本地开发,用Docker跑个单节点是最方便的。如果你还没装Docker,去官网下一个,安装过程很简单。
打开你的终端,执行下面这条命令,一个ES 8.12.0的单节点服务就跑起来了:
docker run -d \
--name es8 \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
docker.elastic.co/elasticsearch/elasticsearch:8.12.0
我来解释一下这几个参数:-p是把容器内的9200和9300端口映射到宿主机,这样我们才能访问;discovery.type=single-node指定为单节点模式;xpack.security.enabled=false是暂时关闭安全认证,方便我们本地调试,生产环境一定要开启;最后一句是设置JVM堆内存,避免把电脑卡死。
启动后,在浏览器访问 http://localhost:9200,如果看到一串包含"you Know, for Search"的JSON信息,恭喜你,ES启动成功了!
光有ES还不够,我们还需要一个可视化工具来查看数据和调试查询,这就是Kibana。同样用Docker启动:
docker run -d \
--name kibana8 \
-p 5601:5601 \
-e "ELASTICSEARCH_HOSTS=http://es8:9200" \
--link es8:es8 \
docker.elastic.co/kibana/kibana:8.12.0
这里--link参数把Kibana容器和刚才的ES容器连接起来。访问 http://localhost:5601,就能进入Kibana的界面了。在左侧菜单找到“Dev Tools”,这里可以像在IDE里一样编写和执行ES的查询语句,非常方便。
2.2 创建SpringBoot 3.x项目并引入关键依赖
打开你喜欢的IDE(比如IntelliJ IDEA),创建一个新的SpringBoot项目。我习惯用Spring Initializr,选上这几个核心依赖:Spring Web、Lombok(简化实体类代码)、Spring Configuration Processor(让配置提示更友好)。注意,JDK版本要选17或以上,因为SpringBoot 3.x要求最低JDK 17。
项目创建好后,打开pom.xml文件,添加Elasticsearch 8.x官方Java客户端和其他必要的依赖。这里有个大坑要注意:ES 8.x的Java客户端和Spring Boot自带的spring-boot-starter-data-elasticsearch starter不兼容,后者内部还是用的老版本客户端。所以我们必须手动引入官方的elasticsearch-java。
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.12.0</version> <!-- 版本号最好和你的ES服务保持一致 -->
</dependency>
<!-- 新客户端底层依赖Jackson和特定的JSON处理API -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
</dependency>
<!-- 可选:如果你需要更复杂的JSON处理,可以引入这个 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>8.12.0</version>
</dependency>
依赖加好后,我建议你立刻运行一下mvn clean compile,确保没有依赖冲突。我遇到过好几次因为Jackson版本不对导致序列化出错的问题,提前检查能省下不少调试时间。
3. 配置与连接:打造健壮的ES客户端Bean
依赖搞定,接下来就是怎么让SpringBoot认识我们的ES服务了。这一步的核心是创建一个被Spring管理的ElasticsearchClient Bean。
3.1 在application.yml中灵活配置连接信息
我不喜欢把配置硬编码在Java代码里,所以先在application.yml(或application.properties)里定义好连接参数。这里我支持配置多个ES节点地址,用逗号分隔,为以后接入集群留好扩展性。
spring:
elasticsearch:
# 支持单节点或集群,多个地址用逗号分隔,例如:192.168.1.100:9200,192.168.1.101:9200
uris: localhost:9200
# 如果ES开启了安全认证(生产环境必须),在这里配置用户名密码
username: elastic
password: your_password_here
# 连接和读写超时设置,根据网络状况调整
connection-timeout: 5000ms
socket-timeout: 60000ms
3.2 编写配置类,注入功能完备的客户端
配置读进来了,现在写一个配置类来创建客户端Bean。这里面的代码稍微有点多,但每一行都有它的作用,我带你仔细看看。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
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.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import javax.net.ssl.SSLContext;
import java.util.Arrays;
@Configuration
public class ElasticSearchConfig {
@Value("${spring.elasticsearch.uris}")
private String hosts;
@Value("${spring.elasticsearch.username:}")
private String userName;
@Value("${spring.elasticsearch.password:}")
private String passWord;
@Bean(name = "elasticsearchClient")
public ElasticsearchClient elasticsearchClient() {
// 1. 将配置的字符串地址,转换为HttpHost数组
HttpHost[] httpHosts = parseHosts();
// 2. 构建最底层的RestClient Builder
RestClientBuilder restClientBuilder = RestClient.builder(httpHosts);
// 3. 配置请求超时时间(重要!避免慢查询拖死线程)
restClientBuilder.setRequestConfigCallback(requestConfigBuilder ->
requestConfigBuilder
.setConnectTimeout(5000) // 连接超时5秒
.setSocketTimeout(60000) // socket读写超时60秒
);
// 4. 配置HTTP客户端,这里主要处理认证
restClientBuilder.setHttpClientConfigCallback(httpClientBuilder -> {
HttpAsyncClientBuilder builderToReturn = httpClientBuilder;
// 如果配置了用户名密码,就设置基础认证
if (StringUtils.hasText(userName) && StringUtils.hasText(passWord)) {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(userName, passWord)
);
builderToReturn.setDefaultCredentialsProvider(credentialsProvider);
}
// 如果你的ES集群使用了自签名证书,需要在这里配置SSLCo


3956

被折叠的 条评论
为什么被折叠?



