Skip to content

Spring Data Elasticsearch

什么是Elasticsearch?

Elasticsearch是一个基于Lucene的开源分布式搜索和分析引擎,专为水平扩展、高可靠性和易管理而设计。它可以近实时地存储、搜索和分析大量数据,通常用于实现应用程序搜索、网站搜索、企业搜索、日志分析、实时分析等功能。

Spring Data Elasticsearch简介

Spring Data Elasticsearch是Spring Data项目的一部分,旨在为Elasticsearch提供更简单的编程模型。它提供了与Spring Framework集成的高级抽象,简化了与Elasticsearch的交互,使开发者能够专注于业务逻辑而非技术细节。

主要特性

  1. Repository抽象:简化基本CRUD操作和查询
  2. 实体映射:Java对象与Elasticsearch文档的自动映射
  3. ElasticsearchTemplate:提供丰富的API进行复杂操作
  4. 注解支持:使用注解定义索引和字段映射
  5. 高级查询DSL:通过API构建复杂查询
  6. 地理空间查询:支持地理位置搜索
  7. 聚合分析:支持Elasticsearch的聚合功能
  8. 响应式API:支持响应式编程模型

依赖配置

Maven

xml
<!-- Spring Boot方式 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

<!-- 非Spring Boot方式 -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-elasticsearch</artifactId>
    <version>5.1.0</version>
</dependency>

配置Elasticsearch连接

Spring Boot配置

properties
# application.properties
spring.elasticsearch.uris=http://localhost:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=password

Java配置

java
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.repository")
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
    
    @Value("${spring.elasticsearch.uris}")
    private String elasticsearchUrl;
    
    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
                .connectedTo(elasticsearchUrl.substring(7)) // 移除 "http://"
                .withBasicAuth("elastic", "password")
                .withConnectTimeout(Duration.ofSeconds(5))
                .withSocketTimeout(Duration.ofSeconds(3))
                .build();
    }
    
    @Bean
    @Override
    public ElasticsearchOperations elasticsearchOperations() {
        return new ElasticsearchRestTemplate(elasticsearchClient());
    }
}

实体映射

Elasticsearch文档映射到Java对象:

java
@Document(indexName = "products")
public class Product {
    
    @Id
    private String id;
    
    @Field(type = FieldType.Text, analyzer = "standard")
    private String name;
    
    @Field(type = FieldType.Text, analyzer = "standard")
    private String description;
    
    @Field(type = FieldType.Double)
    private Double price;
    
    @Field(type = FieldType.Keyword)
    private String category;
    
    @Field(type = FieldType.Boolean)
    private Boolean available;
    
    @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
    private Date createdAt;
    
    @Field(type = FieldType.Nested)
    private List<Review> reviews;
    
    @GeoPointField
    private GeoPoint location;
    
    // 构造函数、getter和setter
}

public class Review {
    @Field(type = FieldType.Keyword)
    private String username;
    
    @Field(type = FieldType.Integer)
    private Integer rating;
    
    @Field(type = FieldType.Text)
    private String comment;
    
    // 构造函数、getter和setter
}

索引管理

创建索引和映射

java
@Service
public class IndexService {
    
    private final ElasticsearchOperations operations;
    
    public IndexService(ElasticsearchOperations operations) {
        this.operations = operations;
    }
    
    // 检查索引是否存在
    public boolean indexExists(String indexName) {
        return operations.indexOps(IndexCoordinates.of(indexName)).exists();
    }
    
    // 创建索引
    public boolean createIndex(String indexName) {
        return operations.indexOps(IndexCoordinates.of(indexName)).create();
    }
    
    // 根据Entity类创建索引
    public boolean createProductIndex() {
        IndexOperations indexOps = operations.indexOps(Product.class);
        return indexOps.create();
    }
    
    // 创建自定义映射
    public void createMapping() {
        IndexOperations indexOps = operations.indexOps(Product.class);
        Document mapping = indexOps.createMapping();
        indexOps.putMapping(mapping);
    }
    
    // 删除索引
    public boolean deleteIndex(String indexName) {
        return operations.indexOps(IndexCoordinates.of(indexName)).delete();
    }
}

Repository操作

使用ElasticsearchRepository接口:

