官方文档链接

1. 主要查询类概览

Spring Data Elasticsearch 提供了多种查询构建方式:

  • NativeSearchQueryBuilder - 原生查询构建器

  • CriteriaQuery - 条件查询

  • StringQuery - 字符串查询

  • ElasticsearchRestTemplate - 模板类操作

2. NativeSearchQueryBuilder (最常用)

基本结构

NativeSearchQuery query = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.xxx())        // 查询条件
    .withFilter(QueryBuilders.xxx())       // 过滤器
    .withSort(SortBuilders.xxx())          // 排序
    .withPageable(PageRequest.of(0, 10))   // 分页
    .withAggregation(AggregationBuilders.xxx()) // 聚合
    .withSourceFilter(SourceFilter.xxx())  // 字段过滤
    .build();

完整示例

@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;

public void nativeSearchExample() {
    // 构建查询
    NativeSearchQuery query = new NativeSearchQueryBuilder()
        // 查询条件:商品名称包含"手机"且价格在1000-5000之间
        .withQuery(QueryBuilders.boolQuery()
            .must(QueryBuilders.matchQuery("name", "手机"))
            .filter(QueryBuilders.rangeQuery("price").gte(1000).lte(5000))
        )
        // 过滤条件:状态为上架
        .withFilter(QueryBuilders.termQuery("status", "上架"))
        // 排序:按价格降序,再按创建时间降序
        .withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC))
        .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
        // 分页:第1页,每页20条
        .withPageable(PageRequest.of(0, 20))
        // 聚合:按品牌和分类聚合
        .addAggregation(AggregationBuilders.terms("brand_agg").field("brand.keyword"))
        .addAggregation(AggregationBuilders.terms("category_agg").field("category.keyword"))
        // 字段过滤:只返回需要的字段
        .withSourceFilter(new FetchSourceFilter(
            new String[]{"id", "name", "price", "brand"}, 
            null
        ))
        .build();
    
    // 执行查询
    SearchHits<Product> hits = elasticsearchRestTemplate.search(query, Product.class);
    
    // 处理结果
    List<Product> products = hits.getSearchHits().stream()
        .map(SearchHit::getContent)
        .collect(Collectors.toList());
    
    // 处理聚合
    Aggregations aggregations = hits.getAggregations();
    // ... 聚合解析逻辑
}

3. CriteriaQuery (面向对象方式)

基本使用

public void criteriaQueryExample() {
    // 创建条件
    Criteria criteria = new Criteria("name").contains("手机")
        .and("price").between(1000, 5000)
        .and("status").is("上架");
    
    // 构建查询
    CriteriaQuery query = new CriteriaQuery(criteria)
        .addSort(Sort.by("price").descending())
        .setPageable(PageRequest.of(0, 10));
    
    // 执行查询
    SearchHits<Product> hits = elasticsearchRestTemplate.search(query, Product.class);
}

复杂条件示例

public void complexCriteriaQuery() {
    Criteria criteria = new Criteria();
    
    // 组合条件
    Criteria nameCriteria = new Criteria("name").contains("手机").or("description").contains("智能");
    Criteria priceCriteria = new Criteria("price").greaterThan(1000).lessThan(5000);
    Criteria brandCriteria = new Criteria("brand").in("华为", "小米", "苹果");
    
    // 最终条件
    Criteria finalCriteria = criteria.and(nameCriteria).and(priceCriteria).and(brandCriteria);
    
    CriteriaQuery query = new CriteriaQuery(finalCriteria);
    SearchHits<Product> hits = elasticsearchRestTemplate.search(query, Product.class);
}

4. StringQuery (原生DSL查询)

直接使用JSON查询

带参数的字符串查询

public void stringQueryExample() {
    String jsonQuery = """
    {
      "query": {
        "bool": {
          "must": [
            {"match": {"name": "手机"}}
          ],
          "filter": [
            {"range": {"price": {"gte": 1000, "lte": 5000}}}
          ]
        }
      },
      "sort": [{"price": {"order": "desc"}}]
    }
    """;
    
    StringQuery query = new StringQuery(jsonQuery);
    SearchHits<Product> hits = elasticsearchRestTemplate.search(query, Product.class);
}
public void stringQueryWithParams() {
    String jsonQuery = """
    {
      "query": {
        "bool": {
          "must": [
            {"match": {"name": "?0"}}
          ],
          "filter": [
            {"range": {"price": {"gte": ?1, "lte": ?2}}}
          ]
        }
      }
    }
    """;
    
    StringQuery query = new StringQuery(jsonQuery, "手机", 1000, 5000);
    SearchHits<Product> hits = elasticsearchRestTemplate.search(query, Product.class);
}

