Elasticsearch8.14.0详细讲解搭配SpringBoot
第一部分:Elasticsearch & Kibana 核心概念
1. 什么是 Elasticsearch (ES)?
Elasticsearch 是一个基于 Apache Lucene 构建的分布式、RESTful 风格的搜索和数据分析引擎。
- 核心功能:
-
- 全文搜索:类似 Google,快速在海量文本中检索关键词。
- 结构化搜索:查找特定字段(例如:查找“价格 > 100”且“库存 > 0”的商品)。
- 数据分析:聚合统计(例如:计算“上个月各品牌的平均销售额”)。
- 核心概念对比 (ES vs 关系型数据库):
为了方便理解,我们可以将 ES 的概念映射到 MySQL 中:
|
Elasticsearch 概念 |
关系型数据库 (MySQL) |
描述 |
|
Index (索引) |
Database / Table |
文档的集合,数据存储的地方。 |
|
Document (文档) |
Row (行) |
存储数据的基本单元,JSON 格式。 |
|
Field (字段) |
Column (列) |
文档中的属性(Key-Value)。 |
|
Node (节点) |
Server Instance |
单台运行 ES 的服务器。 |
|
Cluster (集群) |
Database Cluster |
多台节点协同工作,保证高可用。 |
2. 什么是 Kibana?
Kibana 是 ES 的“脸面”。如果说 ES 是后端引擎,Kibana 就是前端操作台。
- 核心用途:
-
- Dev Tools (开发工具):开发人员神器,可直接编写 DSL 语句与 ES 交互。
- 数据可视化:生成饼图、柱状图、热力图等。
- 集群监控:查看索引状态、磁盘与内存使用率。
第二部分:下载与安装 (v8.x 快速上手版)
环境说明:本教程基于 Elasticsearch 8.14.0 和 Kibana 8.14.0。
重要前提:ES 和 Kibana 版本必须严格一致。
1. 官方下载地址
请根据你的操作系统(Windows/macOS/Linux)下载对应版本:
2. Elasticsearch 安装与配置
ES 8.x 默认开启了强安全模式(HTTPS + SSL + 密码),这对本地开发调试非常繁琐。为了快速跑通代码,我们采用免密 HTTP 模式。
第一步:解压并修改配置
进入解压后的 config 目录,编辑 elasticsearch.yml。请直接在文件末尾添加或修改以下关键配置:
# ======================== 核心配置 (本地开发用) ========================
# 1. 关闭安全认证 (核心:关闭后连接无需账号密码,避免证书报错)
xpack.security.enabled: false
# 2. 关闭自动注册流程
xpack.security.enrollment.enabled: false
# 3. 禁用 HTTP SSL 加密 (核心:允许使用 http://localhost:9200 访问)
xpack.security.http.ssl:
enabled: false
# 4. 允许跨域 (可选:方便第三方可视化工具连接)
http.cors.enabled: true
http.cors.allow-origin: "*"
第二步:启动 ES
- Windows: 双击
bin\elasticsearch.bat - Mac/Linux: 终端运行
./bin/elasticsearch
第三步:验证
浏览器访问 http://localhost:9200。
如果看到一段包含 "tagline": "You Know, for Search" 的 JSON 文本,说明启动成功!
3. Kibana 安装与配置
第一步:解压并修改配置
进入 config 目录,编辑 kibana.yml:
# Kibana 端口
server.port: 5601
# 指向 ES 地址 (因为上面禁用了 SSL,这里用 http)
elasticsearch.hosts: ["http://localhost:9200"]
# 设置中文界面 (可选)
i18n.locale: "zh-CN"
第二步:启动 Kibana
- Windows: 双击
bin\kibana.bat - Mac/Linux: 终端运行
./bin/kibana
第三步:验证
浏览器访问 http://localhost:5601。
第三部分:安装 IK 中文分词器 (必装插件)
ES 默认分词器会将中文拆成单字(例如“华为手机”拆成“华、为、手、机”),这不符合中文搜索习惯。IK 分词器支持智能中文切分。
- 安装命令
在 Elasticsearch 安装根目录下打开终端/CMD,执行:
./bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.14.0
(如果提示 [y/N],输入 y 确认安装)
- 重启生效 (重要)
安装完成后,必须重启 Elasticsearch 服务,插件才会生效。
第四部分:讲解Elasticsearch的查询
1. 索引与映射管理 (Index & Mapping)
在 Elasticsearch 8.x 中,显式定义 Mapping 依然是最佳实践,特别是为了区分全文检索字段和精确匹配字段。
1.1 创建索引 (Create Index)
定义索引结构,包括分片设置、字段类型以及 8.x 的向量字段定义。
JSON
PUT /products
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"name": {
"type": "text", // 文本:分词,用于全文检索
"analyzer": "standard"
},
"sku": {
"type": "keyword" // 关键字:不分词,用于精确匹配/聚合
},
"price": { "type": "double" },
"created_at": { "type": "date" },
"status": { "type": "keyword" },
"feature_vector": { // 8.x Dense Vector 字段
"type": "dense_vector",
"dims": 3, // 向量维度
"index": true, // 允许被索引用于搜索
"similarity": "cosine" // 相似度算法:cosine, l2_norm, dot_product
}
}
}
}
1.2 查看与删除 (View & Delete)
- 查看 Mapping:
GET /products/_mapping - 删除索引:
DELETE /products
2. 文档 CRUD 操作 (Document Operations)
2.1 写入文档 (Index)
指定 ID 写入(如果 ID 存在则覆盖,版本号增加)。
JSON
POST /products/_doc/101
{
"name": "Sony Noise Cancelling Headphones",
"sku": "WH-1000XM5",
"price": 349.99,
"status": "active",
"created_at": "2024-06-01T12:00:00Z",
"feature_vector": [0.1, 0.5, 0.9]
}
2.2 局部更新 (Update)
仅修改文档中的部分字段,无需重新提交整个文档。
JSON
POST /products/_update/101
{
"doc": {
"price": 299.99,
"status": "on_sale"
}
}
2.3 查询单条与删除 (Get & Delete)
- 根据 ID 获取:
GET /products/_doc/101 - 根据 ID 删除:
DELETE /products/_doc/101
3. 基础查询 DSL (Basic Queries)
3.1 精确查询 (Term Level)
适用于 keyword、date、boolean 和数字类型。切勿对 text 字段使用此类查询。
Term (单值):
- JSON
GET /products/_search
{
"query": {
"term": { "status": "active" }
}
}
Range (范围):
- JSON
GET /products/_search
{
"query": {
"range": {
"price": { "gte": 100, "lte": 500 } // >= 100 且 <= 500
}
}
}
IDs (主键查询):
- JSON
GET /products/_search
{
"query": { "ids": { "values": ["101", "102"] } }
}
3.2 全文检索 (Full Text)
适用于 text 类型,查询词会被分词器处理。
Match (标准匹配):
- JSON
GET /products/_search
{
"query": {
"match": { "name": "sony headphones" }
// 查找包含 "sony" 或 "headphones" 的文档
}
}
Match Phrase (短语匹配):
- JSON
GET /products/_search
{
"query": {
"match_phrase": {
"name": "noise cancelling"
// "noise" 和 "cancelling" 必须相邻且顺序一致
}
}
}
4. 复合查询 (Compound Queries)
4.1 Bool 查询 (Boolean)
这是 ES 中最核心的组合逻辑,包含 must (AND), should (OR), must_not (NOT), 和 filter (AND, 无算分)。
JSON
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "headphones" }} // 必须匹配,贡献算分
],
"filter": [
{ "term": { "status": "active" }}, // 必须匹配,不贡献算分(高性能缓存)
{ "range": { "price": { "lt": 1000 }}}
],
"must_not": [
{ "term": { "sku": "OLD-MODEL-001" }} // 排查特定款
],
"should": [
{ "term": { "sku": "WH-1000XM5" }} // 如果匹配,得分更高
]
}
}
}
5. 聚合分析 (Aggregations)
用于数据统计、报表生成。
5.1 桶聚合 (Bucket Aggregations)
类似于 SQL 的 Group By。
JSON
GET /products/_search
{
"size": 0, // 不返回具体文档
"aggs": {
"status_distribution": {
"terms": {
"field": "status",
"size": 10
},
"aggs": {
// 子聚合:计算每种状态下的平均价格
"avg_price_per_status": {
"avg": { "field": "price" }
}
}
}
}
}
5.2 日期直方图 (Date Histogram)
按时间维度统计(如:按月统计销量)。
JSON
GET /products/_search
{
"size": 0,
"aggs": {
"monthly_additions": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month"
}
}
}
}
6. 8.14.0 向量搜索与高级特性 (Advanced & Vector)
6.1 KNN 向量搜索 (k-Nearest Neighbors)
ES 8.x 将 KNN 提升为一级查询参数,支持与传统 DSL 混合使用。
JSON
GET /products/_search
{
"knn": {
"field": "feature_vector",
"query_vector": [0.1, 0.5, 0.9], // 待查询向量
"k": 5, // 返回最近的5个
"num_candidates": 50 // 每个分片搜索的候选数量
},
"_source": ["name", "price"]
}
6.2 排序与深度分页 (Sort & Pagination)
- 基础分页: 使用
from和size。
排序:
- JSON
GET /products/_search
{
"query": { "match_all": {} },
"sort": [
{ "price": { "order": "desc" }},
{ "_score": { "order": "desc" }}
]
}
- Search After (深度分页推荐): 用于处理超过 10,000 条数据的导出场景,需配合
sort结果中的 values 使用。
6.3 搜索高亮 (Highlighting)
在结果中用 HTML 标签包裹匹配词。
JSON
GET /products/_search
{
"query": { "match": { "name": "sony" }},
"highlight": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"],
"fields": { "name": {} }
}
}
7. 辅助工具与 SQL (Tools & SQL)
7.1 SQL 查询支持
适合熟悉 SQL 但不熟悉 DSL 的用户。
JSON
POST /_sql?format=txt
{
"query": "SELECT name, price FROM products WHERE price > 300 ORDER BY price DESC LIMIT 5"
}
7.2 Explain API (排查算分)
理解为什么某个文档被搜出来,或者为什么排名靠前。
JSON
GET /products/_explain/101
{
"query": {
"match": { "name": "headphones" }
}
}
第五部分:Spring Boot 整合 Elasticsearch (代码实战)
1. 添加 Maven 依赖 (pom.xml)
注意:这是之前文档缺失的部分,必须添加依赖才能运行代码。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.14.0</version> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 配置文件 (application.properties)
elasticsearch.host=localhost
elasticsearch.port=9200
# 如果在 elasticsearch.yml 中设置了 xpack.security.enabled: false
# 下面的账号密码其实是不生效的,但在代码中保留可以方便后续开启安全认证
elasticsearch.username=elastic
elasticsearch.password=MurroR2T0*OmihFJH3C2
3. 配置类 (ElasticsearchConfig.java)
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.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticsearchConfig {
@Value("${elasticsearch.host}")
private String host;
@Value("${elasticsearch.port}")
private int port;
@Value("${elasticsearch.username}")
private String username;
@Value("${elasticsearch.password}")
private String password;
@Bean
public ElasticsearchClient elasticsearchClient() {
// 1. 创建凭证提供者
// (注:如果 ES 关闭了 Security,这部分其实不会被服务端验证,但保留无妨)
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(username, password)
);
// 2. 创建 RestClient(使用 HTTP,无 SSL)
RestClient restClient = RestClient
.builder(new HttpHost(host, port, "http"))
.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
)
.build();
// 3. 创建 Transport 和 Client
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
4. 测试类 (EsTest.java)
1. 索引管理 (Index Management)
创建索引
@Test
void contextLoads() throws IOException {
CreateIndexRequest request = new CreateIndexRequest.Builder().index(EsUtils.EsIndex).build();
CreateIndexResponse response = elasticsearchClient.indices().create(request);
System.out.println(response);
}
检查索引是否存在
@Test
void exitEs() throws IOException {
ExistsRequest request = new ExistsRequest.Builder().index(EsUtils.EsIndex).build();
BooleanResponse exists = elasticsearchClient.indices().exists(request);
System.out.println(exists.value());
}
删除索引
@Test
void deleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest.Builder().index(EsUtils.EsIndex).build();
DeleteIndexResponse response = elasticsearchClient.indices().delete(request);
System.out.println(response);
}
2. 文档基础操作 (CRUD)
添加文档
@Test
void addDocument() throws IOException {
User user = new User("zhangsan", 11);
IndexRequest<User> request = IndexRequest.of(builder ->
builder
.id("1")
.index(EsUtils.EsIndex)
.document(user)
);
IndexResponse index = elasticsearchClient.index(request);
System.out.println(index);
}
获取文档
@Test
void getDocument() throws IOException {
GetRequest request = GetRequest.of(builder -> builder.index(EsUtils.EsIndex).id("1"));
GetResponse<User> userGetResponse = elasticsearchClient.get(request, User.class);
System.out.println(userGetResponse);
System.out.println(userGetResponse.source());
}
更新文档
@Test
void updateDocument() throws IOException {
User user = new User("zhangsan", 14);
UpdateRequest<User, Object> request = UpdateRequest.of(builder -> builder.index(EsUtils.EsIndex).id("1").doc(user));
UpdateResponse<User> update = elasticsearchClient.update(request, User.class);
System.out.println(update);
System.out.println(update.result());
}
删除文档
@Test
void deleteDocument() throws IOException {
DeleteRequest request = DeleteRequest.of(builder -> builder.index(EsUtils.EsIndex).id("1"));
DeleteResponse delete = elasticsearchClient.delete(request);
System.out.println(delete);
System.out.println(delete.result());
}
批量添加文档 (Bulk)
@Test
void addBulkDocument() throws IOException {
ArrayList<User> users = new ArrayList<>();
users.add(new User("zhangsan", 13));
users.add(new User("wangwu", 15));
users.add(new User("lisi", 14));
BulkRequest.Builder request = new BulkRequest.Builder();
for (User user : users) {
request.operations(operationBuilder ->
operationBuilder.index(index -> index.index(EsUtils.EsIndex).document(user))
);
}
BulkResponse bulk = elasticsearchClient.bulk(request.build());
System.out.println(bulk);
System.out.println("耗时: " + bulk.took());
System.out.println("项详情: " + bulk.items());
}
3. 搜索与查询 (Search Queries)
Match 查询 (分词模糊匹配)
@Test
void searchByMatch() throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index("zwz")
.query(q -> q
.match(m -> m
.field("name")
.query("zhangsan")
)
)
);
SearchResponse<User> response = elasticsearchClient.search(request, User.class);
response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
Term 查询 (精确匹配)
@Test
void searchByTerm() throws IOException {
SearchResponse<User> response = elasticsearchClient.search(s -> s
.index("zwz")
.query(q -> q
.term(t -> t
.field("age")
.value(14)
)
),
User.class
);
response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
Range 范围查询
@Test
void searchByRange() throws IOException {
SearchResponse<User> response = elasticsearchClient.search(s -> s
.index("zwz")
.query(q -> q
.range(r -> r
.field("age")
.gte(JsonData.of(10))
.lte(JsonData.of(20))
)
),
User.class
);
response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
Bool 组合查询
@Test
void searchByBool() throws IOException {
SearchResponse<User> response = elasticsearchClient.search(s -> s
.index("zwz")
.query(q -> q
.bool(b -> b
.must(m -> m.match(t -> t.field("name").query("zhangsan"))) // 必须匹配
.filter(f -> f.range(r -> r.field("age").gte(JsonData.of(10)))) // 过滤不打分
)
),
User.class
);
response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
分页与排序
@Test
void searchWithPaging() throws IOException {
SearchResponse<User> response = elasticsearchClient.search(s -> s
.index("zwz")
.from(0) // offset
.size(10) // limit
.sort(so -> so.field(f -> f.field("age").order(SortOrder.Desc)))
.query(q -> q.matchAll(m -> m)),
User.class
);
System.out.println("总命中: " + response.hits().total().value());
response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
高亮显示
@Test
void searchWithHighlight() throws IOException {
SearchResponse<User> response = elasticsearchClient.search(s -> s
.index("zwz")
.query(q -> q.match(m -> m.field("name").query("zhangsan")))
.highlight(h -> h
.fields("name", hf -> hf.preTags("<em>").postTags("</em>"))
),
User.class
);
response.hits().hits().forEach(hit -> {
System.out.println("Source: " + hit.source());
System.out.println("Highlight: " + hit.highlight());
});
}
查询所有数据
@Test
void searchAll() throws IOException {
SearchResponse<User> response = elasticsearchClient.search(s -> s
.index("zwz")
.query(q -> q.matchAll(m -> m)),
User.class
);
System.out.println("总数: " + response.hits().total().value());
response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
注意事项
不同的版本对应的Java操作可能不一样,我这边使用的elasticsearch8.14.0版本的
Git地址
更多推荐
所有评论(0)