欢迎光临
我们一直在努力

SpringBoot 整合 Elasticsearch 实战——搜索引擎从入门到落地

当 MySQL 的 LIKE '%keyword%' 越来越慢,或者你需要做全文搜索、高亮展示、聚合统计时,Elasticsearch 就是标配方案了。

一、Elasticsearch 核心概念

ES 概念类比 MySQL说明
Index(索引) 数据库 存储同类文档的地方
Type(类型) 7.x 以后废弃,一个 Index 下面不再分 Type
Document(文档) 一行记录 JSON 格式的数据单元
Field(字段) 一列 文档中的字段
Mapping(映射) 表结构 定义字段类型和分析器
Shard(分片) 分表 数据水平拆分到多台机器

关键词: ES 的搜索快,靠的是倒排索引——把文档拆成词条,建立"词条→文档"的映射,查的时候直接定位,不走全表扫描。

二、SpringBoot 整合 ES

1. 引入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

2. 配置连接

spring:
elasticsearch:
uris: http://localhost:9200
connection-timeout: 3s
socket-timeout: 30s

3. 创建实体类

@Data
@Document(indexName = "products") // 对应 ES 索引名
public class ProductDocument {

@Id
private Long id;

@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title; // 商品标题(中文分词)

@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String description; // 商品描述

@Field(type = FieldType.Keyword)
private String brand; // 品牌(不分词,精确匹配)

@Field(type = FieldType.Double)
private Double price; // 价格

@Field(type = FieldType.Integer)
private Integer stock; // 库存

@Field(type = FieldType.Date)
private Date createTime;
}

注意: analyzer = "ik_max_word" 是中文分词器,需要提前安装到 ES 中,否则用默认的 standard 分词器会把中文一个字一个字地分。

4. 编写 Repository

@Repository
public interface ProductRepository
extends ElasticsearchRepository<ProductDocument, Long> {

// 按标题搜索
List<ProductDocument> findByTitle(String title);

// 按标题模糊搜索
List<ProductDocument> findByTitleContaining(String keyword);

// 组合条件:标题包含 + 价格区间
List<ProductDocument> findByTitleContainingAndPriceBetween(
String keyword, Double min, Double max);

// 自定义查询(复杂查询用 @Query)
@Query("{\\"match\\": {\\"description\\": \\"?0\\"}}")
List<ProductDocument> searchByDescription(String keyword);
}

三、CRUD 实战

1. 批量导入数据(从 MySQL 同步到 ES)

@Service
public class ProductIndexService {

@Autowired
private ProductRepository productRepository;

@Autowired
private ProductMapper productMapper; // MyBatis-Plus 的 Mapper

/**
* 全量同步:把 MySQL 的商品数据导入到 ES
*/

public void fullSync() {
List<Product> products = productMapper.selectList(null);
List<ProductDocument> docs = products.stream()
.map(this::convertToDoc)
.collect(Collectors.toList());

productRepository.saveAll(docs);
System.out.println("全量同步完成,共 " + docs.size() + " 条");
}

/**
* 增量同步:单条新增或更新
*/

public void syncById(Long productId) {
Product product = productMapper.selectById(productId);
if (product != null) {
productRepository.save(convertToDoc(product));
}
}

/**
* 从 ES 删除
*/

public void deleteFromIndex(Long productId) {
productRepository.deleteById(productId);
}

private ProductDocument convertToDoc(Product product) {
ProductDocument doc = new ProductDocument();
BeanUtils.copyProperties(product, doc);
return doc;
}
}

2. 搜索接口

