侧边栏壁纸
博主头像
winson的blog博主等级

行动起来,活在当下

  • 累计撰写 32 篇文章
  • 累计创建 39 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Spring Data Elasticsearch 使用指南

winson
2025-06-28 / 0 评论 / 0 点赞 / 28 阅读 / 47193 字

Spring Data Elasticsearch 使用指南

版本说明

本文档基于 spring-boot-starter-data-elasticsearch 2.7.18 版本

核心概念

Spring Data Elasticsearch 提供了多个接口来定义对 Elasticsearch 索引的操作:

1. 核心操作接口

1.1 IndexOperations - 索引操作接口

用于索引级别的操作,如创建、删除索引等。

@Component
public class IndexExample {
    @Autowired
    private ElasticsearchOperations elasticsearchOperations;

    public void indexOperations() {
        // 获取索引操作对象
        IndexOperations indexOps = elasticsearchOperations.indexOps(Product.class);

        // 创建索引
        boolean created = indexOps.create();

        // 创建索引并设置映射
        indexOps.createWithMapping();

        // 删除索引
        boolean deleted = indexOps.delete();

        // 判断索引是否存在
        boolean exists = indexOps.exists();

        // 刷新索引
        indexOps.refresh();

        // 设置索引映射
        indexOps.putMapping();
    }
}

对应的 DSL 操作:

// 创建索引
PUT /product

// 删除索引
DELETE /product

// 检查索引是否存在
HEAD /product

// 刷新索引
POST /product/_refresh

1.2 DocumentOperations - 文档操作接口

用于基于 ID 的文档存储、更新和检索操作。

@Component
public class DocumentExample {
    @Autowired
    private ElasticsearchOperations elasticsearchOperations;

    public void documentOperations() {
        Product product = new Product("1", "笔记本电脑", 5999.0);

        // 保存文档
        Product saved = elasticsearchOperations.save(product);

        // 批量保存
        List<Product> products = Arrays.asList(
            new Product("2", "手机", 3999.0),
            new Product("3", "平板", 2999.0)
        );
        Iterable<Product> savedProducts = elasticsearchOperations.save(products);

        // 根据ID获取文档
        Product found = elasticsearchOperations.get("1", Product.class);

        // 根据ID删除文档
        String deletedId = elasticsearchOperations.delete("1", Product.class);

        // 删除实体
        String deletedEntity = elasticsearchOperations.delete(product);

        // 更新文档
        UpdateQuery updateQuery = UpdateQuery.builder("1")
            .withDocument(Document.create()
                .append("price", 5599.0))
            .build();
        UpdateResponse response = elasticsearchOperations.update(updateQuery, IndexCoordinates.of("product"));
    }
}

对应的 DSL 操作:

// 保存/更新文档
PUT /product/_doc/1
{
  "name": "笔记本电脑",
  "price": 5999.0
}

// 批量操作
POST /_bulk
{"index":{"_index":"product","_id":"2"}}
{"name":"手机","price":3999.0}
{"index":{"_index":"product","_id":"3"}}
{"name":"平板","price":2999.0}

// 获取文档
GET /product/_doc/1

// 删除文档
DELETE /product/_doc/1

// 部分更新
POST /product/_update/1
{
  "doc": {
    "price": 5599.0
  }
}

1.3 SearchOperations - 搜索操作接口

用于搜索多个实体的操作。

@Component
public class SearchExample {
    @Autowired
    private ElasticsearchOperations elasticsearchOperations;

    public void searchOperations() {
        // 使用 CriteriaQuery 搜索
        Criteria criteria = new Criteria("name").contains("电脑");
        Query query = new CriteriaQuery(criteria);
        SearchHits<Product> searchHits = elasticsearchOperations.search(query, Product.class);

        // 分页搜索
        Pageable pageable = PageRequest.of(0, 10);
        query.setPageable(pageable);
        SearchHits<Product> pagedHits = elasticsearchOperations.search(query, Product.class);

        // 计数
        long count = elasticsearchOperations.count(query, Product.class);

        // 滚动搜索
        query.setPageable(PageRequest.of(0, 100));
        SearchScrollHits<Product> scroll = elasticsearchOperations.searchScrollStart(1000, query, Product.class, IndexCoordinates.of("product"));
        String scrollId = scroll.getScrollId();
        while (scroll.hasSearchHits()) {
            // 处理当前批次
            scroll = elasticsearchOperations.searchScrollContinue(scrollId, 1000, Product.class, IndexCoordinates.of("product"));
        }
        elasticsearchOperations.searchScrollClear(scrollId);
    }
}

对应的 DSL 操作:

// 基本搜索
POST /product/_search
{
  "query": {
    "match": {
      "name": "电脑"
    }
  }
}

// 分页搜索
POST /product/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "match": {
      "name": "电脑"
    }
  }
}

// 计数
POST /product/_count
{
  "query": {
    "match": {
      "name": "电脑"
    }
  }
}

// 滚动搜索
POST /product/_search?scroll=1m
{
  "size": 100,
  "query": {
    "match_all": {}
  }
}

1.4 ElasticsearchOperations - 综合操作接口

组合了 DocumentOperations 和 SearchOperations 的功能。

