崩溃大陆2免安装绿色中文版
387M · 2025-11-04
本文面向有一定 Spring Boot 开发经验的 Java 程序员,将详细介绍如何在现有 Spring Boot + MySQL 技术栈中集成 ElasticSearch,实现高效的数据搜索功能。
ElasticSearch 是一个基于 Lucene 构建的开源、分布式、RESTful 搜索引擎。它提供了一个分布式多用户能力的全文搜索引擎,能够处理大规模数据的实时搜索和分析需求。
核心特性:
在传统的关系型数据库(如 MySQL)中,面对以下场景时会遇到瓶颈:
xml
<!-- Spring Boot Starter Data Elasticsearch -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- Spring Data JPA -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
yaml
# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/blog_db
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  
  elasticsearch:
    uris: http://localhost:9200
    connection-timeout: 10s
    socket-timeout: 30s
# 自定义配置
app:
  elasticsearch:
    index-settings:
      number-of-shards: 3
      number-of-replicas: 1
java
@Entity
@Table(name = "articles")
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 500)
    private String title;
    
    @Column(columnDefinition = "TEXT")
    private String content;
    
    @Column(name = "author_id")
    private Long authorId;
    
    @Column(name = "create_time")
    private LocalDateTime createTime;
    
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    // getters and setters
}
java
@Document(indexName = "articles")
public class ArticleDocument {
    @Id
    private String id;
    
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String title;
    
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String content;
    
    @Field(type = FieldType.Long)
    private Long authorId;
    
    @Field(type = FieldType.Keyword)
    private String authorName;
    
    @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
    private LocalDateTime createTime;
    
    // getters and setters
}
java
@Service
@Transactional
public class ArticleService {
    
    private final ArticleRepository articleRepository;
    private final ArticleSearchRepository articleSearchRepository;
    
    public ArticleService(ArticleRepository articleRepository, 
                         ArticleSearchRepository articleSearchRepository) {
        this.articleRepository = articleRepository;
        this.articleSearchRepository = articleSearchRepository;
    }
    
    public Article createArticle(Article article) {
        // 保存到 MySQL
        Article savedArticle = articleRepository.save(article);
        
        // 同步到 ElasticSearch
        ArticleDocument document = convertToDocument(savedArticle);
        articleSearchRepository.save(document);
        
        return savedArticle;
    }
    
    public void updateArticle(Long id, Article article) {
        // 更新 MySQL
        article.setId(id);
        Article updatedArticle = articleRepository.save(article);
        
        // 更新 ElasticSearch
        ArticleDocument document = convertToDocument(updatedArticle);
        articleSearchRepository.save(document);
    }
    
    private ArticleDocument convertToDocument(Article article) {
        ArticleDocument document = new ArticleDocument();
        document.setId(article.getId().toString());
        document.setTitle(article.getTitle());
        document.setContent(article.getContent());
        document.setAuthorId(article.getAuthorId());
        document.setCreateTime(article.getCreateTime());
        return document;
    }
}
对于存量数据或大数据量场景,建议使用 Logstash 进行数据同步:
ruby
# mysql-to-es.conf
input {
  jdbc {
    jdbc_driver_library => "/path/to/mysql-connector-java-8.0.23.jar"
    jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
    jdbc_connection_string => "jdbc:mysql://localhost:3306/blog_db"
    jdbc_user => "root"
    jdbc_password => "password"
    schedule => "* * * * *"
    statement => "SELECT id, title, content, author_id, create_time FROM articles WHERE update_time > :sql_last_value"
    use_column_value => true
    tracking_column => "update_time"
  }
}
output {
  elasticsearch {
    hosts => ["localhost:9200"]
    index => "articles"
    document_id => "%{id}"
  }
}
java
public interface ArticleSearchRepository extends ElasticsearchRepository<ArticleDocument, String> {
    
    // 简单查询
    List<ArticleDocument> findByTitle(String title);
    
    // 分词搜索
    List<ArticleDocument> findByTitleContainingOrContentContaining(String title, String content);
    
    // 使用 @Query 注解自定义查询
    @Query("""
    {
      "bool": {
        "should": [
          { "match": { "title": "?0" } },
          { "match": { "content": "?0" } }
        ]
      }
    }
    """)
    List<ArticleDocument> findByCustomQuery(String keyword);
    