5. 查询条件构建器 (QueryBuilders)

常用查询类型

// 匹配查询
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", "手机");

// 多字段匹配查询
MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery("手机", "name", "description");

// 词项查询
TermQueryBuilder termQuery = QueryBuilders.termQuery("status", "上架");

// 多词项查询
TermsQueryBuilder termsQuery = QueryBuilders.termsQuery("brand", "华为", "小米", "苹果");

// 范围查询
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price").gte(1000).lte(5000);

// 通配符查询
WildcardQueryBuilder wildcardQuery = QueryBuilders.wildcardQuery("name", "*手机*");

// 前缀查询
PrefixQueryBuilder prefixQuery = QueryBuilders.prefixQuery("brand", "华");

// 布尔查询(最常用)
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
    .must(QueryBuilders.matchQuery("name", "手机"))           // 必须满足
    .should(QueryBuilders.termQuery("isHot", true))          // 应该满足(加分)
    .filter(QueryBuilders.rangeQuery("price").lte(5000))     // 过滤条件
    .mustNot(QueryBuilders.termQuery("status", "下架"));     // 必须不满足

词项查询 vs 匹配查询的区别

本质区别
特性 词项查询 (Term Query) 匹配查询 (Match Query)
分词处理 ❌ 不分词 - 精确匹配 ✅ 会分词 - 全文搜索
查询类型 精确值查询 全文检索查询
使用场景 关键字、状态、ID、标签等 文本内容、描述、标题等
性能 较高(倒排索引直接查找) 相对较低(需要分词和评分计算)
大小写敏感 通常敏感(取决于分析器) 通常不敏感(取决于分析器)
实际代码对比
// 假设文档内容:{ "tags": "java,spring,mysql", "content": "Java编程和Spring框架" }

// 🔹 词项查询 - 精确匹配
TermQueryBuilder termQuery = QueryBuilders.termQuery("tags", "java");
// ✅ 匹配:因为 tags 字段包含精确的 "java"
// ❌ 不匹配:"java编程"、"javascript"

TermsQueryBuilder termsQuery = QueryBuilders.termsQuery("tags", "java", "spring");
// ✅ 匹配:包含 "java" 或 "spring"

// 🔹 匹配查询 - 全文搜索
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("content", "Java Spring");
// 分词为 ["java", "spring"],然后搜索
// ✅ 匹配:"Java编程和Spring框架"、"Spring和Java"
// ✅ 匹配:"javaweb和springboot"(部分匹配)

MatchQueryBuilder matchQuery2 = QueryBuilders.matchQuery("content", "Java编程")
// 分词为 ["java", "编程"],然后搜索
// ✅ 匹配:"Java编程"、"编程Java"、"Java高级编程"
字段类型影响
// 对于 text 类型字段(默认会分词)
// 🔹 mapping: "title": { "type": "text" }
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", "Hello World");
// 搜索 "hello" 或 "world"

TermQueryBuilder termQuery = QueryBuilders.termQuery("title", "Hello World");  
// ❌ 基本不会匹配,因为 "Hello World" 被分词了

// 对于 keyword 类型字段(不分词)
// 🔹 mapping: "category": { "type": "keyword" } 
MatchQueryBuilder matchQuery2 = QueryBuilders.matchQuery("category", "Electronics");
// ✅ 匹配:"Electronics"

TermQueryBuilder termQuery2 = QueryBuilders.termQuery("category", "Electronics");
// ✅ 匹配:"Electronics"
实际业务场景选择
// ✅ 使用词项查询的场景:
public SearchHits<Product> searchByStatusAndCategory() {
    BoolQueryBuilder query = QueryBuilders.boolQuery()
        // 状态精确匹配
        .filter(QueryBuilders.termQuery("status", "ACTIVE"))
        // 分类精确匹配  
        .filter(QueryBuilders.termsQuery("category", "ELECTRONICS", "BOOKS"))
        // ID精确匹配
        .filter(QueryBuilders.termsQuery("id", "123", "456", "789"));
    
    return elasticsearchRestTemplate.search(
        new NativeSearchQueryBuilder().withQuery(query).build(), Product.class);
}