java
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
    
    // 基本查询方法
    List<Product> findByName(String name);
    
    // 多条件查询
    List<Product> findByNameAndCategory(String name, String category);
    
    // 模糊查询
    List<Product> findByNameContaining(String name);
    
    // 范围查询
    List<Product> findByPriceBetween(double minPrice, double maxPrice);
    
    // 排序
    List<Product> findByAvailableTrue(Sort sort);
    
    // 嵌套查询
    List<Product> findByReviews_Rating(int rating);
    
    // 地理位置查询
    List<Product> findByLocationNear(GeoPoint location, Distance distance);
    
    // 分页
    Page<Product> findByCategory(String category, Pageable pageable);
    
    // 自定义查询
    @Query("{\"bool\": {\"must\": [{\"match\": {\"name\": \"?0\"}}]}}")
    List<Product> findByNameCustomQuery(String name);
}

ElasticsearchOperations和ElasticsearchRestTemplate

对于更复杂的查询和操作:

java
@Service
public class ProductService {
    
    private final ElasticsearchOperations elasticsearchOperations;
    private final ProductRepository productRepository;
    
    public ProductService(ElasticsearchOperations elasticsearchOperations, 
                           ProductRepository productRepository) {
        this.elasticsearchOperations = elasticsearchOperations;
        this.productRepository = productRepository;
    }
    
    // 保存文档
    public Product save(Product product) {
        return productRepository.save(product);
    }
    
    // 批量保存
    public Iterable<Product> saveAll(List<Product> products) {
        return productRepository.saveAll(products);
    }
    
    // 根据ID查找
    public Optional<Product> findById(String id) {
        return productRepository.findById(id);
    }
    
    // 查找所有
    public Iterable<Product> findAll() {
        return productRepository.findAll();
    }
    
    // 删除
    public void delete(Product product) {
        productRepository.delete(product);
    }
    
    // 使用NativeQuery
    public List<Product> searchByNameAndCategory(String name, String category) {
        // 使用Elasticsearch的Query DSL构建查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("name", name))
                .must(QueryBuilders.termQuery("category", category));
        
        NativeQuery searchQuery = new NativeQuery(boolQuery);
        return elasticsearchOperations.search(searchQuery, Product.class)
                .stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
    }
    
    // 全文搜索
    public List<Product> searchProducts(String text) {
        // 在多个字段中搜索
        MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery(text)
                .field("name", 2.0f)  // 提高name字段的权重
                .field("description")
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS);
        
        NativeQuery searchQuery = new NativeQuery(multiMatchQuery);
        return elasticsearchOperations.search(searchQuery, Product.class)
                .stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
    }
    
    // 分页和排序
    public Page<Product> findByCategory(String category, int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("price").ascending());
        return productRepository.findByCategory(category, pageable);
    }
    
    // 复杂搜索带高亮
    public List<SearchHit<Product>> searchWithHighlight(String text) {
        // 构建查询
        MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("description", text);
        
        // 配置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("description");
        highlightBuilder.preTags("<strong>");
        highlightBuilder.postTags("</strong>");
        
        NativeQuery searchQuery = new NativeQuery(matchQuery);
        searchQuery.setHighlightQuery(
            new HighlightQuery(
                new Highlight(highlightBuilder),
                Product.class
            )
        );
        
        return elasticsearchOperations.search(searchQuery, Product.class).getSearchHits();
    }
}

高级查询

复杂条件查询

java
public List<Product> complexSearch(String name, List<String> categories, 
                                  Double minPrice, Double maxPrice, 
                                  Boolean available) {
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    
    // 动态添加条件
    if (name != null && !name.isEmpty()) {
        boolQuery.must(QueryBuilders.matchQuery("name", name));
    }
    
    if (categories != null && !categories.isEmpty()) {
        boolQuery.filter(QueryBuilders.termsQuery("category", categories));
    }
    
    if (minPrice != null || maxPrice != null) {
        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
        if (minPrice != null) {
            rangeQuery.gte(minPrice);
        }
        if (maxPrice != null) {
            rangeQuery.lte(maxPrice);
        }
        boolQuery.filter(rangeQuery);
    }
    
    if (available != null) {
        boolQuery.filter(QueryBuilders.termQuery("available", available));
    }
    
    NativeQuery searchQuery = new NativeQuery(boolQuery);
    return elasticsearchOperations.search(searchQuery, Product.class)
            .stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
}

聚合查询