@Configuration
@EnableElasticsearchRepositories
public class ElasticsearchConfig {

    @Bean
    public ElasticsearchOperations elasticsearchOperations(ElasticsearchClient elasticsearchClient) {
        return new ElasticsearchTemplate(elasticsearchClient);
    }
}

2. 查询类型详解

2.1 CriteriaQuery - 条件查询

基于条件的查询,无需了解 Elasticsearch 查询语法。

@Component
public class CriteriaQueryExample {
    @Autowired
    private ElasticsearchOperations operations;

    public void criteriaExamples() {
        // 简单查询
        Criteria criteria = new Criteria("price").is(42.0);
        Query query = new CriteriaQuery(criteria);

        // 范围查询
        criteria = new Criteria("price").greaterThan(100.0).lessThan(500.0);

        // AND 条件(链式)
        criteria = new Criteria("category").is("电子产品")
            .and("price").lessThan(5000);

        // OR 条件(子查询)
        criteria = new Criteria("category").is("电子产品")
            .subCriteria(
                new Criteria().or("brand").is("Apple")
                    .or("brand").is("Samsung")
            );

        // 模糊查询
        criteria = new Criteria("name").contains("手机");

        // 前缀查询
        criteria = new Criteria("name").startsWith("苹果");

        // 通配符查询
        criteria = new Criteria("name").expression("*手机*");

        // 存在查询
        criteria = new Criteria("description").exists();

        // 空值查询
        criteria = new Criteria("description").isNull();

        // IN 查询
        criteria = new Criteria("category").in("手机", "平板", "电脑");

        // NOT 查询
        criteria = new Criteria("category").not().is("配件");
    }
}

对应的 DSL:

// 简单查询
{ "term": { "price": 42.0 } }

// 范围查询
{ "range": { "price": { "gt": 100.0, "lt": 500.0 } } }

// AND 条件
{
  "bool": {
    "must": [
      { "term": { "category": "电子产品" } },
      { "range": { "price": { "lt": 5000 } } }
    ]
  }
}

// OR 条件
{
  "bool": {
    "must": [
      { "term": { "category": "电子产品" } }
    ],
    "should": [
      { "term": { "brand": "Apple" } },
      { "term": { "brand": "Samsung" } }
    ],
    "minimum_should_match": 1
  }
}

2.2 StringQuery - 字符串查询

直接使用 JSON 格式的 Elasticsearch 查询。

@Component
public class StringQueryExample {
    @Autowired
    private ElasticsearchOperations operations;

    public void stringQueryExamples() {
        // Match 查询
        String matchQuery = """
            {
              "match": {
                "name": {
                  "query": "苹果手机",
                  "operator": "and"
                }
              }
            }
            """;
        Query query = new StringQuery(matchQuery);

        // Bool 查询
        String boolQuery = """
            {
              "bool": {
                "must": [
                  { "match": { "category": "手机" } }
                ],
                "filter": [
                  { "range": { "price": { "gte": 3000, "lte": 8000 } } }
                ]
              }
            }
            """;
        query = new StringQuery(boolQuery);

        // 聚合查询
        String aggQuery = """
            {
              "match_all": {}
            }
            """;
        String aggString = """
            {
              "categories": {
                "terms": {
                  "field": "category.keyword",
                  "size": 10
                }
              }
            }
            """;
        query = new StringQuery(aggQuery);
        // 注意:聚合需要使用 NativeQuery
    }
}

2.3 NativeQuery - 原生查询

用于复杂查询,支持所有 Elasticsearch 功能。

@Component
public class NativeQueryExample {
    @Autowired
    private ElasticsearchOperations operations;

    public void nativeQueryExamples() {
        // 基本查询
        Query query = NativeQuery.builder()
            .withQuery(q -> q
                .match(m -> m
                    .field("name")
                    .query("手机")
                )
            )
            .build();

        // 复合查询
        query = NativeQuery.builder()
            .withQuery(q -> q
                .bool(b -> b
                    .must(must -> must
                        .match(m -> m.field("category").query("手机"))
                    )
                    .filter(f -> f
                        .range(r -> r
                            .field("price")
                            .gte(JsonData.of(3000))
                            .lte(JsonData.of(8000))
                        )
                    )
                )
            )
            .build();

        // 带聚合的查询
        query = NativeQuery.builder()
            .withAggregation("avgPrice", Aggregation.of(a -> a
                .avg(avg -> avg.field("price"))
            ))
            .withAggregation("categories", Aggregation.of(a -> a
                .terms(t -> t
                    .field("category.keyword")
                    .size(10)
                )
            ))
            .build();

        // 高亮查询
        query = NativeQuery.builder()
            .withQuery(q -> q
                .match(m -> m.field("name").query("手机"))
            )
            .withHighlightQuery(new HighlightQuery(
                new Highlight(List.of(new HighlightField("name"))),
                null
            ))
            .build();

        // 排序和分页
        query = NativeQuery.builder()
            .withQuery(q -> q.matchAll(m -> m))
            .withSort(Sort.by(Sort.Direction.DESC, "price"))
            .withPageable(PageRequest.of(0, 20))
            .build();

        // 指定返回字段
        query = NativeQuery.builder()
            .withQuery(q -> q.matchAll(m -> m))
            .withSourceFilter(new FetchSourceFilter(
                new String[]{"name", "price"},
                new String[]{}
            ))
            .build();
    }
}