// ✅ 使用匹配查询的场景:
public SearchHits<Article> searchArticles(String keywords) {
    BoolQueryBuilder query = QueryBuilders.boolQuery()
        // 标题全文搜索
        .must(QueryBuilders.matchQuery("title", keywords))
        // 内容全文搜索
        .should(QueryBuilders.matchQuery("content", keywords));
    
    return elasticsearchRestTemplate.search(
        new NativeSearchQueryBuilder().withQuery(query).build(), Article.class);
}

6. 排序构建器 (SortBuilders)

NativeSearchQuery query = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.matchAllQuery())
    // 字段排序
    .withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC))
    .withSort(SortBuilders.fieldSort("sales").order(SortOrder.DESC))
    // 地理距离排序
    .withSort(SortBuilders.geoDistanceSort("location")
        .point(31.2304, 121.4737)  // 上海坐标
        .order(SortOrder.ASC)
        .unit(DistanceUnit.KILOMETERS))
    // 脚本排序
    .withSort(SortBuilders.scriptSort(
        new Script("doc['price'].value * 0.8 + doc['sales'].value * 0.2"), 
        ScriptSortType.NUMBER
    ).order(SortOrder.DESC))
    .build();

7. 分页查询

基本分页

public SearchPage<Product> searchWithPagination(String keyword, int page, int size) {
    NativeSearchQuery query = new NativeSearchQueryBuilder()
        .withQuery(QueryBuilders.matchQuery("name", keyword))
        .withPageable(PageRequest.of(page, size))
        .build();
    
    SearchHits<Product> hits = elasticsearchRestTemplate.search(query, Product.class);
    
    // 转换为Spring Data Page对象
    return SearchHitSupport.searchPageFor(hits, query.getPageable());
}

深度分页 - search_after

public List<Product> searchAfter(String keyword, Object[] searchAfter, int size) {
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
        .withQuery(QueryBuilders.matchQuery("name", keyword))
        .withPageable(Pageable.ofSize(size));
    
    if (searchAfter != null) {
        queryBuilder.withSearchAfter(searchAfter);
    }
    
    SearchHits<Product> hits = elasticsearchRestTemplate.search(queryBuilder.build(), Product.class);
    List<SearchHit<Product>> searchHits = hits.getSearchHits();
    
    if (!searchHits.isEmpty()) {
        // 获取最后一个结果的search_after值,用于下一次查询
        Object[] lastSearchAfter = searchHits.get(searchHits.size() - 1).getSortValues();
        // 存储 lastSearchAfter 用于下一次查询
    }
    
    return searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());
}

8. 聚合查询详解

指标聚合

NativeSearchQuery query = new NativeSearchQueryBuilder()
    .addAggregation(AggregationBuilders.avg("avg_price").field("price"))
    .addAggregation(AggregationBuilders.max("max_price").field("price"))
    .addAggregation(AggregationBuilders.min("min_price").field("price"))
    .addAggregation(AggregationBuilders.sum("total_sales").field("sales"))
    .addAggregation(AggregationBuilders.count("product_count").field("id"))
    .addAggregation(AggregationBuilders.stats("price_stats").field("price"))
    .build();

桶聚合

NativeSearchQuery query = new NativeSearchQueryBuilder()
    // 词项聚合
    .addAggregation(AggregationBuilders.terms("brand_agg").field("brand.keyword"))
    // 范围聚合
    .addAggregation(AggregationBuilders.range("price_range")
        .field("price")
        .addRange(0, 1000)
        .addRange(1000, 5000)
        .addRange(5000, 10000))
    // 日期直方图聚合
    .addAggregation(AggregationBuilders.dateHistogram("sales_trend")
        .field("createTime")
        .calendarInterval(DateHistogramInterval.MONTH))
    .build();

多级聚合

NativeSearchQuery query = new NativeSearchQueryBuilder()
    .addAggregation(
        AggregationBuilders.terms("brand_agg").field("brand.keyword")
            .subAggregation(AggregationBuilders.avg("avg_price").field("price"))
            .subAggregation(AggregationBuilders.terms("category_agg").field("category.keyword"))
    )
    .build();

指标聚合 vs 桶聚合的理解

核心概念比喻

桶聚合 = GROUP BY(分组)

  • 把数据分成不同的组/桶

  • 回答:"数据有哪些分类?每个分类有多少?"