java
public Map<String, Long> countProductsByCategory() {
    TermsAggregationBuilder aggregation = AggregationBuilders.terms("categories")
            .field("category");
    
    NativeQuery query = new NativeQuery(
        QueryBuilders.matchAllQuery(),
        new NativeSearchAggregation(aggregation)
    );
    
    SearchHits<Product> searchHits = elasticsearchOperations.search(query, Product.class);
    ParsedTerms terms = searchHits.getAggregations().get("categories");
    
    Map<String, Long> results = new HashMap<>();
    for (Terms.Bucket bucket : terms.getBuckets()) {
        results.put(bucket.getKeyAsString(), bucket.getDocCount());
    }
    
    return results;
}

public Map<String, Object> getProductPriceStats() {
    StatsAggregationBuilder aggregation = AggregationBuilders.stats("price_stats")
            .field("price");
    
    NativeQuery query = new NativeQuery(
        QueryBuilders.matchAllQuery(),
        new NativeSearchAggregation(aggregation)
    );
    
    SearchHits<Product> searchHits = elasticsearchOperations.search(query, Product.class);
    ParsedStats stats = searchHits.getAggregations().get("price_stats");
    
    Map<String, Object> results = new HashMap<>();
    results.put("avg", stats.getAvg());
    results.put("min", stats.getMin());
    results.put("max", stats.getMax());
    results.put("sum", stats.getSum());
    results.put("count", stats.getCount());
    
    return results;
}

地理空间查询

java
public List<Product> findProductsNearLocation(double lat, double lon, String distance) {
    GeoDistanceQueryBuilder geoDistanceQuery = QueryBuilders.geoDistanceQuery("location")
            .point(lat, lon)
            .distance(distance);
    
    NativeQuery searchQuery = new NativeQuery(geoDistanceQuery);
    
    // 按距离排序
    GeoDistanceSortBuilder sort = SortBuilders.geoDistanceSort("location", lat, lon)
            .order(SortOrder.ASC);
    searchQuery.addSort(sort);
    
    return elasticsearchOperations.search(searchQuery, Product.class)
            .stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
}

模糊匹配和自动完成

java
public List<String> autoComplete(String prefix) {
    WildcardQueryBuilder wildcardQuery = QueryBuilders.wildcardQuery("name", prefix + "*");
    
    NativeQuery searchQuery = new NativeQuery(wildcardQuery);
    searchQuery.setFields(Collections.singletonList("name"));
    
    return elasticsearchOperations.search(searchQuery, Product.class)
            .stream()
            .map(hit -> hit.getContent().getName())
            .distinct()
            .collect(Collectors.toList());
}

public List<Product> fuzzySearch(String term) {
    FuzzyQueryBuilder fuzzyQuery = QueryBuilders.fuzzyQuery("name", term)
            .fuzziness(Fuzziness.AUTO);
    
    NativeQuery searchQuery = new NativeQuery(fuzzyQuery);
    return elasticsearchOperations.search(searchQuery, Product.class)
            .stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
}

批量操作

java
@Service
public class BulkOperationService {
    
    private final ElasticsearchOperations operations;
    
    public BulkOperationService(ElasticsearchOperations operations) {
        this.operations = operations;
    }
    
    public void bulkIndex(List<Product> products) {
        List<IndexQuery> queries = products.stream()
                .map(product -> {
                    IndexQuery indexQuery = new IndexQuery();
                    indexQuery.setId(product.getId());
                    indexQuery.setObject(product);
                    return indexQuery;
                })
                .collect(Collectors.toList());
        
        operations.bulkIndex(queries, IndexCoordinates.of("products"));
    }
    
    public void bulkUpdate(List<Product> products) {
        List<UpdateQuery> queries = products.stream()
                .map(product -> {
                    Map<String, Object> doc = new HashMap<>();
                    doc.put("price", product.getPrice());
                    doc.put("available", product.getAvailable());
                    
                    return UpdateQuery.builder(product.getId())
                            .withDocument(Document.from(doc))
                            .build();
                })
                .collect(Collectors.toList());
        
        operations.bulkUpdate(queries, IndexCoordinates.of("products"));
    }
    