    // 分页查询
    Page<ArticleDocument> findByTitleContaining(String title, Pageable pageable);
}
java
@Service
public class ArticleSearchService {
    
    private final ElasticsearchRestTemplate elasticsearchTemplate;
    
    public ArticleSearchService(ElasticsearchRestTemplate elasticsearchTemplate) {
        this.elasticsearchTemplate = elasticsearchTemplate;
    }
    
    public SearchHits<ArticleDocument> advancedSearch(ArticleSearchRequest request) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        
        // 构建布尔查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        
        if (StringUtils.hasText(request.getKeyword())) {
            boolQuery.must(QueryBuilders.multiMatchQuery(request.getKeyword(), "title", "content")
                    .analyzer("ik_smart"));
        }
        
        if (request.getAuthorId() != null) {
            boolQuery.filter(QueryBuilders.termQuery("authorId", request.getAuthorId()));
        }
        
        if (request.getStartTime() != null && request.getEndTime() != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("createTime")
                    .gte(request.getStartTime())
                    .lte(request.getEndTime()));
        }
        
        queryBuilder.withQuery(boolQuery);
        
        // 高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title").preTags("<em>").postTags("</em>");
        highlightBuilder.field("content").preTags("<em>").postTags("</em>");
        queryBuilder.withHighlightBuilder(highlightBuilder);
        
        // 分页和排序
        queryBuilder.withPageable(PageRequest.of(request.getPage(), request.getSize()));
        queryBuilder.withSort(SortBuilders.scoreSort());
        
        NativeSearchQuery searchQuery = queryBuilder.build();
        
        return elasticsearchTemplate.search(searchQuery, ArticleDocument.class);
    }
}
索引 (Index)
文档 (Document)
分片 (Shard)
映射 (Mapping)
Match Query
Term Query
Bool Query
java
@Configuration
public class ElasticsearchConfig {
    
    @Value("${app.elasticsearch.index-settings.number-of-shards:3}")
    private int numberOfShards;
    
    @Value("${app.elasticsearch.index-settings.number-of-replicas:1}")
    private int numberOfReplicas;
    
    @Bean
    public ElasticsearchRestTemplate elasticsearchTemplate(ElasticsearchRestTemplate restTemplate) {
        // 创建索引时应用配置
        return restTemplate;
    }
}
java
@Service
public class OptimizedSearchService {
    
    public SearchHits<ArticleDocument> optimizedSearch(String keyword) {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.multiMatchQuery(keyword, "title^2", "content")
                        .analyzer("ik_smart")
                        .fuzziness("AUTO"))
                .withPageable(PageRequest.of(0, 20))
                .withSourceFilter(new FetchSourceFilter(new String[]{"id", "title", "authorName"}, null))
                .build();
        
        query.setTrackTotalHits(false); // 对于大量数据,避免精确计数
        
        return elasticsearchTemplate.search(query, ArticleDocument.class);
    }
}
java
@Component
public class ElasticsearchHealthCheck {
    
    private final ElasticsearchRestTemplate elasticsearchTemplate;
    
    public ElasticsearchHealthCheck(ElasticsearchRestTemplate elasticsearchTemplate) {
        this.elasticsearchTemplate = elasticsearchTemplate;
    }
    
    public boolean isClusterHealthy() {
        try {
            ClusterHealth clusterHealth = elasticsearchTemplate.execute(client -> 
                client.cluster().health(RequestOptions.DEFAULT));
            return clusterHealth.getStatus() != ClusterHealthStatus.RED;
        } catch (Exception e) {
            return false;
        }
    }
}
java
@RestController
public class MetricsController {
    
    private final MeterRegistry meterRegistry;
    private final Counter searchRequestCounter;
    
    public MetricsController(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.searchRequestCounter = Counter.builder("es.search.requests")
                .description("Number of search requests")
                .register(meterRegistry);
    }
    
    @PostMapping("/api/search")
    public ResponseEntity<SearchResult> search(@RequestBody SearchRequest request) {
        searchRequestCounter.increment();
        // 搜索逻辑
    }
}
通过本文的介绍,我们了解了如何在 Spring Boot + MySQL 的技术栈中集成 ElasticSearch,实现了:
这种架构模式既保留了关系型数据库的事务特性,又获得了搜索引擎的高性能查询能力,是构建现代 Web 应用的理想选择。