指标聚合 = SELECT 聚合函数(计算)

  • 对数据进行数学计算

  • 回答:"数据的平均值是多少?总和是多少?"

详细对比
维度 桶聚合 (Bucket Aggregation) 指标聚合 (Metric Aggregation)
目的 分组、分类、分段 计算、统计、度量
输出 一组桶(每个桶包含文档) 数值计算结果
类比SQL GROUP BY 子句 AVG()SUM()COUNT() 等
常见类型 terms, range, date_histogram avg, sum, max, min, stats
实际代码示例
/ 🔹 纯桶聚合:只要分组信息
NativeSearchQuery bucketOnlyQuery = new NativeSearchQueryBuilder()
    .addAggregation(AggregationBuilders.terms("by_category").field("category.keyword"))
    .addAggregation(AggregationBuilders.range("by_price")
        .field("price")
        .addRange(0, 100)
        .addRange(100, 500)
        .addRange(500, 1000))
    .build();

// 🔹 纯指标聚合:只要统计信息  
NativeSearchQuery metricOnlyQuery = new NativeSearchQueryBuilder()
    .addAggregation(AggregationBuilders.avg("avg_price").field("price"))
    .addAggregation(AggregationBuilders.max("max_price").field("price"))
    .addAggregation(AggregationBuilders.sum("total_sales").field("sales"))
    .build();

// 🔹 组合使用:分组后统计(最常用)
NativeSearchQuery combinedQuery = new NativeSearchQueryBuilder()
    .addAggregation(
        AggregationBuilders.terms("by_category").field("category.keyword")
            // 在每个分类桶内进行指标计算
            .subAggregation(AggregationBuilders.avg("avg_price").field("price"))
            .subAggregation(AggregationBuilders.sum("total_sales").field("sales"))
            .subAggregation(AggregationBuilders.stats("price_stats").field("price"))
    )
    .build();
结果解析对比
SearchHits<Product> hits = elasticsearchRestTemplate.search(combinedQuery, Product.class);
Aggregations aggregations = hits.getAggregations();

// 🔹 解析桶聚合
ParsedStringTerms categoryAgg = aggregations.get("by_category");
for (Terms.Bucket bucket : categoryAgg.getBuckets()) {
    String category = bucket.getKeyAsString();    // 桶的键:分类名称
    long docCount = bucket.getDocCount();         // 桶的大小:文档数量
    
    // 🔹 解析桶内的指标聚合
    ParsedAvg avgPrice = bucket.getAggregations().get("avg_price");
    ParsedSum totalSales = bucket.getAggregations().get("total_sales"); 
    ParsedStats priceStats = bucket.getAggregations().get("price_stats");
    
    System.out.println(String.format(
        "分类: %s, 商品数: %d, 平均价格: %.2f, 总销量: %d, 价格统计: %s",
        category, docCount, avgPrice.getValue(), totalSales.getValue(),
        String.format("min=%.2f, max=%.2f, avg=%.2f", 
            priceStats.getMin(), priceStats.getMax(), priceStats.getAvg())
    ));
}

9. 字段过滤

源过滤

NativeSearchQuery query = new NativeSearchQueryBuilder()
    .withSourceFilter(new FetchSourceFilterBuilder()
        .withIncludes("id", "name", "price", "brand")  // 包含字段
        .withExcludes("description", "specs")          // 排除字段
        .build())
    .build();

.withSourceFilter(new FetchSourceFilterBuilder().build())使用空的 FetchSourceFilterBuilder,加这个过滤条件和不加有什么区别

核心区别对比
方面 不加 withSourceFilter 使用空的 FetchSourceFilterBuilder().build()
_source 返回 返回完整的 _source 所有字段 ❌ 不返回任何 _source 字段
实体类映射 ✅ 实体类字段正常填充 ❌ 实体类所有字段为 null
性能影响 传输数据量较大 传输数据量最小
使用场景 需要完整业务数据时 只需要元数据或聚合结果时
实际代码演示
测试数据准备
java

@Entity
public class Product {
    private String id;
    private String name;
    private Double price;
    private String description;
    private String brand;
    // getters and setters
}

// 保存测试数据
Product product = new Product();
product.setId("1");
product.setName("iPhone 15");
product.setPrice(6999.0);
product.setDescription("最新款苹果手机");
product.setBrand("Apple");
elasticsearchRestTemplate.save(product);
测试对比
java

