分布式专题——49 SpringBoot整合ElasticSearch8.x实战
1 Spring Data ElasticSearch 介绍
-
Spring Data Elasticsearch 是基于 Spring Data API 构建的框架,它的核心作用是简化 Elasticsearch 的操作——将 Elasticsearch 原始的客户端 API 进行封装,为 Elasticsearch 项目提供集成搜索引擎的能力;
-
它的关键功能包括:
-
以**POJO(Plain Old Java Object,普通 Java 对象)**为中心的模型,实现与 Elasticsearch 交互文档的便捷操作;
-
帮助开发者轻松编写存储索引库的数据访问层,降低 Elasticsearch 数据操作的技术门槛;
-
-
官网:Spring Data Elasticsearch;

2 SpringBoot 整合 Spring Data ElasticSearch
2.1 版本选型
-
ElasticSearch 8.14.x 对应依赖 Spring Data Elasticsearch 5.3.x,对应Spring6.1.x,Spring Boot版本可以选择3.3.x;

2.2 引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
-
如果 SpringBoot 版本选择的是3.3.2,那么对应的 Spring Data ElasticSearch 为5.3.2;

2.3 配置 ElasticSearch
-
SpringBoot中有两种配置 ElasticSearch 的方式,选择一种即可;
-
方式1:
yaml文件配置spring:elasticsearch:uris: http://localhost:9200connection-timeout: 3s -
方式2:
@Configuration配置@Configuration public class MyESClientConfig extends ElasticsearchConfiguration {@Overridepublic ClientConfiguration clientConfiguration() {return ClientConfiguration.builder().connectedTo("localhost:9200").build();} }
2.4 Java代码实现
2.4.1 方式1:ElasticsearchRepository
-
ElasticsearchRepository是 Spring Data Elasticsearch 中的核心接口,用于简化 Elasticsearch 集群的CRUD(增删改查)操作及高级搜索功能集成。它采用声明式编程模型,让开发者无需直接编写复杂的 REST API 调用代码,即可完成数据持久化操作; -
创建实体类:
@Data @AllArgsConstructor @NoArgsConstructor @Document(indexName = "employees") public class Employee {@Idprivate Long id;@Field(type= FieldType.Keyword)private String name;private int sex;private int age;@Field(type= FieldType.Text,analyzer="ik_max_word")private String address;private String remark; } -
实现 ElasticsearchRepository 接口(EmployeeRepository)
@Repository // 标记该接口为数据访问层组件,让 Spring 能够扫描并管理 public interface EmployeeRepository extends ElasticsearchRepository<Employee, Long> {// 自定义查询方法。Spring Data支持方法名语义解析,只需按规则命名(如findBy+字段名),框架会自动生成对应的查询逻辑List<Employee> findByName(String name); } -
测试:
@Autowired EmployeeRepository employeeRepository;@Test public void testDocument() {Employee employee = new Employee(10L, "fox666", 1, 32, "长沙麓谷", "java architect");//插入文档employeeRepository.save(employee);//根据id查询Optional<Employee> result = employeeRepository.findById(10L);if (!result.isEmpty()){log.info(String.valueOf(result.get()));}//根据name查询List<Employee> list = employeeRepository.findByName("fox666");if(!list.isEmpty()){log.info(String.valueOf(list.get(0)));}} -
更多实现:Query methods :: Spring Data Elasticsearch。
2.4.2 方式2:ElasticsearchTemplate
2.4.2.1 概述
-
ElasticsearchTemplate是 Spring Data Elasticsearch 提供的模板类,封装了操作 Elasticsearch 的便捷方法,覆盖索引管理、文档CRUD、高级查询等底层操作和高级功能,让开发者无需直接处理 Elasticsearch 的 REST API 细节;@Autowired ElasticsearchTemplate elasticsearchTemplate; -
ElasticSearch 客户端演进历史
-
Transport 客户端:基于 Elasticsearch 0.9 版本(2010年),是早期的客户端方案,后来因性能和兼容性问题被弃用;
-
REST 客户端:
- Low Level REST 客户端:Elasticsearch 5.0 版本(2016年)推出,提供基础的 HTTP 级 REST 通信;
- High Level REST 客户端(
RestHighLevelClient):Elasticsearch 5.6.0 版本(2017年)推出,对 Low Level 客户端封装,支持更易用的 API,但从 Java Rest Client 7.15.0 版本开始被官方标记为废弃;
-
Java API 客户端(
ElasticsearchClient):Elasticsearch 7.16 版本(2021年)推出,是官方推荐的新客户端。Spring Data Elasticsearch 对其进一步封装,形成了新的ElasticsearchTemplate;

-
2.4.2.1 索引操作
@Autowired
ElasticsearchTemplate elasticsearchTemplate;@Test
public void testCreateIndex(){// 检查索引是否存在boolean exist = elasticsearchTemplate.indexOps(Employee.class).exists();// 若索引存在,则删除if(exist){elasticsearchTemplate.indexOps(Employee.class).delete();}// 创建索引// 配置settingsMap<String, Object> settings = new HashMap<>();settings.put("number_of_shards",1); // 配置索引的分片数,影响索引的性能settings.put("number_of_replicas",1); // 配置索引的副本数,影响索引的高可用性// 配置mappingString json = "{\n" +" \"properties\": {\n" +" \"_class\": {\n" +" \"type\": \"text\",\n" +" \"fields\": {\n" +" \"keyword\": {\n" +" \"type\": \"keyword\",\n" +" \"ignore_above\": 256\n" +" }\n" +" }\n" +" },\n" +" \"address\": {\n" +" \"type\": \"text\",\n" +" \"fields\": {\n" +" \"keyword\": {\n" +" \"type\": \"keyword\"\n" +" }\n" +" },\n" +" \"analyzer\": \"ik_max_word\"\n" +" },\n" +" \"age\": {\n" +" \"type\": \"integer\"\n" +" },\n" +" \"id\": {\n" +" \"type\": \"long\"\n" +" },\n" +" \"name\": {\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"remark\": {\n" +" \"type\": \"text\",\n" +" \"fields\": {\n" +" \"keyword\": {\n" +" \"type\": \"keyword\"\n" +" }\n" +" },\n" +" \"analyzer\": \"ik_smart\"\n" +" },\n" +" \"sex\": {\n" +" \"type\": \"integer\"\n" +" }\n" +" }\n" +" }";Document mapping = Document.parse(json);// 创建索引elasticsearchTemplate.indexOps(Employee.class).create(settings,mapping);// 查看索引mappings信息Map<String, Object> mappings = elasticsearchTemplate.indexOps(Employee.class).getMapping();log.info(mappings.toString());
}
2.4.2.3 批量文档插入
@Autowired
ElasticsearchTemplate elasticsearchTemplate;@Test
public void testBulkBatchInsert(){// 构建 Employee 数据列表List<Employee> employees = new ArrayList<>();employees.add(new Employee(2L,"张三",1,25,"广州天河公园","java developer"));employees.add(new Employee(3L,"李四",1,28,"广州荔湾大厦","java assistant"));employees.add(new Employee(4L,"小红",0,26,"广州白云山公园","php developer"));// 通过 IndexQuery 封装每条数据的 ID 和 JSON 内容List<IndexQuery> bulkInsert = new ArrayList<>();for (Employee employee : employees) {IndexQuery indexQuery = new IndexQuery();indexQuery.setId(String.valueOf(employee.getId()));String json = JSONObject.toJSONString(employee);indexQuery.setSource(json);bulkInsert.add(indexQuery);}// 调用 elasticsearchTemplate.bulkIndex() 实现批量插入,提升大数据量下的写入效率elasticsearchTemplate.bulkIndex(bulkInsert,Employee.class);
}
2.4.2.4 文档CRUD
@Autowired
ElasticsearchTemplate elasticsearchTemplate;@Test
public void testDocument(){// 删除文档:调用 delete() 根据 ID 删除指定文档elasticsearchTemplate.delete(String.valueOf(12L),Employee.class);// 插入文档:调用 save() 插入单条 Employee 文档Employee employee = new Employee(12L,"张三三",1,25,"广州天河公园","java developer");elasticsearchTemplate.save(employee);// 查询文档:调用 get() 根据 ID 查询文档并打印结果Employee emp = elasticsearchTemplate.get(String.valueOf(12L),Employee.class);log.info(String.valueOf(emp));
}
2.4.2.5 精确查询
@Autowired
ElasticsearchTemplate elasticsearchTemplate;@Test
public void testQueryDocument(){// 条件查询:查询姓名为张三的员工信息/* GET /employee/_search{"query": {"term": {"name": {"value": "张三"}}}}*/// 方式1:直接编写 JSON 格式的查询语句// Query query = new StringQuery("{\n" +// " \"term\": {\n" +// " \"name\": {\n" +// " \"value\": \"张三\"\n" +// " }\n" +// " }\n" +// " }");// 方式2:通过编程式 API 构建查询,可读性更强Query query = NativeQuery.builder().withQuery(q -> q.term(t -> t.field("name").value("张三"))).build();SearchHits<Employee> search = elasticsearchTemplate.search(query, Employee.class);// 调用 search() 执行查询List<SearchHit<Employee>> searchHits = search.getSearchHits();// 解析返回的 SearchHits 结果for (SearchHit hit: searchHits){log.info("返回结果:"+hit.toString());}
}
2.4.2.6 分词匹配查询
@Autowired
ElasticsearchTemplate elasticsearchTemplate;@Test
public void testMatchQueryDocument(){// 条件查询:查询地址中至少匹配“广州”“公园”两个词的员工/*GET /employee/_search{"query": {"match": {"address": {"query": "广州公园","minimum_should_match": 2}}}}*/// 方式1:直接编写 JSON 格式的查询语句// Query query = new StringQuery("{\n" +// " \"match\": {\n" +// " \"address\": {\n" +// " \"query\": \"广州公园\",\n" +// " \"minimum_should_match\": 2\n" +// " }\n" +// " }\n" +// " }");// 方式2:通过编程式 API 构建查询,可读性更强Query query = NativeQuery.builder().withQuery(q -> q.match(m -> m.field("address").query("广州公园").minimumShouldMatch("2"))) // 通过 minimumShouldMatch 配置最少匹配词数,满足模糊查询的业务场景.build();SearchHits<Employee> search = elasticsearchTemplate.search(query, Employee.class);List<SearchHit<Employee>> searchHits = search.getSearchHits();for (SearchHit hit: searchHits){log.info("返回结果:"+hit.toString());}
}
2.4.2.7 分页、排序、高亮查询
@Autowired
ElasticsearchTemplate elasticsearchTemplate;@Test
public void testQueryDocument3(){// 分页排序高亮:结合分页、排序、高亮实现复杂查询,查询 remark 中含 “JAVA” 的员工,按年龄倒序取前 3 条,并对匹配内容高亮显示/*GET /employee/_search{"from": 0,"size": 3,"query": {"match": {"remark": {"query": "JAVA"}}},"highlight": {"pre_tags": ["<font color='red'>"],"post_tags": ["<font/>"],"require_field_match": "false","fields": {"*":{}}},"sort": [{"age": {"order": "desc"}}]}*///第一步:构建查询语句Query query = new StringQuery("{\n" +" \"match\": {\n" +" \"remark\": {\n" +" \"query\": \"JAVA\"\n" +" }\n" +" }\n" +" }");// 分页:通过 PageRequest.of(0, 3) 配置页码和每页条数query.setPageable(PageRequest.of(0, 3));// 排序:通过 Sort.by(Order.desc("age")) 按年龄倒序query.addSort(Sort.by(Order.desc("age")));// 高亮:通过 HighlightParameters 配置高亮标签(如 <font color='red'>)和作用字段,让匹配结果更直观HighlightField highlightField = new HighlightField("*");HighlightParameters highlightParameters = new HighlightParameters.HighlightParametersBuilder().withPreTags("<font color='red'>").withPostTags("<font/>").withRequireFieldMatch(false).build();Highlight highlight = new Highlight(highlightParameters,Arrays.asList(highlightField));HighlightQuery highlightQuery = new HighlightQuery(highlight,Employee.class);query.setHighlightQuery(highlightQuery);SearchHits<Employee> search = elasticsearchTemplate.search(query, Employee.class);List<SearchHit<Employee>> searchHits = search.getSearchHits();for (SearchHit hit: searchHits){log.info("返回结果:"+hit.toString());}
}
2.4.2.8 布尔查询
@Autowired
ElasticsearchTemplate elasticsearchTemplate;@Test
public void testBoolQueryDocument(){// 实现 bool 查询(多条件组合),查询地址含 “广州” 且 remark 含 “java” 的员工// must 子句表示“必须同时满足”,同样支持 StringQuery 和 NativeQuery 两种构建方式,满足多条件组合的复杂业务查询场景/*GET /employee/_search{"query": {"bool": {"must": [{"match": {"address": "广州"}},{"match": {"remark": "java"}}]}}}*/// 方式1:直接编写 JSON 格式的查询语句// Query query = new StringQuery("{\n" +// " \"bool\": {\n" +// " \"must\": [\n" +// " {\n" +// " \"match\": {\n" +// " \"address\": \"广州\"\n" +// " }\n" +// " },{\n" +// " \"match\": {\n" +// " \"remark\": \"java\"\n" +// " }\n" +// " }\n" +// " ]\n" +// " }\n" +// " }");// 方式2:通过编程式 API 构建查询,可读性更强Query query = NativeQuery.builder().withQuery(q -> q.bool(m -> m.must(QueryBuilders.match( q1 -> q1.field("address").query("广州")),QueryBuilders.match( q2 -> q2.field("remark").query("java"))))).build();SearchHits<Employee> search = elasticsearchTemplate.search(query, Employee.class);List<SearchHit<Employee>> searchHits = search.getSearchHits();for (SearchHit hit: searchHits){log.info("返回结果:"+hit.toString());}
}
2.4.3 使用ElasticsearchClient
2.4.3.1 概述
-
ElasticsearchClient是 Elasticsearch 7.16+ 版本推出的官方推荐客户端,替代了 deprecated 的RestHighLevelClient,提供类型安全的 API 设计,支持链式调用构建请求,更贴合 Elasticsearch 的 DSL 语法;@Autowired ElasticsearchTemplate elasticsearchTemplate;
2.4.3.2 索引操作
@Autowired
ElasticsearchClient elasticsearchClient;String indexName = "employee_demo";@Test
public void testCreateIndex() throws IOException {// 检查索引是否存在BooleanResponse exist = elasticsearchClient.indices().exists(e->e.index(indexName));// 若索引存在,则删除if(exist.value()){elasticsearchClient.indices().delete(d->d.index(indexName));}// 创建索引elasticsearchClient.indices().create(c->c.index(indexName) // 指定索引名// 配置索引的分片数和副本数.settings(s->s.numberOfShards("1").numberOfReplicas("1"))// name 字段为 keyword 类型(不分词,适合精确匹配).mappings(m-> m.properties("name", p->p.keyword(k->k))// sex 字段为 long 类型(整数存储).properties("sex", p->p.long_(l->l))// address字段为text 类型,使用ik_max_word中文分词器(最大化分词,适合全文检索).properties("address", p->p.text(t->t.analyzer("ik_max_word")))));// 查询索引GetIndexResponse getIndexResponse = elasticsearchClient.indices().get(g -> g.index(indexName));log.info(getIndexResponse.result().toString());
}
2.4.3.3 批量插入文档
// 使用 BulkOperation 封装多条文档操作,通过 elasticsearchClient.bulk(...) 一次性提交,提升大量数据写入效率
@Autowired
ElasticsearchClient elasticsearchClient;String indexName = "employee_demo";@Test
public void testBulkBatchInsert() throws IOException {// 构建 Employee 实体列表(含 id、name、sex 等字段)List<Employee> employees = new ArrayList<>();employees.add(new Employee(2L,"张三",1,25,"广州天河公园","java developer"));employees.add(new Employee(3L,"李四",1,28,"广州荔湾大厦","java assistant"));employees.add(new Employee(4L,"小红",0,26,"广州白云山公园","php developer"));// 为每个实体创建 BulkOperation:// 通过 BulkOperation.Builder().create(...) 定义 “创建文档” 操作// 指定文档 ID 和实体对象(document(employee) 自动序列化实体为 JSONList<IndexQuery> bulkInsert = new ArrayList<>();for (Employee employee : employees) {IndexQuery indexQuery = new IndexQuery();indexQuery.setId(String.valueOf(employee.getId()));String json = JSONObject.toJSONString(employee);indexQuery.setSource(json);bulkInsert.add(indexQuery);}List<BulkOperation> list = new ArrayList<>();for (Employee employee : employees) {BulkOperation bulkOperation = new BulkOperation.Builder().create(c->c.id(String.valueOf(employee.getId())).document(employee)).build();list.add(bulkOperation);}// 执行批量插入,operations(list) 传入批量操作列表。elasticsearchClient.bulk(b->b.index(indexName).operations(list));
}
2.4.3.4 单文档插入
@Autowired
ElasticsearchClient elasticsearchClient;String indexName = "employee_demo";@Test
public void testDocument() throws IOException {Employee employee = new Employee(12L,"张三三",1,25,"广州天河公园","java developer");// 通过 IndexRequest 构建插入请求,指定索引名、文档 ID 和实体对象,调用 elasticsearchClient.index(request) 执行插入IndexRequest<Employee> request = // 链式配置请求参数,document(employee) 自动将实体序列化为 Elasticsearch 文档IndexRequest.of(i -> i.index(indexName).id(employee.getId().toString()).document(employee));IndexResponse response = elasticsearchClient.index(request);log.info("response:"+response);
}
2.4.3.5 单条件查询
@Autowired
ElasticsearchClient elasticsearchClient;String indexName = "employee_demo";@Test
public void testQuery() throws IOException {// 构建 SearchRequest:通过 SearchRequest.of(s -> s.index(indexName).query(...)) 配置查询索引和查询条件SearchRequest searchRequest = SearchRequest.of(s -> s.index(indexName)// 定义查询逻辑:对 name 字段执行 match 匹配(因 name 是 keyword 类型,实际等价于精确匹配).query(q -> q.match(m -> m.field("name").query("张三三"))));log.info("构建的DSL语句:"+ searchRequest.toString());SearchResponse<Employee> searchResponse = elasticsearchClient.search(searchRequest, Employee.class);// 通过 hits().hits() 获取命中的文档列表,Hit::source 提取文档源数据(自动反序列化为 Employee 对象)List<Hit<Employee>> hits = searchResponse.hits().hits();hits.stream().map(Hit::source).forEach(employee -> {log.info("员工信息:"+employee);});
}
2.4.3.6 布尔组合查询
- 实现 bool 多条件查询:查询
address含“广州”且remark含“java”的文档,对应 Elasticsearch 的 bool-must 逻辑(所有条件必须满足)。 - 步骤解析:
- 构建
BoolQuery:通过BoolQuery.Builder()创建布尔查询构建器,调用must(...)方法添加子查询(每个must对应一个条件):must(m->m.match(q->q.field("address").query("广州"))):address字段匹配“广州”。must(m->m.match(q->q.field("remark").query("java"))):remark字段匹配“java”。
- 构建
SearchRequest:将布尔查询设置为查询条件(query(q->q.bool(boolQueryBuilder.build())))。 - 执行查询并解析:同单条件查询,提取
hit.source()得到匹配的Employee实体。
- 构建
@Autowired
ElasticsearchClient elasticsearchClient;@Test
public void testBoolQueryDocument() throws IOException {// 条件查询:查询 address 含 “广州” 且 remark 含 “java” 的文档,对应 Elasticsearch 的 bool-must 逻辑(所有条件必须满足)/*GET /employee/_search{"query": {"bool": {"must": [{"match": {"address": "广州"}},{"match": {"remark": "java"}}]}}}*/// 构建 BoolQuery:通过 BoolQuery.Builder() 创建布尔查询构建器,调用 must(...) 方法添加子查询(每个 must 对应一个条件)BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();boolQueryBuilder.must(m->m.match(q->q.field("address").query("广州"))) // address 字段匹配 “广州”.must(m->m.match(q->q.field("remark").query("java"))); // remark 字段匹配 “java”// 构建 SearchRequestSearchRequest searchRequest = new SearchRequest.Builder().index("employee").query(q->q.bool(boolQueryBuilder.build())) // 将布尔查询设置为查询条件.build();// 执行查询SearchResponse<Employee> searchResponse = elasticsearchClient.search(searchRequest, Employee.class);// 解析List<Hit<Employee>> list = searchResponse.hits().hits();for(Hit<Employee> hit: list){log.info(String.valueOf(hit.source()));}
}