对应的 DSL:

// 复合查询
{
  "query": {
    "bool": {
      "must": [
        { "match": { "category": "手机" } }
      ],
      "filter": [
        { "range": { "price": { "gte": 3000, "lte": 8000 } } }
      ]
    }
  }
}

// 聚合查询
{
  "aggs": {
    "avgPrice": {
      "avg": { "field": "price" }
    },
    "categories": {
      "terms": {
        "field": "category.keyword",
        "size": 10
      }
    }
  }
}

// 高亮查询
{
  "query": {
    "match": { "name": "手机" }
  },
  "highlight": {
    "fields": {
      "name": {}
    }
  }
}

3. 搜索结果类型

3.1 SearchHit

包含单个搜索结果的详细信息。

public void handleSearchHit() {
    SearchHits<Product> searchHits = operations.search(query, Product.class);

    for (SearchHit<Product> hit : searchHits) {
        // 获取文档 ID
        String id = hit.getId();

        // 获取得分
        float score = hit.getScore();

        // 获取排序值
        List<Object> sortValues = hit.getSortValues();

        // 获取高亮字段
        Map<String, List<String>> highlightFields = hit.getHighlightFields();

        // 获取实体对象
        Product product = hit.getContent();
    }
}

3.2 SearchHits

包含整个搜索结果的信息。

public void handleSearchHits() {
    SearchHits<Product> searchHits = operations.search(query, Product.class);

    // 总命中数
    long totalHits = searchHits.getTotalHits();

    // 总命中数关系(EQUAL_TO, GREATER_THAN_OR_EQUAL_TO)
    TotalHitsRelation relation = searchHits.getTotalHitsRelation();

    // 最高得分
    float maxScore = searchHits.getMaxScore();

    // 获取聚合结果
    Aggregations aggregations = searchHits.getAggregations();

    // 获取建议结果
    Suggest suggest = searchHits.getSuggest();
}

3.3 SearchPage

支持 Spring Data 分页的搜索结果。

public void handleSearchPage() {
    Pageable pageable = PageRequest.of(0, 10);
    Query query = new CriteriaQuery(criteria).setPageable(pageable);

    SearchHits<Product> searchHits = operations.search(query, Product.class);
    SearchPage<Product> page = SearchHitSupport.searchPageFor(searchHits, pageable);

    // 分页信息
    int totalPages = page.getTotalPages();
    long totalElements = page.getTotalElements();
    int currentPage = page.getNumber();
    int pageSize = page.getSize();

    // 获取内容
    List<Product> content = page.getContent();
}

4. 实体映射注解

@Document(indexName = "product")
@Setting(replicas = 1, shards = 3)
public class Product {
    @Id
    private String id;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String name;

    @Field(type = FieldType.Keyword)
    private String category;

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

    @Field(type = FieldType.Date, format = DateFormat.date_time)
    private Date createTime;

    @Field(type = FieldType.Nested)
    private List<Tag> tags;

    @CompletionField(maxInputLength = 100)
    private Completion suggest;

    // getter/setter
}

5. Repository 支持

@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
    // 方法名查询
    List<Product> findByName(String name);
    List<Product> findByPriceBetween(Double min, Double max);
    List<Product> findByCategoryAndPriceLessThan(String category, Double price);

    // @Query 注解查询
    @Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
    List<Product> searchByName(String name);

    // 高亮查询
    @Highlight(fields = {
        @HighlightField(name = "name"),
        @HighlightField(name = "description")
    })
    List<SearchHit<Product>> findByNameContaining(String keyword);
}

6. 配置示例

# application.yml
spring:
  elasticsearch:
    uris: http://localhost:9200
    username: elastic
    password: password
    connection-timeout: 5s
    socket-timeout: 30s
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.repository")
public class ElasticsearchConfig extends ElasticsearchConfiguration {

    @Override
    public ClientConfiguration clientConfiguration() {
        return ClientConfiguration.builder()
            .connectedTo("localhost:9200")
            .withBasicAuth("elastic", "password")
            .withConnectTimeout(Duration.ofSeconds(5))
            .withSocketTimeout(Duration.ofSeconds(30))
            .build();
    }
}

7. 实际应用示例

7.1 商品搜索服务

@Service
public class ProductSearchService {
    @Autowired
    private ElasticsearchOperations operations;

