邪修版Spring Data Elasticsearch快速入门使用教程
·
官方文档链接
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();
}
}更多推荐

所有评论(0)