大家好,我是小悟。
一、ElasticSearch 是什么?
你有一个超级健忘的朋友(比如金鱼记忆的那种),但他却能在0.0001秒内从100万本书里找到你想要的句子。这就是 ElasticSearch(简称 ES)!
ES 的"人格特征":
- 速度狂魔:搜索速度比咖啡因过量的程序员找Bug还快
- 文档收藏家:什么JSON、日志都能存
- 超级侦探:模糊搜索、精确搜索、拼音搜索样样精通
- 大象胃口:数据量再大也不怕(毕竟名字里就有"elastic"弹性)
- 马戏团团长:天生分布式,节点之间跳来跳去从不出错
二、整合大冒险:SpringBoot 与 ES 的"相亲大会"
第 1 步:先来个"相亲介绍人"(Maven 依赖)
xml
<!-- pom.xml 里加入这些"红娘" -->
<dependencies>
<!-- SpringBoot 给 ES 的专属"情书" -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 防止程序说"我不会JSON" -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
第 2 步:配置"约会地点"(application.yml)
yaml
# application.yml
spring:
elasticsearch:
uris: http://localhost:9200 # ES 的"家庭地址"
username: elastic # 用户名(默认是这个)
password: your_password # 密码(安装时设置的)
# 可选:让日志"多说点话",方便调试
data:
elasticsearch:
repositories:
enabled: true
# 给 ES 客户端一点"咖啡因",让它更精神
elasticsearch:
connection-timeout: 5000 # 连接超时(毫秒)
socket-timeout: 30000 # socket超时
第 3 步:创建"相亲对象"(实体类)
kotlin
import lombok.Data;
import org.springframework.data.annotation.Id;
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 = "book_index") // 告诉ES:"这是我家的书架名字"
public class Book {
@Id // 相当于书的"身份证号"
private String id;
@Field(type = FieldType.Text, analyzer = "ik_max_word") // 用中文分词器
private String title; // 书名
@Field(type = FieldType.Text, analyzer = "ik_smart")
private String author; // 作者
@Field(type = FieldType.Double)
private Double price; // 价格
@Field(type = FieldType.Date)
private Date publishDate; // 出版日期
@Field(type = FieldType.Keyword) // 关键词,不分词
private String category; // 分类
@Field(type = FieldType.Nested) // 嵌套对象
private List<Tag> tags;
@Data
public static class Tag {
@Field(type = FieldType.Keyword)
private String name;
@Field(type = FieldType.Integer)
private Integer priority;
}
}
第 4 步:找个"媒婆"(Repository 接口)
swift
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface BookRepository extends ElasticsearchRepository<Book, String> {
// 方法名就是查询!Spring Data 的"魔法"
// 1. 按作者精确查找(比找失散多年的兄弟还准)
List<Book> findByAuthor(String author);
// 2. 按标题模糊查找(支持分词)
List<Book> findByTitleContaining(String keyword);
// 3. 价格区间查找(找买得起的书)
List<Book> findByPriceBetween(Double minPrice, Double maxPrice);
// 4. 多条件查询(作者+分类)
List<Book> findByAuthorAndCategory(String author, String category);
// 5. 自定义查询(展示真正的技术)
@Query("{\"bool\": {\"must\": [{\"match\": {\"title\": \"?0\"}}]}}")
Page<Book> customSearch(String keyword, Pageable pageable);
// 6. 统计某个作者有多少书
Long countByAuthor(String author);
}
第 5 步:写个"恋爱导师"(Service 层)
typescript
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class BookService {
@Autowired
private BookRepository bookRepository;
/**
* 添加/更新一本书
* 如果书有id,就是更新;没有id,就是新增
*/
public Book saveBook(Book book) {
return bookRepository.save(book);
}
/**
* 批量添加(ES最喜欢批量操作了,效率高)
*/
public void saveAllBooks(List<Book> books) {
bookRepository.saveAll(books);
}
/**
* 按ID查找(速度飞快)
*/
public Optional<Book> findById(String id) {
return bookRepository.findById(id);
}
/**
* 复杂搜索:按标题和作者搜索
*/
public List<Book> searchBooks(String title, String author) {
// 这里可以写更复杂的逻辑
if (title != null && author != null) {
return bookRepository.findByTitleContainingAndAuthor(title, author);
} else if (title != null) {
return bookRepository.findByTitleContaining(title);
} else {
return bookRepository.findByAuthor(author);
}
}
/**
* 删除一本书(谨慎操作!)
*/
public void deleteBook(String id) {
bookRepository.deleteById(id);
}
/**
* 分页查询(大数据量的好朋友)
*/
public Page<Book> findAllBooks(Pageable pageable) {
return bookRepository.findAll(pageable);
}
/**
* 高级搜索:使用QueryBuilder
*/
public List<Book> advancedSearch(String keyword, Double minPrice, Double maxPrice) {
// 使用NativeSearchQueryBuilder构建复杂查询
// 这里先省略,下面会有详细示例
return null;
}
}
第 6 步:高级搜索的"秘密武器"
kotlin
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class AdvancedSearchService {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 多条件组合搜索(布尔查询)
*/
public List<Book> multiConditionSearch(String keyword, Double minPrice,
Double maxPrice, String category) {
// 1. 创建布尔查询构建器(相当于SQL的WHERE)
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2. 添加必须条件(must = AND)
if (keyword != null && !keyword.trim().isEmpty()) {
boolQuery.must(QueryBuilders.multiMatchQuery(keyword, "title", "author")
.analyzer("ik_max_word"));
}
// 3. 添加价格范围(range查询)
if (minPrice != null || maxPrice != null) {
var rangeQuery = QueryBuilders.rangeQuery("price");
if (minPrice != null) {
rangeQuery.gte(minPrice);
}
if (maxPrice != null) {
rangeQuery.lte(maxPrice);
}
boolQuery.must(rangeQuery);
}
// 4. 添加分类过滤(filter不计算分数,更快)
if (category != null && !category.trim().isEmpty()) {
boolQuery.filter(QueryBuilders.termQuery("category", category));
}
// 5. 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withSorts(org.springframework.data.elasticsearch.core.query.SortBuilders
.fieldSort("price").order(org.springframework.data.domain.Sort.Direction.ASC))
.withPageable(org.springframework.data.domain.PageRequest.of(0, 10))
.build();
// 6. 执行查询
SearchHits<Book> searchHits = elasticsearchRestTemplate.search(searchQuery, Book.class);
// 7. 转换结果
return searchHits.getSearchHits().stream()
.map(hit -> hit.getContent())
.collect(Collectors.toList());
}
/**
* 聚合查询:统计每个分类有多少本书
*/
public Map<String, Long> categoryStatistics() {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.addAggregation(org.springframework.data.elasticsearch.core.query.aggregation.AggregationBuilders
.terms("category_agg").field("category.keyword"))
.build();
SearchHits<Book> searchHits = elasticsearchRestTemplate.search(query, Book.class);
// 处理聚合结果(这里简化了,实际需要解析Aggregations)
return new HashMap<>();
}
}
第 7 步:REST API 控制器(对外接口)
less
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/books")
public class BookController {
@Autowired
private BookService bookService;
@Autowired
private AdvancedSearchService advancedSearchService;
/**
* 创建新书
*/
@PostMapping
public ResponseEntity<Book> createBook(@RequestBody Book book) {
Book savedBook = bookService.saveBook(book);
return ResponseEntity.ok(savedBook);
}
/**
* 批量导入(适合初始化数据)
*/
@PostMapping("/batch")
public ResponseEntity<String> batchImport(@RequestBody List<Book> books) {
bookService.saveAllBooks(books);
return ResponseEntity.ok("成功导入 " + books.size() + " 本书");
}
/**
* 搜索书籍(简单版)
*/
@GetMapping("/search")
public ResponseEntity<List<Book>> searchBooks(
@RequestParam(required = false) String title,
@RequestParam(required = false) String author) {
List<Book> books = bookService.searchBooks(title, author);
return ResponseEntity.ok(books);
}
/**
* 高级搜索(多条件)
*/
@GetMapping("/advanced-search")
public ResponseEntity<List<Book>> advancedSearch(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Double minPrice,
@RequestParam(required = false) Double maxPrice,
@RequestParam(required = false) String category) {
List<Book> books = advancedSearchService
.multiConditionSearch(keyword, minPrice, maxPrice, category);
return ResponseEntity.ok(books);
}
/**
* 分页查询
*/
@GetMapping("/page")
public ResponseEntity<Page<Book>> getBooksByPage(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Page<Book> books = bookService.findAllBooks(PageRequest.of(page, size));
return ResponseEntity.ok(books);
}
}
第 8 步:配置类(锦上添花)
kotlin
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
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.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.yourpackage.repository")
public class ElasticsearchConfig {
@Bean
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.withBasicAuth("elastic", "your_password")
.withConnectTimeout(5000)
.withSocketTimeout(30000)
.build();
return RestClients.create(clientConfiguration).rest();
}
@Bean
public ElasticsearchRestTemplate elasticsearchRestTemplate() {
return new ElasticsearchRestTemplate(elasticsearchClient());
}
}
三、测试一下我们的"杰作"
测试数据(JSON格式)
bash
POST /api/books/batch
[
{
"title": "SpringBoot从入门到放弃",
"author": "程序猿老张",
"price": 68.5,
"category": "技术",
"publishDate": "2023-01-01",
"tags": [
{"name": "Java", "priority": 1},
{"name": "后端", "priority": 2}
]
},
{
"title": "ElasticSearch实战指南",
"author": "搜索达人李",
"price": 89.0,
"category": "技术",
"publishDate": "2023-02-15",
"tags": [
{"name": "搜索", "priority": 1},
{"name": "大数据", "priority": 2}
]
}
]
搜索示例
ini
GET /api/books/search?title=SpringBoot&author=程序猿老张
GET /api/books/advanced-search?keyword=实战&minPrice=50&maxPrice=100
四、遇到的"坑"和解决方案
-
连接问题:确保ES服务已启动,检查端口9200
arduinocurl http://localhost:9200 -
版本兼容:SpringBoot版本和ES版本要匹配
- SpringBoot 2.7.x → ES 7.17.x
- SpringBoot 3.x → ES 8.x
-
中文分词:安装IK分词器
bash# 进入ES容器/安装目录的plugins文件夹 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.3/elasticsearch-analysis-ik-7.17.3.zip -
内存不足:调整JVM参数
diff# config/jvm.options -Xms1g -Xmx1g
五、总结:为什么选择这对"黄金搭档"?
SpringBoot + ES = 天作之合
优点大放送:
- 开发速度:SpringBoot的自动配置 + ES的简单API = 生产力翻倍
- 性能表现:ES的倒排索引 + 分布式架构 = 搜索如飞
- 扩展性:微服务架构中,ES可作为独立的搜索服务
- 生态完善:Spring Data Elasticsearch 封装了大多数常用操作
- 实时性:近实时搜索,数据一秒内可查
适用场景(ES大显身手的时候)
- 电商网站:商品搜索、筛选、排序
- 日志分析:ELK栈中的核心组件
- 内容平台:文章、新闻的全文检索
- 监控系统:实时数据分析
- 推荐系统:用户行为分析+相似度搜索
最后
- 不要滥用:简单的CRUD用MySQL,复杂搜索再用ES
- 数据同步:考虑使用Logstash或自定义同步机制
- 索引设计:合理的mapping设计是性能的关键
- 监控告警:用Kibana监控ES集群健康状态
SpringBoot整合ElasticSearch,就像给程序装上了"谷歌大脑"------存得多、找得快、查得准。虽然配置过程像在组装乐高,偶尔会找不到零件(版本兼容),但一旦搭建完成,你就能享受到"秒级搜索"的快感。

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海