    public Page<Product> searchProducts(String keyword, Double minPrice, Double maxPrice,
                                      String category, Pageable pageable) {
        // 构建查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (StringUtils.hasText(keyword)) {
            boolQuery.must(QueryBuilders.multiMatchQuery(keyword, "name", "description")
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS));
        }

        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 (StringUtils.hasText(category)) {
            boolQuery.filter(QueryBuilders.termQuery("category.keyword", category));
        }

        // 构建原生查询
        NativeQuery query = NativeQuery.builder()
            .withQuery(q -> q._toQuery(boolQuery))
            .withPageable(pageable)
            .withSort(Sort.by("_score").descending()
                .and(Sort.by("createTime").descending()))
            .build();

        SearchHits<Product> searchHits = operations.search(query, Product.class);
        return SearchHitSupport.searchPageFor(searchHits, pageable);
    }

    public Map<String, Long> getCategoryAggregation() {
        NativeQuery query = NativeQuery.builder()
            .withQuery(q -> q.matchAll(m -> m))
            .withAggregation("categories",
                Aggregation.of(a -> a
                    .terms(t -> t
                        .field("category.keyword")
                        .size(50)
                    )
                )
            )
            .build();

        SearchHits<Product> searchHits = operations.search(query, Product.class);

        Map<String, Long> categoryCount = new HashMap<>();
        if (searchHits.hasAggregations()) {
            Aggregations aggregations = searchHits.getAggregations();
            List<StringTermsBucket> buckets = aggregations.get("categories")
                .sterms().buckets().array();

            for (StringTermsBucket bucket : buckets) {
                categoryCount.put(bucket.key()._toJsonString(), bucket.docCount());
            }
        }

        return categoryCount;
    }
}

7.2 日志分析服务

@Service
public class LogAnalysisService {
    @Autowired
    private ElasticsearchOperations operations;

    public List<LogEntry> searchLogs(String level, String message,
                                   LocalDateTime startTime, LocalDateTime endTime) {
        Criteria criteria = new Criteria();

        if (StringUtils.hasText(level)) {
            criteria = criteria.and("level").is(level);
        }

        if (StringUtils.hasText(message)) {
            criteria = criteria.and("message").contains(message);
        }

        if (startTime != null || endTime != null) {
            Criteria timeCriteria = new Criteria("timestamp");
            if (startTime != null) {
                timeCriteria = timeCriteria.greaterThanEqual(startTime);
            }
            if (endTime != null) {
                timeCriteria = timeCriteria.lessThanEqual(endTime);
            }
            criteria = criteria.and(timeCriteria);
        }

        Query query = new CriteriaQuery(criteria)
            .addSort(Sort.by(Sort.Direction.DESC, "timestamp"));

        SearchHits<LogEntry> hits = operations.search(query, LogEntry.class);
        return hits.stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
    }
}

8. 最佳实践

  1. 索引设计

    • 合理设置分片和副本数
    • 选择合适的分词器
    • 避免过度嵌套
  2. 查询优化

    • 使用 filter 替代 query(filter 可缓存)
    • 合理使用分页,避免深度分页
    • 使用 source filtering 减少传输数据
  3. 批量操作

    • 使用 bulk API 进行批量写入
    • 控制批量大小(通常 1000-5000 个文档)
  4. 错误处理

    • 捕获并处理 ElasticsearchException
    • 实现重试机制
    • 记录详细日志
  5. 性能监控

    • 监控查询响应时间
    • 监控索引大小和文档数
    • 定期优化索引

9. 常见问题

  1. 版本兼容性

    • Spring Boot 2.7.x 对应 Elasticsearch 7.17.x
    • 注意客户端版本与服务端版本匹配
  2. 中文分词

    • 安装 IK 分词器
    • 配置自定义词典
  3. 深度分页问题

    • 使用 scroll API 或 search_after
    • 避免使用大的 from 值
  4. 聚合查询限制

    • text 字段不能直接聚合
    • 使用 keyword 类型或 fielddata

10. 总结

Spring Data Elasticsearch 提供了多种方式来操作 Elasticsearch:

  • CriteriaQuery:适合简单查询,易于理解
  • StringQuery:适合已有查询 DSL 的场景
  • NativeQuery:适合复杂查询,功能最全面
  • Repository:适合简单的 CRUD 操作

选择合适的查询方式,结合实际业务需求,可以高效地实现各种搜索功能。

11. Spring Data Elasticsearch 类架构详解

11.1 核心接口层

Operations 接口家族
1. IndexOperations - 索引操作接口
  • 作用:管理 Elasticsearch 索引的生命周期
  • 主要方法
    • create() - 创建索引 → ES: PUT /index_name
    • createWithMapping() - 创建索引并设置映射 → ES: PUT /index_name with mappings
    • delete() - 删除索引 → ES: DELETE /index_name
    • exists() - 检查索引是否存在 → ES: HEAD /index_name
    • refresh() - 刷新索引 → ES: POST /index_name/_refresh
    • putMapping() - 更新映射 → ES: PUT /index_name/_mapping
    • getSettings() - 获取索引设置 → ES: GET /index_name/_settings
// 使用示例
IndexOperations indexOps = elasticsearchOperations.indexOps(Product.class);
if (!indexOps.exists()) {
    indexOps.createWithMapping();
}
2. DocumentOperations - 文档操作接口
  • 作用:处理单个或批量文档的 CRUD 操作
  • 主要方法
    • save(T entity) - 保存单个文档 → ES: PUT /index/_doc/{id}
    • save(Iterable<T> entities) - 批量保存 → ES: POST /_bulk
    • get(String id, Class<T>) - 根据 ID 获取 → ES: GET /index/_doc/{id}
    • exists(String id, Class<T>) - 检查文档存在 → ES: HEAD /index/_doc/{id}
    • delete(String id, Class<T>) - 删除文档 → ES: DELETE /index/_doc/{id}
    • update(UpdateQuery) - 部分更新 → ES: POST /index/_update/{id}