// 🔹 测试1:不加 withSourceFilter
NativeSearchQuery query1 = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.termQuery("id", "1"))
    .build();

SearchHits<Product> hits1 = elasticsearchRestTemplate.search(query1, Product.class);
SearchHit<Product> hit1 = hits1.getSearchHits().get(0);
Product product1 = hit1.getContent();

System.out.println("=== 不加 withSourceFilter ===");
System.out.println("ID: " + product1.getId());        // ✅ "1"
System.out.println("Name: " + product1.getName());    // ✅ "iPhone 15" 
System.out.println("Price: " + product1.getPrice());  // ✅ 6999.0
System.out.println("Source: " + hit1.getSourceAsString()); // ✅ 完整JSON

// 🔹 测试2:使用空的 FetchSourceFilterBuilder
NativeSearchQuery query2 = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.termQuery("id", "1"))
    .withSourceFilter(new FetchSourceFilterBuilder().build())
    .build();

SearchHits<Product> hits2 = elasticsearchRestTemplate.search(query2, Product.class);
SearchHit<Product> hit2 = hits2.getSearchHits().get(0);
Product product2 = hit2.getContent();

System.out.println("=== 使用空的 FetchSourceFilterBuilder ===");
System.out.println("ID: " + product2.getId());        // ❌ null
System.out.println("Name: " + product2.getName());    // ❌ null
System.out.println("Price: " + product2.getPrice());  // ❌ null
System.out.println("Source: " + hit2.getSourceAsString()); // ❌ null

高亮显示

NativeSearchQuery query = new NativeSearchQueryBuilder()
    .withQuery(QueryBuilders.matchQuery("name", "手机"))
    .withHighlightFields(
        new HighlightBuilder.Field("name").preTags("<em>").postTags("</em>"),
        new HighlightBuilder.Field("description").preTags("<em>").postTags("</em>")
    )
    .build();

// 处理高亮结果
SearchHits<Product> hits = elasticsearchRestTemplate.search(query, Product.class);
for (SearchHit<Product> hit : hits) {
    List<String> nameHighlights = hit.getHighlightField("name");
    if (!nameHighlights.isEmpty()) {
        String highlightedName = nameHighlights.get(0);
        // 使用高亮内容
    }
}

10. 完整业务示例

商品搜索服务

@Service
public class ProductSearchService {
    
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    
    public ProductSearchResult searchProducts(ProductSearchRequest request) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        
        // 关键词搜索
        if (StringUtils.hasText(request.getKeyword())) {
            boolQuery.must(QueryBuilders.multiMatchQuery(request.getKeyword(), 
                "name", "description", "brand"));
        }
        
        // 分类过滤
        if (request.getCategoryIds() != null && !request.getCategoryIds().isEmpty()) {
            boolQuery.filter(QueryBuilders.termsQuery("categoryId", request.getCategoryIds()));
        }
        
        // 价格范围
        if (request.getMinPrice() != null || request.getMaxPrice() != null) {
            RangeQueryBuilder priceRange = QueryBuilders.rangeQuery("price");
            if (request.getMinPrice() != null) priceRange.gte(request.getMinPrice());
            if (request.getMaxPrice() != null) priceRange.lte(request.getMaxPrice());
            boolQuery.filter(priceRange);
        }
        
        // 品牌过滤
        if (request.getBrands() != null && !request.getBrands().isEmpty()) {
            boolQuery.filter(QueryBuilders.termsQuery("brand.keyword", request.getBrands()));
        }
        
        NativeSearchQuery query = new NativeSearchQueryBuilder()
            .withQuery(boolQuery)
            .withPageable(PageRequest.of(request.getPage(), request.getSize()))
            .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
            .withSort(SortBuilders.fieldSort("sales").order(SortOrder.DESC))
            .addAggregation(AggregationBuilders.terms("brand_agg").field("brand.keyword"))
            .addAggregation(AggregationBuilders.terms("category_agg").field("categoryName.keyword"))
            .withSourceFilter(new FetchSourceFilter(new String[]{
                "id", "name", "price", "brand", "image", "sales"
            }, null))
            .build();
        
        SearchHits<Product> hits = elasticsearchRestTemplate.search(query, Product.class);
        
        return ProductSearchResult.builder()
            .products(hits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList()))
            .total(hits.getTotalHits())
            .aggregations(hits.getAggregations())
            .build();
    }
}
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