SpringBoot 整合 ElasticSearch,给搜索插上“光速翅膀”

大家好,我是小悟。

一、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

四、遇到的"坑"和解决方案

  1. 连接问题:确保ES服务已启动,检查端口9200

    arduino 复制代码
    curl http://localhost:9200
  2. 版本兼容:SpringBoot版本和ES版本要匹配

    • SpringBoot 2.7.x → ES 7.17.x
    • SpringBoot 3.x → ES 8.x
  3. 中文分词:安装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
  4. 内存不足:调整JVM参数

    diff 复制代码
    # config/jvm.options
    -Xms1g
    -Xmx1g

五、总结:为什么选择这对"黄金搭档"?

SpringBoot + ES = 天作之合

优点大放送:

  1. 开发速度:SpringBoot的自动配置 + ES的简单API = 生产力翻倍
  2. 性能表现:ES的倒排索引 + 分布式架构 = 搜索如飞
  3. 扩展性:微服务架构中,ES可作为独立的搜索服务
  4. 生态完善:Spring Data Elasticsearch 封装了大多数常用操作
  5. 实时性:近实时搜索,数据一秒内可查

适用场景(ES大显身手的时候)

  1. 电商网站:商品搜索、筛选、排序
  2. 日志分析:ELK栈中的核心组件
  3. 内容平台:文章、新闻的全文检索
  4. 监控系统:实时数据分析
  5. 推荐系统:用户行为分析+相似度搜索

最后

  • 不要滥用:简单的CRUD用MySQL,复杂搜索再用ES
  • 数据同步:考虑使用Logstash或自定义同步机制
  • 索引设计:合理的mapping设计是性能的关键
  • 监控告警:用Kibana监控ES集群健康状态

SpringBoot整合ElasticSearch,就像给程序装上了"谷歌大脑"------存得多、找得快、查得准。虽然配置过程像在组装乐高,偶尔会找不到零件(版本兼容),但一旦搭建完成,你就能享受到"秒级搜索"的快感。

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

相关推荐
骚戴6 小时前
DeepSeek V3 & Llama 3 推理避坑指南:自建 vLLM 集群 vs API 网关架构深度对比
java·人工智能·python·大模型·api·vllm
星浩AI6 小时前
手把手教你用 RAG 打造专属知识库问答系统
后端
墨雪不会编程6 小时前
C++基础语法篇八 ——【类型转换、再探构造、友元】
java·开发语言·c++
老毛肚6 小时前
登录架构设计
java·开发语言
月明长歌6 小时前
【码道初阶】【牛客BM30】二叉搜索树与双向链表:java中以引用代指针操作的艺术与陷阱
java·数据结构·算法·leetcode·二叉树·笔试·字节跳动
Elasticsearch6 小时前
开始使用 Elastic Agent Builder 和 Strands Agents SDK
elasticsearch
喵个咪6 小时前
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:深度解析 Wire 依赖注入集成实践
后端·go
回家路上绕了弯6 小时前
代码的三大核心素养:如何同时兼顾可维护性、可扩展性、可测试性
分布式·后端
快手技术6 小时前
入围AA总榜Top 10,Non-Reasoning Model榜单第一!KAT-Coder-Pro V1 新版本踏浪归来!
前端·后端·前端框架