// 使用示例
Product product = new Product("1", "手机", 3999.0);
Product saved = documentOperations.save(product);
Product retrieved = documentOperations.get("1", Product.class);
3. SearchOperations - 搜索操作接口
  • 作用:执行各种搜索查询和聚合操作
  • 主要方法
    • count(Query) - 统计文档数 → ES: POST /index/_count
    • search(Query) - 执行搜索 → ES: POST /index/_search
    • searchScrollStart() - 开始滚动搜索 → ES: POST /index/_search?scroll=1m
    • searchScrollContinue() - 继续滚动 → ES: POST /_search/scroll
    • multiSearch() - 多重搜索 → ES: POST /_msearch
// 使用示例
Query query = new CriteriaQuery(new Criteria("category").is("手机"));
SearchHits<Product> hits = searchOperations.search(query, Product.class);
4. ElasticsearchOperations - 综合操作接口
  • 作用:整合所有操作接口,提供统一的访问入口
  • 继承关系:继承自 DocumentOperations 和 SearchOperations
  • 额外功能
    • indexOps() - 获取 IndexOperations 实例
    • getElasticsearchConverter() - 获取转换器

11.2 实现类层

1. ElasticsearchTemplate
  • 作用:使用新版 Elasticsearch Java API Client 的实现
  • 特点
    • 基于 co.elastic.clients.elasticsearch.ElasticsearchClient
    • 支持 Elasticsearch 7.17+ 和 8.x
    • 提供类型安全的 API
    • 推荐在新项目中使用
@Configuration
public class ElasticsearchConfig {
    @Bean
    public ElasticsearchOperations elasticsearchOperations(ElasticsearchClient client) {
        return new ElasticsearchTemplate(client);
    }
}
2. ElasticsearchRestTemplate(已废弃)
  • 作用:使用旧版 High Level REST Client 的实现
  • 特点
    • 基于 RestHighLevelClient
    • 在 Spring Data Elasticsearch 5.x 中已标记为废弃
    • 建议迁移到 ElasticsearchTemplate

11.3 查询类体系

1. Query 接口
  • 作用:所有查询类的基础接口
  • 通用功能
    • 设置分页(Pageable)
    • 设置返回字段(Source Filter)
    • 设置查询选项(Track Total Hits 等)
2. CriteriaQuery
  • 作用:基于条件的查询,适合不熟悉 ES 语法的开发者
  • 特点
    • 链式 API 设计
    • 自动转换为 ES 查询 DSL
    • 支持复杂的 AND/OR 组合
// 复杂查询示例
Criteria criteria = new Criteria("category").is("手机")
    .and("price").between(3000, 8000)
    .and(new Criteria("brand").in("Apple", "Samsung")
        .or("features").contains("5G"));
3. StringQuery
  • 作用:直接使用 JSON 字符串查询
  • 使用场景
    • 已有现成的查询 DSL
    • 从 Kibana 复制的查询
    • 动态构建的查询字符串
String queryJson = """
    {
        "bool": {
            "must": [
                {"match": {"name": "手机"}},
                {"range": {"price": {"gte": 3000}}}
            ]
        }
    }
    """;
Query query = new StringQuery(queryJson);
4. NativeQuery
  • 作用:使用 Elasticsearch Java API 构建的原生查询
  • 优势
    • 支持所有 ES 功能
    • 类型安全
    • IDE 自动补全
    • 支持复杂聚合
NativeQuery query = NativeQuery.builder()
    .withQuery(q -> q
        .bool(b -> b
            .must(m -> m.match(mt -> mt.field("name").query("手机")))
            .filter(f -> f.range(r -> r.field("price").gte(JsonData.of(3000))))
        )
    )
    .withAggregation("avgPrice", a -> a
        .avg(avg -> avg.field("price"))
    )
    .build();
5. Criteria 类
  • 作用:构建查询条件的核心类
  • 支持的操作
    • 比较操作:is, equals, not
    • 范围操作:greaterThan, lessThan, between
    • 文本操作:contains, startsWith, endsWith
    • 集合操作:in, notIn
    • 存在性:exists, isNull
    • 逻辑组合:and, or, subCriteria

11.4 结果类体系

1. SearchHit
  • 作用:封装单个搜索结果
  • 包含信息
    • 文档 ID
    • 相关性得分(score)
    • 排序值(sortValues)
    • 高亮结果(highlightFields)
    • 内部命中(innerHits)
    • 实体内容(content)
2. SearchHits
  • 作用:封装整个搜索响应
  • 包含信息
    • 总命中数和关系类型
    • 最高得分
    • SearchHit 列表
    • 聚合结果
    • 建议结果
    • 滚动 ID(如果是滚动查询)
3. SearchPage
  • 作用:支持 Spring Data 分页的搜索结果
  • 实现Page<T> 接口
  • 用途:与 Spring Data 的分页机制无缝集成

11.5 Repository 层