    public void bulkDelete(List<String> productIds) {
        List<Query> queries = productIds.stream()
                .map(id -> new NativeQuery(QueryBuilders.idsQuery().addIds(id)))
                .collect(Collectors.toList());
        
        operations.delete(queries, Product.class, IndexCoordinates.of("products"));
    }
}

响应式Elasticsearch支持

Spring Data Elasticsearch也提供了响应式编程支持:

java
// 配置
@Configuration
@EnableReactiveElasticsearchRepositories
public class ReactiveElasticsearchConfig extends AbstractReactiveElasticsearchConfiguration {
    
    @Override
    public ReactiveElasticsearchClient reactiveElasticsearchClient() {
        ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("localhost:9200")
                .build();
        
        return ReactiveRestClients.create(clientConfiguration);
    }
}

// 响应式Repository
public interface ReactiveProductRepository extends ReactiveElasticsearchRepository<Product, String> {
    
    Flux<Product> findByName(String name);
    
    Flux<Product> findByCategory(String category);
    
    Mono<Product> findByNameAndCategory(String name, String category);
}

// 服务层
@Service
public class ReactiveProductService {
    
    private final ReactiveProductRepository productRepository;
    private final ReactiveElasticsearchOperations operations;
    
    public ReactiveProductService(ReactiveProductRepository productRepository,
                                  ReactiveElasticsearchOperations operations) {
        this.productRepository = productRepository;
        this.operations = operations;
    }
    
    public Mono<Product> save(Product product) {
        return productRepository.save(product);
    }
    
    public Flux<Product> findAll() {
        return productRepository.findAll();
    }
    
    public Mono<Product> findById(String id) {
        return productRepository.findById(id);
    }
    
    public Flux<Product> search(String text) {
        NativeQuery searchQuery = new NativeQuery(
            QueryBuilders.multiMatchQuery(text, "name", "description")
        );
        
        return operations.search(searchQuery, Product.class)
                .map(SearchHit::getContent);
    }
}

实际应用场景

1. 全文搜索应用

java
@RestController
@RequestMapping("/api/search")
public class SearchController {
    
    private final ProductService productService;
    
    public SearchController(ProductService productService) {
        this.productService = productService;
    }
    
    @GetMapping
    public List<Product> search(@RequestParam String query,
                               @RequestParam(required = false) List<String> categories,
                               @RequestParam(required = false) Double minPrice,
                               @RequestParam(required = false) Double maxPrice) {
        
        return productService.complexSearch(query, categories, minPrice, maxPrice, true);
    }
    
    @GetMapping("/autocomplete")
    public List<String> autocomplete(@RequestParam String prefix) {
        return productService.autoComplete(prefix);
    }
}

2. 日志分析系统

java
@Document(indexName = "logs")
public class LogEntry {
    
    @Id
    private String id;
    
    @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
    private Date timestamp;
    
    @Field(type = FieldType.Keyword)
    private String level;
    
    @Field(type = FieldType.Text)
    private String message;
    
    @Field(type = FieldType.Keyword)
    private String application;
    
    @Field(type = FieldType.Object)
    private Map<String, Object> details;
    
    // 构造函数、getter和setter
}

@Service
public class LogAnalysisService {
    
    private final ElasticsearchOperations operations;
    
    public LogAnalysisService(ElasticsearchOperations operations) {
        this.operations = operations;
    }
    
    public List<LogEntry> findErrorLogs(Date startDate, Date endDate) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("level", "ERROR"))
                .must(QueryBuilders.rangeQuery("timestamp")
                        .from(startDate.getTime())
                        .to(endDate.getTime()));
        
        NativeQuery searchQuery = new NativeQuery(boolQuery);
        
        return operations.search(searchQuery, LogEntry.class)
                .stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
    }
    
    public Map<String, Long> countLogsByLevel() {
        TermsAggregationBuilder aggregation = AggregationBuilders.terms("log_levels")
                .field("level");
        
        NativeQuery query = new NativeQuery(
            QueryBuilders.matchAllQuery(),
            new NativeSearchAggregation(aggregation)
        );
        
        SearchHits<LogEntry> searchHits = operations.search(query, LogEntry.class);
        ParsedTerms terms = searchHits.getAggregations().get("log_levels");
        
        Map<String, Long> results = new HashMap<>();
        for (Terms.Bucket bucket : terms.getBuckets()) {
            results.put(bucket.getKeyAsString(), bucket.getDocCount());
        }
        
        return results;
    }
}

