Spring Boot 整合 Elasticsearch指南
Elasticsearch是一个基于Lucene的分布式搜索和分析引擎,常用于全文检索、日志分析、实时数据分析等场景。本文详细介绍Spring Boot如何整合Elasticsearch,实现高效的搜索功能。
一、Elasticsearch简介
Elasticsearch(简称ES)是一个开源的分布式搜索引擎,具有以下特点:
分布式架构,支持海量数据
全文检索能力强大
实时性好
支持RESTful API
配套Kibana提供可视化界面
基本概念:
Index(索引):相当于数据库
Type(类型):相当于表(ES7已移除)
Document(文档):相当于行
Field(字段):相当于列
Shard(分片):数据分片
Replica(副本)::数据副本
二、引入依赖
<!--Elasticsearch--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>三、配置
spring: elasticsearch: uris:http://localhost:9200 # ES地址,多个用逗号分隔 # 账号密码(如果需要) # username: elastic # password: password四、创建实体类
@Data // 指定索引名称和类型 @Document(indexName = "user", shards = 3, replicas = 1) publicclassUserDocument{ // 主键 @Id private String id; // 姓名 - 字段类型为keyword(不分词) @Field(type = FieldType.Keyword) private String name; // 邮箱 - keyword类型 @Field(type = FieldType.Keyword) private String email; // 年龄 - integer类型 @Field(type = FieldType.Integer) private Integer age; // 简介 - text类型(会分词) @Field(type = FieldType.Text, analyzer = "ik_max_word") private String bio; // 创建时间 @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second_millis) private LocalDateTime createTime; }常用FieldType:
Text:文本类型,会分词,用于全文检索
Keyword:关键字类型,不分词,用于精确匹配
Integer/Long:整数类型
Float/Double:浮点类型
Boolean:布尔类型
Date:日期类型
Object:对象类型
Nested:嵌套对象类型
五、创建Repository
publicinterfaceUserSearchRepositoryextendsElasticsearchRepository<UserDocument, String> { // 根据姓名查询 List<UserDocument> findByName(String name); // 根据年龄范围查询 List<UserDocument> findByAgeBetween(Integer from, Integer to); // 模糊查询 List<UserDocument> findByBioContaining(String keyword); }六、基本操作
@Service publicclassUserSearchService{ @Autowired private UserSearchRepository userSearchRepository; /** * 保存文档 */ publicvoidsave(UserDocument user){ userSearchRepository.save(user); } /** * 批量保存 */ publicvoidbatchSave(List<UserDocument> users){ userSearchRepository.saveAll(users); } /** * 根据ID查询 */ public UserDocument findById(String id){ return userSearchRepository.findById(id).orElse(null); } /** * 查询所有 */ public Iterable<UserDocument> findAll(){ return userSearchRepository.findAll(); } /** * 根据姓名查询 */ public List<UserDocument> findByName(String name){ return userSearchRepository.findByName(name); } /** * 删除 */ publicvoiddelete(String id){ userSearchRepository.deleteById(id); } /** * 删除所有 */ publicvoiddeleteAll(){ userSearchRepository.deleteAll(); } }七、复杂查询(NativeSearchQuery)
有时候复杂的查询逻辑无法通过方法名来实现,需要使用NativeSearchQuery。
@Service publicclassUserSearchService{ @Autowired private ElasticsearchTemplate elasticsearchTemplate; /** * 条件查询 */ public List<UserDocument> searchByCondition(String keyword, Integer age){ // 构建查询条件 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // 姓名或简介中包含关键词 if (StringUtils.isNotBlank(keyword)) { boolQueryBuilder.should(QueryBuilders.matchQuery("name", keyword)); boolQueryBuilder.should(QueryBuilders.matchQuery("bio", keyword)); } // 年龄大于指定值 if (age != null) { boolQueryBuilder.must(QueryBuilders.rangeQuery("age").gte(age)); } // 构建查询对象 NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(boolQueryBuilder) .build(); // 执行查询 SearchHits<UserDocument> hits = elasticsearchTemplate.search(searchQuery, UserDocument.class); // 转换为列表 return hits.stream() .map(SearchHit::getContent) .collect(Collectors.toList()); } /** * 分页查询 */ public Page<UserDocument> searchPage(String keyword, int page, int size){ // 查询条件:匹配关键词 MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("bio", keyword); // 分页查询 NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(queryBuilder) .withPageable(PageRequest.of(page, size)) .build(); SearchHits<UserDocument> hits = elasticsearchTemplate.search(searchQuery, UserDocument.class); // 转换为分页结果 List<UserDocument> list = hits.stream() .map(SearchHit::getContent) .collect(Collectors.toList()); returnnew PageImpl<>(list, PageRequest.of(page, size), hits.getTotalHits()); } /** * 聚合查询 - 按年龄分组统计 */ public Map<Integer, Long> aggregateByAge(){ // 构建聚合 TermsAggregationBuilder aggregation = AggregationBuilders .terms("age_agg") .field("age") .size(100); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withAggregation(aggregation) .build(); SearchHits<UserDocument> hits = elasticsearchTemplate.search(searchQuery, UserDocument.class); // 解析聚合结果 Terms terms = hits.getAggregations().get("age_agg"); Map<Integer, Long> result = new HashMap<>(); for (Terms.Bucket bucket : terms.getBuckets()) { result.put(bucket.getKeyAsNumber().intValue(), bucket.getDocCount()); } return result; } /** * 高亮显示 */ public List<UserDocument> searchWithHighlight(String keyword){ // 构建高亮字段 HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.field("bio"); // 对bio字段高亮 highlightBuilder.preTags("<span style='color:red'>"); // 前缀 highlightBuilder.postTags("</span>"); // 后缀 NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchQuery("bio", keyword)) .withHighlightBuilder(highlightBuilder) .build(); SearchHits<UserDocument> hits = elasticsearchTemplate.search(searchQuery, UserDocument.class); return hits.stream() .map(SearchHit::getContent) .collect(Collectors.toList()); } }八、ES与MySQL数据同步
实际项目中,ES的数据通常来自MySQL,需要保持数据同步。
方案一:Logstash同步
通过Canal监听MySQL binlog
写入ES(推荐生产环境使用)
方案二:定时任务同步
@Service publicclassDataSyncService{ @Autowired private UserMapper userMapper; @Autowired private UserSearchRepository userSearchRepository; /** * 全量同步 - 定时执行 */ @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点 publicvoidfullSync(){ // 查询所有用户 List<User> users = userMapper.selectList(null); // 转换为ES文档 List<UserDocument> documents = users.stream() .map(this::convertToDocument) .collect(Collectors.toList()); // 批量保存到ES userSearchRepository.saveAll(documents); System.out.println("全量同步完成,共同步 " + documents.size() + " 条数据"); } /** * 增量同步 - 通过消息队列 */ publicvoidincrementalSync(User user){ UserDocument document = convertToDocument(user); userSearchRepository.save(document); } private UserDocument convertToDocument(User user){ UserDocument doc = new UserDocument(); doc.setId(user.getId().toString()); doc.setName(user.getName()); doc.setEmail(user.getEmail()); doc.setAge(user.getAge()); doc.setBio(user.getBio()); doc.setCreateTime(user.getCreateTime()); return doc; } }九、总结
Elasticsearch是处理海量数据搜索的利器,本文介绍了:
基本概念:Index、Document、Field
Spring Data Elasticsearch:简化ES操作
Repository:快速实现CRUD
NativeSearchQuery:复杂查询
分页、聚合、高亮:高级功能
数据同步:ES与MySQL同步方案
记住:ES不是替代MySQL,而是MySQL的补充!