1. ElasticsearchRepository<T, ID>
  • 作用:提供基础的 CRUD 和搜索功能
  • 继承自:Spring Data 的 CrudRepositoryPagingAndSortingRepository
  • 额外方法
    • search(QueryBuilder) - 使用查询构建器搜索
    • search(Query) - 使用 Query 对象搜索
    • searchSimilar() - 相似文档搜索
2. SimpleElasticsearchRepository
  • 作用:ElasticsearchRepository 的默认实现
  • 内部使用:ElasticsearchOperations 完成实际操作
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
    // 方法名查询 - 自动生成查询
    List<Product> findByNameContainingAndPriceBetween(String name, Double minPrice, Double maxPrice);

    // 自定义查询
    @Query("{\"bool\": {\"must\": [{\"match\": {\"name\": \"?0\"}}]}}")
    Page<Product> searchByName(String name, Pageable pageable);
}

11.6 配置类

1. ElasticsearchConfiguration
  • 作用:配置基类,简化配置过程
  • 提供
    • 客户端配置方法
    • Bean 定义模板
    • 转换器配置
2. ClientConfiguration
  • 作用:配置 Elasticsearch 客户端连接
  • 配置项
    • 连接地址和端口
    • 认证信息
    • 超时设置
    • SSL/TLS 配置
    • 自定义请求头
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
    .connectedTo("localhost:9200", "localhost:9201")  // 集群地址
    .usingSsl()                                        // 启用 SSL
    .withBasicAuth("elastic", "password")              // 基础认证
    .withConnectTimeout(Duration.ofSeconds(5))         // 连接超时
    .withSocketTimeout(Duration.ofSeconds(30))         // 套接字超时
    .withHeaders(() -> {                               // 自定义头
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Custom-Header", "value");
        return headers;
    })
    .build();

11.7 注解体系

1. @Document
  • 作用:标记实体类映射到 ES 索引
  • 属性
    • indexName - 索引名称
    • createIndex - 是否自动创建索引
    • versionType - 版本控制类型
2. @Field
  • 作用:配置字段映射
  • 属性
    • type - 字段类型(text, keyword, long 等)
    • analyzer - 分析器
    • format - 日期格式
    • fielddata - 是否启用 fielddata
3. @Id
  • 作用:标记文档 ID 字段

11.8 类之间的协作关系

  1. 客户端创建流程

    ElasticsearchConfiguration → ClientConfiguration → ElasticsearchClient → ElasticsearchTemplate
    
  2. 查询执行流程

    Repository方法 → Query对象 → ElasticsearchOperations → ElasticsearchClient → Elasticsearch
    
  3. 结果处理流程

    Elasticsearch响应 → SearchHits → SearchHit → Entity对象
    
  4. 索引管理流程

    ElasticsearchOperations.indexOps() → IndexOperations → 索引操作
    

这个类体系设计遵循了单一职责原则,每个接口和类都有明确的职责,通过组合和继承实现了功能的复用和扩展。

12. 执行流程详解

12.1 查询执行的完整流程

查询从客户端发起到返回结果,经历了以下几个关键步骤:

  1. 方法调用:客户端通过 Repository 方法或直接使用 ElasticsearchOperations
  2. 查询构建:根据选择的查询类型(CriteriaQuery、StringQuery、NativeQuery)构建查询
  3. 查询转换:ElasticsearchConverter 将查询对象转换为 Elasticsearch DSL
  4. 请求发送:通过 ElasticsearchClient 发送 HTTP 请求到 Elasticsearch
  5. 结果处理:将 ES 响应转换为 SearchHits 和实体对象

12.2 不同查询类型的执行路径

CriteriaQuery 路径
  • 优点:易于理解,不需要了解 ES 语法
  • 缺点:功能有限,复杂查询难以表达
  • 适用场景:简单的条件查询、快速开发
// 执行路径示例
Criteria criteria = new Criteria("name").contains("手机");
// Criteria → CriteriaQueryProcessor → ES Query DSL
StringQuery 路径
  • 优点:灵活,可以使用任何 ES 查询
  • 缺点:容易出错,没有编译时检查
  • 适用场景:已有查询 DSL、动态查询
// 执行路径示例
String json = "{\"match\": {\"name\": \"手机\"}}";
// JSON String → 直接传递 → ES Query DSL
NativeQuery 路径
  • 优点:类型安全,功能完整,IDE 支持
  • 缺点:需要了解 ES Java API
  • 适用场景:复杂查询、聚合分析
// 执行路径示例
NativeQuery query = NativeQuery.builder()
    .withQuery(q -> q.match(m -> m.field("name").query("手机")))
    .build();
// QueryBuilder → ElasticsearchConverter → ES Query DSL

12.3 性能优化建议

  1. 查询优化

    • 使用 filter 代替 query(filter 可缓存)
    • 合理设置返回字段(source filtering)
    • 避免深度分页(使用 scroll 或 search_after)
  2. 批量操作

    • 使用 bulk API 进行批量写入
    • 控制批次大小(1000-5000 文档)
    • 异步处理大量数据
  3. 连接池配置

    ClientConfiguration clientConfiguration = ClientConfiguration.builder()
        .connectedTo("localhost:9200")
        .withConnectTimeout(Duration.ofSeconds(5))
        .withSocketTimeout(Duration.ofSeconds(30))
        .withConnectionRequestTimeout(Duration.ofSeconds(10))
        .build();
    
  4. 缓存策略

    • 对不常变化的数据启用查询缓存
    • 使用 Redis 缓存热点数据
    • 实现本地缓存减少 ES 压力