3. 实时分析仪表板

java
@Service
public class DashboardService {
    
    private final ElasticsearchOperations operations;
    
    public DashboardService(ElasticsearchOperations operations) {
        this.operations = operations;
    }
    
    public Map<String, Object> getProductSalesDashboard() {
        // 销售总额
        SumAggregationBuilder totalSales = AggregationBuilders.sum("total_sales")
                .field("price");
        
        // 按类别分组
        TermsAggregationBuilder categorySales = AggregationBuilders.terms("category_sales")
                .field("category")
                .subAggregation(AggregationBuilders.sum("category_sum").field("price"));
        
        // 时间趋势
        DateHistogramAggregationBuilder salesTrend = AggregationBuilders.dateHistogram("sales_trend")
                .field("orderDate")
                .calendarInterval(DateHistogramInterval.DAY)
                .subAggregation(AggregationBuilders.sum("daily_sales").field("price"));
        
        NativeQuery query = new NativeQuery(QueryBuilders.matchAllQuery());
        query.addAggregation(totalSales);
        query.addAggregation(categorySales);
        query.addAggregation(salesTrend);
        
        SearchHits<Product> searchHits = operations.search(query, Product.class);
        Aggregations aggregations = searchHits.getAggregations();
        
        // 解析聚合结果并构建仪表板数据
        Map<String, Object> dashboard = new HashMap<>();
        dashboard.put("totalSales", ((ParsedSum) aggregations.get("total_sales")).getValue());
        
        // ...解析其他聚合结果
        
        return dashboard;
    }
}

性能优化

1. 索引设置优化

java
@Service
public class IndexOptimizationService {
    
    private final ElasticsearchOperations operations;
    
    public IndexOptimizationService(ElasticsearchOperations operations) {
        this.operations = operations;
    }
    
    public void optimizeProductIndex() {
        IndexOperations indexOps = operations.indexOps(Product.class);
        
        // 配置索引设置
        Map<String, Object> settings = new HashMap<>();
        
        // 分片和副本设置
        settings.put("number_of_shards", 3);
        settings.put("number_of_replicas", 1);
        
        // 刷新间隔
        settings.put("refresh_interval", "5s");
        
        // 分析器设置
        Map<String, Object> analysis = new HashMap<>();
        
        Map<String, Object> analyzer = new HashMap<>();
        Map<String, Object> customAnalyzer = new HashMap<>();
        customAnalyzer.put("type", "custom");
        customAnalyzer.put("tokenizer", "standard");
        customAnalyzer.put("filter", List.of("lowercase", "asciifolding"));
        analyzer.put("custom_analyzer", customAnalyzer);
        
        analysis.put("analyzer", analyzer);
        settings.put("analysis", analysis);
        
        indexOps.create(settings);
    }
    
    // 性能监控
    public Map<String, Object> getIndexStats(String indexName) {
        // 获取索引统计信息
        // 需要使用ElasticsearchClient直接访问Elasticsearch API
        RestHighLevelClient client = operations.getElasticsearchClient().rest();
        IndicesStatsRequest request = new IndicesStatsRequest().indices(indexName);
        
        try {
            IndicesStatsResponse response = client.indices().stats(request, RequestOptions.DEFAULT);
            // 解析和返回统计信息
            Map<String, Object> stats = new HashMap<>();
            stats.put("documentCount", response.getTotal().getDocs().getCount());
            stats.put("indexSize", response.getTotal().getStore().getSizeInBytes());
            // ...其他统计信息
            return stats;
        } catch (IOException e) {
            throw new RuntimeException("Failed to get index stats", e);
        }
    }
}

2. 查询优化

java
// 使用滚动API处理大结果集
public List<Product> scrollProducts() {
    NativeQuery searchQuery = new NativeQuery(QueryBuilders.matchAllQuery());
    searchQuery.setPageable(PageRequest.of(0, 100));
    
    ScrolledPage<Product> scroll = (ScrolledPage<Product>) elasticsearchOperations.searchForPage(
            searchQuery, Product.class);
    
    List<Product> products = new ArrayList<>(scroll.getContent());
    String scrollId = scroll.getScrollId();
    
    while (scroll.hasContent()) {
        scroll = (ScrolledPage<Product>) elasticsearchOperations.searchScrollContinue(
                scrollId, 1000, Product.class);
        products.addAll(scroll.getContent());
    }
    
    elasticsearchOperations.searchScrollClear(Collections.singletonList(scrollId));
    
    return products;
}

