Elasticsearch在搜索场景中的地位毋庸置疑,而Java生态里与之配合最顺手的工具莫过于Spring Data Elasticsearch。它把Elasticsearch的REST客户端封装成Spring Data风格的Repository抽象,让你像操作数据库一样操作索引文档。这篇文章从依赖配置讲起,逐步覆盖实体映射、三种数据访问方式以及分页高亮等进阶用法,把整合过程中最容易踩的坑也说清楚。

一、环境准备与依赖配置
整合的第一步是引入依赖。Spring Boot项目只需要在pom.xml中加入spring-boot-starter-data-elasticsearch,版本跟随Spring Boot的依赖管理,不需要手动指定。需要注意的是,Spring Data Elasticsearch 5.x对应Elasticsearch 8.x服务端,4.x对应7.x,两者不能混用,这是版本踩坑的重灾区。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>接着在application.yml中配置连接地址。默认情况下Spring Data使用REST方式访问9200端口,9300的TransportClient早已废弃,老项目升级时一定要把9300的配置去掉,否则启动会直接报错。
spring:
elasticsearch:
uris: http://127.0.0.1:9200
connection-timeout: 5s
socket-timeout: 30s
username: elastic
password: your_password如果Elasticsearch开启了安全认证(8.x默认开启),username和password两项必须配置,否则连接时会收到401错误。配置完成后,Spring容器会自动注入ElasticsearchOperations和各Repository实例,开箱即用。
二、实体类映射与索引管理
实体映射通过@Document注解完成,作用类似JPA的@Entity。indexName指定索引名,createBoolean属性控制是否自动建索引,shards和replicas设置分片与副本数。字段层面用@Field声明类型和分词器,中文搜索记得显式指定ik分词器,默认的standard分词器对中文是逐字切分,搜索效果会很差。
@Document(indexName = "product", createBoolean = true, shards = 3, replicas = 1)
public class Product {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String name;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Double)
private Double price;
// 省略getter和setter
}这里有个细节值得注意:Text类型会分词,适合全文检索;Keyword类型不分词,适合精确匹配、聚合和排序。把商品分类写成Text会导致按分类筛选时搜不到结果,这是新手常见的误区。写入数据时,Repository的save方法会自动完成文档序列化,若索引不存在会按注解定义的结构自动创建。另外,实体结构变更后索引mapping不会自动更新,需要删除索引重建或用别名方案做平滑迁移。
三、三种数据访问方式对比
Spring Data Elasticsearch提供了三层数据访问能力,各自适用场景不同。第一层是方法命名查询,只要在接口中按规则定义方法名,框架就会自动生成查询DSL。例如findByNameContaining(String keyword)会生成wildcard查询,简单场景下开发效率极高。
public interface ProductRepository extends ElasticsearchRepository<Product, Long> {
List<Product> findByNameContaining(String keyword);
Page<Product> findByCategoryAndPriceBetween(String category,
Double min, Double max, Pageable pageable);
}第二层是@Query注解,允许手写原生DSL。当查询逻辑复杂到命名约定无法表达时,比如多字段should匹配、嵌套bool组合,直接写JSON风格的查询字符串是最直观的方式。参数用?0、?1占位符或:name命名参数绑定。
public interface ProductRepository extends ElasticsearchRepository<Product, Long> {
@Query("{\"bool\":{\"must\":[{\"multi_match\":{\"query\":\"?0\",\"fields\":[\"name\",\"category\"]}}]}}")
Page<Product> searchByKeyword(String keyword, Pageable pageable);
}第三层是ElasticsearchRestTemplate(新版本中为ElasticsearchTemplate),它提供了最完整的能力,包括高亮、聚合、批量操作和原生查询构建器。高亮显示是搜索场景的刚需,通过NativeQuery配合HighlightQuery可以拿到带高亮标签的结果。三种方式的建议是:简单查询用命名方法,中等复杂度用@Query,涉及高亮聚合的复杂搜索直接上Template,不必强行只用一种。
@Autowired
private ElasticsearchOperations operations;
public List<Product> searchWithHighlight(String keyword) {
HighlightQuery highlight = new HighlightQuery(
HighlightParameters.builder()
.withPreTags("<em>").withPostTags("</em>")
.build(), null);
NativeQuery query = NativeQuery.builder()
.withQuery(q -> q.multiMatch(m -> m
.query(keyword).fields("name", "category")))
.withHighlightQuery(highlight)
.build();
SearchHits<Product> hits = operations.search(query, Product.class);
return hits.getSearchHits().stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}四、分页排序与常见坑点
分页直接复用Spring Data的Pageable,传入页码从0开始这一点和数据库一致。返回值用Page可以拿到总命中数,但要注意深分页问题:Elasticsearch默认max_result_window是10000,from加size超过该值会报错。解决方案是调大配置或改用search_after游标方式,对于后台管理的翻页场景,尽量避免无限制地跳页。
Pageable pageable = PageRequest.of(0, 20,
Sort.by(Sort.Direction.DESC, "price"));
Page<Product> page = productRepository
.findByCategoryAndPriceBetween("手机", 1000.0, 5000.0, pageable);除了深分页,还有几个坑需要留意。一是索引mapping一旦创建就不可修改字段类型,实体类改了类型要重建索引;二是@Field漏写type时框架会推断类型,Text和推断成Keyword都可能发生,最好显式声明;三是日期字段格式问题,默认只支持ISO格式,自定义格式需要设置format属性,例如format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss";四是聚合结果无法直接映射到实体,需要用Template拿到原始Aggregate对象自行解析。理解了这些边界,Spring Data Elasticsearch在绝大多数搜索需求下都能胜任,配合Spring Boot的自动装配,从零搭建一个搜索服务通常半天就能完成。
ElasticsearchSpring Data ElasticsearchSpring Boot整合修改时间:2026-09-09 04:46:35