12.4 常见问题排查

  1. 版本不兼容

    Spring Boot 2.7.x → Spring Data Elasticsearch 4.4.x → Elasticsearch 7.17.x
    Spring Boot 3.0.x → Spring Data Elasticsearch 5.0.x → Elasticsearch 8.x
    
  2. 字段映射错误

    • 检查 @Field 注解配置
    • 确认分析器是否正确
    • 验证字段类型匹配
  3. 查询无结果

    • 检查索引名称
    • 验证查询条件
    • 查看 ES 日志
  4. 性能问题

    • 监控查询耗时
    • 优化索引结构
    • 调整 JVM 参数

通过理解这个完整的类体系和执行流程,您可以更好地使用 Spring Data Elasticsearch 构建高效的搜索应用。

核心类图

classDiagram %% 核心操作接口 class Operations { <<interface>> +IndexCoordinates getIndexCoordinatesFor(Class clazz) } class IndexOperations { <<interface>> +boolean create() +boolean createWithMapping() +boolean delete() +boolean exists() +void refresh() +boolean putMapping(Document mapping) +Map getMapping() +Map getSettings() +boolean putSettings(Document settings) } class DocumentOperations { <<interface>> +T save(T entity) +Iterable~T~ save(Iterable~T~ entities) +T get(String id, Class~T~ clazz) +List~T~ multiGet(Query query, Class~T~ clazz) +boolean exists(String id, Class~T~ clazz) +String delete(String id, Class~T~ clazz) +void delete(Query query, Class~T~ clazz) +UpdateResponse update(UpdateQuery updateQuery) +void bulkUpdate(List~UpdateQuery~ queries) } class SearchOperations { <<interface>> +long count(Query query, Class~T~ clazz) +SearchHits~T~ search(Query query, Class~T~ clazz) +SearchScrollHits~T~ searchScrollStart(long scrollTime, Query query, Class~T~ clazz) +SearchScrollHits~T~ searchScrollContinue(String scrollId, long scrollTime, Class~T~ clazz) +void searchScrollClear(List~String~ scrollIds) +SearchPage~T~ searchForPage(Query query, Class~T~ clazz) } class ElasticsearchOperations { <<interface>> +IndexOperations indexOps(Class~T~ clazz) +IndexOperations indexOps(IndexCoordinates index) +ElasticsearchConverter getElasticsearchConverter() } %% 实现类 class ElasticsearchTemplate { -ElasticsearchClient client -ElasticsearchConverter converter +execute(ClientCallback~T~ callback) } class ElasticsearchRestTemplate { -RestHighLevelClient client -ElasticsearchConverter converter } %% 查询类 class Query { <<interface>> +void addFields(String... fields) +void addSourceFilter(SourceFilter sourceFilter) +void setPageable(Pageable pageable) +Pageable getPageable() } class CriteriaQuery { -Criteria criteria +CriteriaQuery(Criteria criteria) +void addCriteria(Criteria criteria) } class StringQuery { -String source +StringQuery(String source) } class NativeQuery { -QueryBuilder queryBuilder -List~AggregationBuilder~ aggregations -List~SortBuilder~ sorts +static NativeQueryBuilder builder() } class Criteria { -String field -List~Criteria~ criteriaChain +Criteria and(String field) +Criteria or(String field) +Criteria is(Object value) +Criteria contains(String value) +Criteria startsWith(String value) +Criteria endsWith(String value) +Criteria greaterThan(Object value) +Criteria lessThan(Object value) +Criteria between(Object from, Object to) +Criteria in(Collection values) +Criteria notIn(Collection values) +Criteria exists() +Criteria doesNotExist() } %% 结果类 class SearchHit~T~ { -String id -float score -Object[] sortValues -Map~String,List~String~~ highlightFields -T content +String getId() +float getScore() +T getContent() +Map getHighlightFields() } class SearchHits~T~ { -long totalHits -TotalHitsRelation totalHitsRelation -float maxScore -String scrollId -List~SearchHit~T~~ searchHits -Aggregations aggregations +long getTotalHits() +List~SearchHit~T~~ getSearchHits() +boolean hasSearchHits() +Aggregations getAggregations() } class SearchPage~T~ { <<interface>> +SearchHits~T~ getSearchHits() } %% Repository相关 class ElasticsearchRepository~T,ID~ { <<interface>> +Iterable~T~ search(QueryBuilder query) +Page~T~ search(QueryBuilder query, Pageable pageable) +Page~T~ searchSimilar(T entity, String[] fields, Pageable pageable) } class SimpleElasticsearchRepository~T,ID~ { -ElasticsearchOperations operations -Class~T~ entityClass } %% 配置类 class ElasticsearchConfiguration { <<abstract>> +abstract ClientConfiguration clientConfiguration() +ElasticsearchClient elasticsearchClient() +ElasticsearchOperations elasticsearchOperations() } class ClientConfiguration { -List~String~ hostAndPorts -Duration connectTimeout -Duration socketTimeout -HttpHeaders headers +static ClientConfigurationBuilder builder() } %% 注解 class Document { <<annotation>> +String indexName() +String type() +boolean createIndex() +String versionType() } class Field { <<annotation>> +FieldType type() +String analyzer() +String searchAnalyzer() +String normalizer() +DateFormat format() +String pattern() } class Id { <<annotation>> } %% 关系 Operations <|-- DocumentOperations Operations <|-- SearchOperations DocumentOperations <|-- ElasticsearchOperations SearchOperations <|-- ElasticsearchOperations ElasticsearchOperations <|.. ElasticsearchTemplate ElasticsearchOperations <|.. ElasticsearchRestTemplate Query <|.. CriteriaQuery Query <|.. StringQuery Query <|.. NativeQuery CriteriaQuery --> Criteria NativeQuery --> QueryBuilder SearchHits o-- SearchHit SearchPage --> SearchHits ElasticsearchRepository <|.. SimpleElasticsearchRepository SimpleElasticsearchRepository --> ElasticsearchOperations ElasticsearchConfiguration --> ClientConfiguration ElasticsearchConfiguration --> ElasticsearchOperations ElasticsearchTemplate --> ElasticsearchClient ElasticsearchRestTemplate --> RestHighLevelClient