// 使用过滤器缓存
public List<Product> efficientFilteredSearch(List<String> categories) {
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
            .filter(QueryBuilders.termsQuery("category", categories));
    
    NativeQuery searchQuery = new NativeQuery(boolQuery);
    
    return elasticsearchOperations.search(searchQuery, Product.class)
            .stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
}

3. 批量处理与并发

java
// 并行处理大量数据
public void indexLargeDataset(List<Product> products) {
    // 分批处理
    int batchSize = 1000;
    AtomicInteger counter = new AtomicInteger();
    
    Collection<List<Product>> batches = products.stream()
            .collect(Collectors.groupingBy(product -> counter.getAndIncrement() / batchSize))
            .values();
    
    // 并行处理每个批次
    batches.parallelStream().forEach(batch -> {
        List<IndexQuery> indexQueries = batch.stream()
                .map(product -> {
                    IndexQuery indexQuery = new IndexQuery();
                    indexQuery.setId(product.getId());
                    indexQuery.setObject(product);
                    return indexQuery;
                })
                .collect(Collectors.toList());
        
        elasticsearchOperations.bulkIndex(indexQueries, IndexCoordinates.of("products"));
    });
}

测试

Spring Data Elasticsearch提供了测试支持:

java
@SpringBootTest
@TestPropertySource(properties = {
    "spring.elasticsearch.rest.uris=localhost:9200"
})
public class ProductRepositoryTest {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private ElasticsearchOperations operations;
    
    @BeforeEach
    public void setUp() {
        operations.indexOps(Product.class).delete();
        operations.indexOps(Product.class).create();
        
        // 创建测试数据
        Product product1 = new Product();
        product1.setId("1");
        product1.setName("Test Product 1");
        product1.setCategory("Electronics");
        product1.setPrice(100.0);
        product1.setAvailable(true);
        
        Product product2 = new Product();
        product2.setId("2");
        product2.setName("Test Product 2");
        product2.setCategory("Books");
        product2.setPrice(50.0);
        product2.setAvailable(true);
        
        productRepository.saveAll(Arrays.asList(product1, product2));
        
        // 刷新索引
        operations.indexOps(Product.class).refresh();
    }
    
    @Test
    public void testFindByName() {
        List<Product> products = productRepository.findByName("Test Product 1");
        
        assertFalse(products.isEmpty());
        assertEquals("1", products.get(0).getId());
        assertEquals("Test Product 1", products.get(0).getName());
    }
    
    @Test
    public void testFindByPriceBetween() {
        List<Product> products = productRepository.findByPriceBetween(30.0, 80.0);
        
        assertFalse(products.isEmpty());
        assertEquals(1, products.size());
        assertEquals("2", products.get(0).getId());
    }
}

集成Spring Boot Actuator

Spring Boot Actuator可以提供Elasticsearch健康检查:

properties
# application.properties
management.endpoint.health.show-details=always
management.health.elasticsearch.enabled=true

故障排除与最佳实践

常见问题

  1. 连接问题:确保Elasticsearch正在运行,并且连接配置正确

  2. 版本兼容性:确保Spring Data Elasticsearch版本与Elasticsearch服务器版本兼容

  3. 映射问题:字段类型映射不正确可能导致查询问题

  4. 过大的分页请求:避免请求过大的分页,使用scroll API代替

最佳实践

  1. 适当的映射:为字段选择合适的类型和分析器

  2. 索引配置:根据数据规模和查询模式配置分片和副本

  3. 批量操作:使用批量API提高索引性能

  4. 查询优化:使用过滤器和缓存提高查询效率

  5. 异步操作:对于大量操作,考虑使用异步和响应式API

总结

Spring Data Elasticsearch提供了丰富的功能集,简化了与Elasticsearch的集成和交互。通过其Repository抽象、ElasticsearchOperations API以及丰富的查询和聚合支持,开发者可以轻松构建强大的搜索、分析和实时数据处理应用。

无论是简单的文档存储和检索,还是复杂的全文搜索、地理空间查询和聚合分析,Spring Data Elasticsearch都能提供优雅的解决方案,同时保持与Spring生态系统的无缝集成。