环境说明: SpringBoot 3.x、Java17、Elasticsearch 8.x、IK 分词器(版本和 ES 一致) ES8 默认开启 SSL + 账号密码,下面代码同时支持:账号密码 + 关闭 SSL(本地开发) 客户端用官方新客户端:Elasticsearch Java Client(新 API,不是老的 HighLevelRestClient)
1. 依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath/>
</parent>
<groupId>com.esdemo</groupId>
<artifactId>es-ik-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es-ik-demo</name>
<properties>
<java.version>17</java.version>
<!-- 和你的ES版本保持一致 -->
<elasticsearch.version>8.15.0</elasticsearch.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ES官方Java客户端 -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<!-- Jackson适配,SpringBoot3需要 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
2. application.yml 配置
spring:
application:
name: es-ik-demo
elasticsearch:
host: 127.0.0.1
port: 9200
username: elastic
password: elastic123456
# 本地开发关闭SSL,ES8默认是true
ssl-enabled: false
3. ES 客户端配置类 EsConfig.java
package com.esdemo.config;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
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.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EsConfig {
@Value("${elasticsearch.host}")
private String host;
@Value("${elasticsearch.port}")
private int port;
@Value("${elasticsearch.username}")
private String username;
@Value("${elasticsearch.password}")
private String password;
@Value("${elasticsearch.ssl-enabled}")
private boolean sslEnabled;
@Bean
public ElasticsearchClient elasticsearchClient() {
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(username, password));
RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, sslEnabled ? "https" : "http"))
.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
RestClient restClient = builder.build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new co.elastic.clients.json.jackson.JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
4. 实体类 Article.java(文档模型)
索引:
article,title、content 使用 IK 分词
package com.esdemo.entity;
import lombok.Data;
@Data
public class Article {
private Long id;
private String title;
private String content;
private String author;
}
5. ES 服务类 ArticleEsService.java
包含:创建索引(IK Mapping)、新增文档、删除、全文检索、高亮查询
package com.esdemo.service;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
import co.elastic.clients.json.JsonData;
import com.esdemo.entity.Article;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ArticleEsService {
private final ElasticsearchClient esClient;
private static final String INDEX_NAME = "article";
/**
* 创建索引,指定IK分词
*/
public boolean createIndex() throws IOException {
// mapping:title/content 索引用ik_max_word,搜索用ik_smart
String mappingJson = """
{
"properties": {
"id": {"type": "long"},
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"author": {"type": "keyword"}
}
}
""";
CreateIndexRequest request = CreateIndexRequest.of(b -> b
.index(INDEX_NAME)
.mappings(m -> m.withJson(mappingJson))
);
CreateIndexResponse resp = esClient.indices().create(request);
return resp.acknowledged();
}
/**
* 删除索引
*/
public boolean deleteIndex() throws IOException {
DeleteIndexRequest req = DeleteIndexRequest.of(b -> b.index(INDEX_NAME));
return esClient.indices().delete(req).acknowledged();
}
/**
* 新增/更新文档
*/
public String saveDoc(Article article) throws IOException {
IndexRequest<Article> req = IndexRequest.of(b -> b
.index(INDEX_NAME)
.id(article.getId().toString())
.document(article)
);
IndexResponse resp = esClient.index(req);
return resp.result().name();
}
/**
* 根据id删除文档
*/
public String deleteDoc(Long id) throws IOException {
DeleteRequest req = DeleteRequest.of(b -> b.index(INDEX_NAME).id(id.toString()));
DeleteResponse resp = esClient.delete(req);
return resp.result().name();
}
/**
* 全文检索:match查询,IK分词生效
*/
public List<Article> search(String keyword) throws IOException {
SearchRequest req = SearchRequest.of(b -> b
.index(INDEX_NAME)
.query(q -> q.multiMatch(m -> m
.query(keyword)
.fields("title", "content")
))
);
SearchResponse<Article> resp = esClient.search(req, Article.class);
List<Article> list = new ArrayList<>();
for (Hit<Article> hit : resp.hits().hits()) {
list.add(hit.source());
}
return list;
}
/**
* 带高亮的全文检索(前端标红关键词)
*/
public List<Article> searchHighlight(String keyword) throws IOException {
SearchRequest req = SearchRequest.of(b -> b
.index(INDEX_NAME)
.query(q -> q.multiMatch(m -> m
.query(keyword)
.fields("title", "content")
))
.highlight(h -> h
.fields("title", f -> f)
.fields("content", f -> f)
.preTags("<em style='color:red'>")
.postTags("</em>")
)
);
SearchResponse<Article> resp = esClient.search(req, Article.class);
List<Article> result = new ArrayList<>();
for (Hit<Article> hit : resp.hits().hits()) {
Article article = hit.source();
// 覆盖为高亮文本
if (hit.highlight().containsKey("title")) {
article.setTitle(hit.highlight().get("title").get(0));
}
if (hit.highlight().containsKey("content")) {
article.setContent(hit.highlight().get("content").get(0));
}
result.add(article);
}
return result;
}
}
6. Controller ArticleEsController.java
package com.esdemo.controller;
import com.esdemo.entity.Article;
import com.esdemo.service.ArticleEsService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/es/article")
@RequiredArgsConstructor
public class ArticleEsController {
private final ArticleEsService articleEsService;
@PostMapping("/createIndex")
public Boolean createIndex() throws IOException {
return articleEsService.createIndex();
}
@DeleteMapping("/deleteIndex")
public Boolean deleteIndex() throws IOException {
return articleEsService.deleteIndex();
}
@PostMapping("/save")
public String save(@RequestBody Article article) throws IOException {
return articleEsService.saveDoc(article);
}
@DeleteMapping("/{id}")
public String delete(@PathVariable Long id) throws IOException {
return articleEsService.deleteDoc(id);
}
@GetMapping("/search")
public List<Article> search(@RequestParam String keyword) throws IOException {
return articleEsService.search(keyword);
}
@GetMapping("/search/highlight")
public List<Article> searchHl(@RequestParam String keyword) throws IOException {
return articleEsService.searchHighlight(keyword);
}
}
7. 启动类
package com.esdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EsIkDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EsIkDemoApplication.class, args);
}
}
✅ 测试流程(按顺序执行)
先确认 ES 已经装好 IK 分词器,版本和 ES 一致,ES 正常启动
启动 SpringBoot 项目
创建索引:
POST http://127.0.0.1:8080/es/article/createIndex插入测试数据
POST http://127.0.0.1:8080/es/article/save
Content-Type: application/json
{
"id": 1,
"title": "南京长江大桥游玩攻略",
"content": "南京长江大桥是南京著名地标,适合亲子出游",
"author": "张三"
}
POST http://127.0.0.1:8080/es/article/save
Content-Type: application/json
{
"id": 2,
"title": "长江航运发展报告",
"content": "长江沿线城市经济发展,航运物流高速增长",
"author": "李四"
}
全文检索:
GET http://127.0.0.1:8080/es/article/search?keyword=南京大桥
IK 会自动拆分【南京、长江大桥】,两条相关文档都能命中
高亮查询:
GET http://127.0.0.1:8080/es/article/search/highlight?keyword=南京大桥
⚠️ 重点坑说明
IK 分词只在创建索引的时候指定 mapping 才生效! 索引建好之后,再改 analyzer 是无效的,需要删除重建索引。
ES8 默认 https,本地开发一定要配置
ssl-enabled: false版本对齐:spring-data-elasticsearch 尽量和 ES 版本对齐,这里直接使用官方原生 client,避开 spring-data 的版本坑
match 查询会自动使用 mapping 里配置的
search_analyzer=ik_smart