SpringBoot + Elasticsearch 配置详解
本文将详细介绍SpringBoot与Elasticsearch的整合配置,包括版本对应关系、依赖引入、连接配置、客户端设置、实体映射、Repository接口使用等内容,帮助开发者快速实现Elasticsearch搜索引擎功能。
一、版本对应关系
SpringBoot与Elasticsearch版本对应非常重要,不匹配的版本可能导致兼容性问题:
| SpringBoot版本 | Elasticsearch版本 |
|---|---|
| 2.6.x | 7.16.x |
| 2.5.x | 7.12.x |
| 2.4.x | 7.9.x |
| 2.3.x | 7.6.x |
二、依赖配置
在pom.xml中添加Spring Data Elasticsearch依赖:
<!-- Spring Data Elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 或者直接使用Elasticsearch高级客户端 -->
<!-- <dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.16.2</version> <!-- 与SpringBoot版本匹配 -->
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.16.2</version>
</dependency> -->
三、基本配置 (application.yml)
在application.yml中配置Elasticsearch连接信息:
1. Spring Boot 2.6.x+ 配置方式
spring:
elasticsearch:
# Elasticsearch 服务地址(多个地址用逗号分隔)
uris: http://localhost:9200,http://localhost:9201
# 连接超时时间
connection-timeout: 10s
# 数据读取超时时间
socket-timeout: 30s
# 用户名(如果启用了安全认证)
username: elastic
# 密码(如果启用了安全认证)
password: 123456
# 是否启用SSL
ssl:
enabled: false
2. 旧版本配置方式
spring:
data:
elasticsearch:
cluster-name: elasticsearch # 集群名称
cluster-nodes: localhost:9300 # 集群节点
repositories:
enabled: true # 启用仓库
四、RestHighLevelClient 配置类
创建自定义配置类,配置Elasticsearch客户端:
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticsearchConfig {
@Value("${spring.elasticsearch.uris:http://localhost:9200}")
private String uris;
@Bean
public RestHighLevelClient restHighLevelClient() {
// 解析uris字符串,支持多个地址
String[] hosts = uris.split(",");
HttpHost[] httpHosts = new HttpHost[hosts.length];
for (int i = 0; i < hosts.length; i++) {
String host = hosts[i].trim();
// 提取主机和端口
String hostWithoutProtocol = host.replace("http://", "").replace("https://", "");
String[] parts = hostWithoutProtocol.split(":");
String hostname = parts[0];
int port = parts.length > 1 ? Integer.parseInt(parts[1]) : 9200;
String scheme = host.startsWith("https") ? "https" : "http";
httpHosts[i] = new HttpHost(hostname, port, scheme);
}
return new RestHighLevelClient(
RestClient.builder(httpHosts)
// 可以在这里添加认证信息、连接池配置等
// .setHttpClientConfigCallback(httpClientBuilder ->
// httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider))
);
}
}
使用 AbstractElasticsearchConfiguration
另一种配置方式,使用Spring Data Elasticsearch提供的抽象类:
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
import java.time.Duration;
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.withConnectTimeout(Duration.ofSeconds(10))
.withSocketTimeout(Duration.ofSeconds(30))
// 如果需要认证
// .withBasicAuth("elastic", "123456")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
五、实体类映射
使用Spring Data Elasticsearch注解定义实体类与索引的映射关系:
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
import java.util.List;
@Data
@Document(indexName = "users", shards = 3, replicas = 1) // 索引名称、分片数、副本数
public class User {
@Id // 文档ID
private String id;
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") // 全文本字段,使用中文分词器
private String username;
@Field(type = FieldType.Keyword) // 关键字字段,不分词
private String email;
@Field(type = FieldType.Integer)
private Integer age;
@Field(type = FieldType.Date, format = DateFormat.date_time) // 日期字段
private Date createdAt;
@Field(type = FieldType.Nested) // 嵌套对象字段
private List<Order> orders;
@Data
public static class Order {
private String orderId;
private Double amount;
private Date orderDate;
}
}
六、Repository接口
Spring Data Elasticsearch支持Repository模式,简化数据访问层开发:
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserRepository extends ElasticsearchRepository<User, String> {
// 基于方法名自动生成查询
Optional<User> findByUsername(String username);
List<User> findByAgeBetween(Integer minAge, Integer maxAge);
List<User> findByUsernameContaining(String keyword);
// 分页查询
org.springframework.data.domain.Page<User> findByAgeGreaterThanEqual(Integer age,
org.springframework.data.domain.Pageable pageable);
}
七、使用RestHighLevelClient操作
对于复杂查询,使用RestHighLevelClient:
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class ElasticsearchService {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Autowired
private UserRepository userRepository;
// 创建或更新文档
public String saveDocument(String index, String id, Map<String, Object> document) throws IOException {
IndexRequest request = new IndexRequest(index).id(id).source(document);
IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
return response.getId();
}
// 复杂查询示例
public List<User> searchUsers(String keyword, Integer minAge, Integer maxAge) throws IOException {
SearchRequest searchRequest = new SearchRequest("users");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键字搜索
if (keyword != null && !keyword.isEmpty()) {
boolQuery.must(QueryBuilders.multiMatchQuery(keyword, "username", "email"));
}
// 年龄范围过滤
BoolQueryBuilder filterQuery = QueryBuilders.boolQuery();
if (minAge != null) {
filterQuery.must(QueryBuilders.rangeQuery("age").gte(minAge));
}
if (maxAge != null) {
filterQuery.must(QueryBuilders.rangeQuery("age").lte(maxAge));
}
boolQuery.filter(filterQuery);
sourceBuilder.query(boolQuery);
sourceBuilder.from(0);
sourceBuilder.size(100);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
List<User> users = new ArrayList<>();
for (SearchHit hit : searchResponse.getHits().getHits()) {
// 将搜索结果转换为实体类
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
// 这里可以手动映射或使用ObjectMapper转换
// User user = objectMapper.convertValue(sourceAsMap, User.class);
// users.add(user);
}
return users;
}
// 使用Repository进行简单查询
public List<User> findUsersByUsername(String username) {
return userRepository.findByUsernameContaining(username);
}
}
八、控制器实现
创建RESTful API控制器:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserRepository userRepository;
@Autowired
private ElasticsearchService elasticsearchService;
// 保存用户
@PostMapping
public User saveUser(@RequestBody User user) {
return userRepository.save(user);
}
// 根据ID获取用户
@GetMapping("/{id}")
public Optional<User> getUserById(@PathVariable String id) {
return userRepository.findById(id);
}
// 搜索用户
@GetMapping("/search")
public List<User> searchUsers(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge) {
try {
return elasticsearchService.searchUsers(keyword, minAge, maxAge);
} catch (Exception e) {
e.printStackTrace();
return List.of();
}
}
// 分页查询
@GetMapping("/page")
public Page<User> getUsersByPage(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return userRepository.findAll(PageRequest.of(page, size));
}
// 删除用户
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable String id) {
userRepository.deleteById(id);
}
}
九、索引操作
创建索引
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class IndexOperation {
@Autowired
private RestHighLevelClient restHighLevelClient;
// 创建索引
public boolean createIndex(String indexName) throws IOException {
CreateIndexRequest request = new CreateIndexRequest(indexName);
// 设置索引参数
request.settings(Settings.builder()
.put("number_of_shards", 3)
.put("number_of_replicas", 1)
);
// 设置映射
String mapping = "{\n" +
" \"properties\": {\n" +
" \"username\": {\"type\": \"text\", \"analyzer\": \"ik_max_word\"},\n" +
" \"email\": {\"type\": \"keyword\"},\n" +
" \"age\": {\"type\": \"integer\"},\n" +
" \"createdAt\": {\"type\": \"date\"}\n" +
" }\n" +
"}";
request.mapping(mapping, XContentType.JSON);
CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
return response.isAcknowledged();
}
}
十、常见问题与解决方案
1. 版本兼容性问题
- 确保SpringBoot版本与Elasticsearch版本匹配
- 检查依赖版本是否一致
2. 中文分词配置
安装IK分词器插件到Elasticsearch:
- 下载与Elasticsearch版本匹配的IK分词器
- 解压到Elasticsearch的plugins目录
- 重启Elasticsearch服务
3. 连接超时问题
spring:
elasticsearch:
uris: http://localhost:9200
connection-timeout: 30s # 增加连接超时时间
socket-timeout: 60s # 增加读取超时时间
4. 安全认证配置
如果Elasticsearch启用了安全认证,需要配置用户名和密码:
spring:
elasticsearch:
uris: https://localhost:9200
username: elastic
password: changeme
ssl:
enabled: true
5. 索引不存在问题
在操作前检查索引是否存在,或者配置自动创建索引:
@Configuration
public class ElasticsearchConfig {
@Bean
public ElasticsearchOperations elasticsearchTemplate(RestHighLevelClient client) {
ElasticsearchRestTemplate template = new ElasticsearchRestTemplate(client);
template.setIndexOperationsInterceptor(indexOperations ->
new IndexOperationsAdapter(indexOperations) {
@Override
public boolean create() {
if (!exists()) {
return super.create();
}
return true;
}
}
);
return template;
}
}
通过以上配置和示例,您可以成功实现SpringBoot与Elasticsearch的整合,并根据项目需求进行灵活的配置和使用。