查询执行完整流程

sequenceDiagram participant Client as 客户端代码 participant Repo as Repository participant Ops as ElasticsearchOperations participant Conv as ElasticsearchConverter participant ES as Elasticsearch participant Result as 结果处理 %% 查询执行流程 Client->>+Repo: findByNameContaining("手机") Note over Repo: 解析方法名<br/>生成查询 Repo->>+Ops: search(Query, Product.class) Note over Ops: 准备查询请求 Ops->>+Conv: 转换查询参数 Conv-->>-Ops: 返回ES查询格式 Ops->>+ES: POST /product/_search<br/>{"query": {"match": {"name": "手机"}}} ES-->>-Ops: 返回搜索结果<br/>{"hits": {...}, "aggregations": {...}} Ops->>+Conv: 转换响应结果 Conv->>Result: 创建SearchHit对象 Result->>Result: 封装文档内容 Result->>Result: 提取高亮信息 Result->>Result: 保存得分和排序值 Result-->>Conv: SearchHit<Product> Conv->>Result: 创建SearchHits对象 Result->>Result: 设置总命中数 Result->>Result: 设置最高得分 Result->>Result: 添加聚合结果 Result-->>Conv: SearchHits<Product> Conv-->>-Ops: 返回SearchHits Ops-->>-Repo: 返回查询结果 Repo-->>-Client: List<Product> %% 索引操作流程 rect rgb(240, 248, 255) Note over Client,ES: 索引操作流程 Client->>+Ops: indexOps(Product.class) Ops-->>-Client: IndexOperations Client->>+Ops: createWithMapping() Ops->>+Conv: 生成映射 Conv-->>-Ops: 映射JSON Ops->>+ES: PUT /product<br/>{"mappings": {...}, "settings": {...}} ES-->>-Ops: {"acknowledged": true} Ops-->>-Client: true end %% 文档保存流程 rect rgb(255, 248, 240) Note over Client,ES: 文档保存流程 Client->>+Ops: save(product) Ops->>+Conv: 转换实体为文档 Conv-->>-Ops: JSON文档 Ops->>+ES: PUT /product/_doc/1<br/>{"name": "iPhone", "price": 8999} ES-->>-Ops: {"_id": "1", "_version": 1} Ops->>+Conv: 更新实体版本信息 Conv-->>-Ops: 更新后的实体 Ops-->>-Client: Product with updated version end

不同查询类型执行路径-流程图

flowchart TB subgraph "查询入口" A[客户端调用] B1[Repository方法] B2[直接使用Operations] end subgraph "查询类型选择" C1[CriteriaQuery<br/>链式API构建] C2[StringQuery<br/>JSON字符串] C3[NativeQuery<br/>Java API构建] end subgraph "查询构建" D1[Criteria对象<br/>组装条件链] D2[JSON字符串<br/>直接传递] D3[QueryBuilder<br/>类型安全构建] end subgraph "转换层" E[ElasticsearchConverter<br/>查询转换] F[生成ES查询DSL] end subgraph "执行层" G[ElasticsearchClient<br/>发送HTTP请求] H[Elasticsearch<br/>执行查询] end subgraph "结果处理" I[响应解析] J[SearchHits构建] K[实体映射] L[返回结果] end %% 连接关系 A --> B1 A --> B2 B1 --> C1 B1 --> C2 B1 --> C3 B2 --> C1 B2 --> C2 B2 --> C3 C1 --> D1 C2 --> D2 C3 --> D3 D1 --> E D2 --> F D3 --> E E --> F F --> G G --> H H --> I I --> J J --> K K --> L %% 样式 style C1 fill:#e1f5fe,stroke:#01579b,stroke-width:2px style C2 fill:#fff3e0,stroke:#e65100,stroke-width:2px style C3 fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px style H fill:#ffebee,stroke:#b71c1c,stroke-width:3px

0

评论区