@RestController
@RequestMapping("/search")
public class SearchController {

@Autowired
private ProductRepository productRepository;

@Autowired
private ElasticsearchRestTemplate elasticsearchTemplate;

/**
* 简单搜索(使用 Repository 自带方法)
*/

@GetMapping("/simple")
public List<ProductDocument> simpleSearch(String keyword) {
return productRepository.findByTitleContaining(keyword);
}

/**
* 高级搜索(分页 + 高亮 + 排序)
*/

@GetMapping("/advanced")
public Page<ProductDocument> advancedSearch(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {

// 构建查询条件
NativeQueryBuilder queryBuilder = new NativeQueryBuilder();

// 多字段匹配:标题和描述都搜
queryBuilder.withQuery(QueryBuilders
.multiMatchQuery(keyword, "title", "description"));

// 分页(ES 页码从 0 开始)
queryBuilder.withPageable(PageRequest.of(page 1, size));

// 高亮设置
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title").preTags("<em>").postTags("</em>");
highlightBuilder.field("description").preTags("<em>").postTags("</em>");
queryBuilder.withHighlightBuilder(highlightBuilder);

// 排序(按价格升序)
queryBuilder.withSort(Sort.by(Sort.Direction.ASC, "price"));

NativeQuery query = queryBuilder.build();
SearchHits<ProductDocument> searchHits =
elasticsearchTemplate.search(query, ProductDocument.class);

// 处理高亮结果
List<ProductDocument> products = searchHits.stream().map(hit -> {
ProductDocument doc = hit.getContent();
Map<String, List<String>> highlights = hit.getHighlightFields();

// 如果有高亮,替换原标题
if (highlights.containsKey("title")) {
doc.setTitle(highlights.get("title").get(0));
}
if (highlights.containsKey("description")) {
doc.setDescription(highlights.get("description").get(0));
}
return doc;
}).collect(Collectors.toList());

return new PageImpl<>(products);
}

/**
* 聚合搜索(按品牌统计)
*/

@GetMapping("/aggregate")
public List<BrandCount> aggregateByBrand() {
NativeQuery query = NativeQuery.builder()
.withAggregation("brands", AggregationBuilders
.terms("brands").field("brand").size(10))
.build();

SearchHits<ProductDocument> hits =
elasticsearchTemplate.search(query, ProductDocument.class);

// 解析聚合结果
ElasticsearchAggregations aggregations =
(ElasticsearchAggregations) hits.getAggregations();
ParsedStringTerms terms =
aggregations.get("brands");

return terms.getBuckets().stream()
.map(bucket -> new BrandCount(
bucket.getKey().toString(),
bucket.getDocCount()))
.collect(Collectors.toList());
}
}

四、ES 和 MySQL 的双写问题

ES 不是主数据库,它只是搜索引擎。正确的架构是:

写入:MySQL(主) → 同步到 ES(从)
搜索:直接查 ES,速度快

同步方式有三种:

方式优点缺点
业务代码双写 简单 耦合高,容易漏
MQ 异步同步 👍 解耦,可靠 多一个 MQ 组件
logstash 定时同步 不写代码 有秒级延迟

推荐用 MQ:

// 商品服务:修改商品后发送消息
@PostMapping("/product/update")
public ResultVO<?> updateProduct(@RequestBody Product product) {
productService.updateById(product);

// 发送消息通知 ES 同步
rabbitTemplate.convertAndSend("product.exchange", "product.sync", product.getId());
return ResultVO.success();
}

// 搜索服务:监听消息,同步到 ES
@RabbitListener(queues = "product.sync.queue")
public void handleProductSync(Long productId) {
productIndexService.syncById(productId);
}

五、SpringBoot 版本适配

不同 SpringBoot 版本对应的 ES 客户端不一样:

SpringBoot 版本推荐的 ES 客户端
2.3.x TransportClient(已弃用)
2.4.x – 2.7.x ElasticsearchRestTemplate
3.x 最新的 Java Client

你现在用 SpringBoot 2.7,就用 RestTemplate 方式,上面给的代码都是兼容的。

六、ES 实际开发注意事项

  • ES 不是银弹——几十万条数据用 MySQL 的 LIKE 也够用,别什么都往 ES 里扔
  • 中文搜索必须装 IK 分词器,否则一个字一个字地搜,体验极差
  • ES 不擅长关联查询(比如"查订单 + 商品名 + 用户地址"),关联用 MySQL 做
  • ES 写性能不如 MySQL,不适合高频写入场景(比如日志除外)
  • 分页别太深:from + size 超过 10000 会慢,深度分页用 search_after

  • 💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

    赞(0)
    未经允许不得转载:171主机测评 » SpringBoot 整合 Elasticsearch 实战——搜索引擎从入门到落地
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址