本文基于 SpringBoot 3 + ES High Level Rest Client,详细介绍精确匹配、模糊检索、布尔多条件、范围、排序分页、聚合、高亮、批量操作的使用。
前置实体
1)商品文档实体 GoodsDoc
java
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Data
@Document(indexName = "goods")
public class GoodsDoc {
@Id
private String id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Integer)
private Integer stock;
}
2)注入模板对象
java
import org.springframework.data.elasticsearch.client.elc.ElasticsearchRestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EsSearchService {
@Autowired
private ElasticsearchRestTemplate restTemplate;
}
term 精确查询(keyword)
DSL
json
GET /goods/_search
{
"query": {
"term": {
"category": {
"value": "手机"
}
}
}
}
Java 代码
java
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import java.util.List;
public List<GoodsDoc> searchByCategory(String categoryVal) {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.termQuery("category", categoryVal));
SearchHits<GoodsDoc> hits = restTemplate.search(
builder.build(), GoodsDoc.class
);
return hits.stream().map(h -> h.getContent()).toList();
}
match 全文分词检索(text)
DSL
json
GET /goods/_search
{
"query": {
"match": {
"title": "华为手机"
}
}
}
Java
java
public List<GoodsDoc> searchByTitle(String keyword) {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.matchQuery("title", keyword));
SearchHits<GoodsDoc> hits = restTemplate.search(builder.build(), GoodsDoc.class);
return hits.stream().map(Hit::getContent).toList();
}
bool 组合多条件(must + filter)
需求:标题含手机,价格 2000~6000,库存>0
json
GET /goods/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "手机"}}
],
"filter": [
{"range": {"price": {"gte":2000,"lte":6000}}},
{"range": {"stock": {"gt":0}}}
]
}
}
}
Java
java
public List<GoodsDoc> searchBoolCombination(String titleKey, double minPrice, double maxPrice) {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
BoolQueryBuilder bool = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("title", titleKey))
.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice))
.filter(QueryBuilders.rangeQuery("stock").gt(0));
builder.withQuery(bool);
SearchHits<GoodsDoc> hits = restTemplate.search(builder.build(), GoodsDoc.class);
return hits.stream().map(Hit::getContent).toList();
}
分页 + 排序
json
GET /goods/_search
{
"from": 0,
"size": 10,
"sort": [{"price": "desc"}]
}
Java
java
public List<GoodsDoc> searchPageSort(int pageNum, int pageSize) {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
// 分页
builder.withPageable(PageRequest.of(pageNum - 1, pageSize));
// 价格降序
builder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));
SearchHits<GoodsDoc> hits = restTemplate.search(builder.build(), GoodsDoc.class);
return hits.stream().map(Hit::getContent).toList();
}
聚合统计(按分类分组,求均价、商品数量)
json
GET /goods/_search
{
"size": 0,
"aggs": {
"group_category": {
"terms": {"field": "category"},
"aggs": {
"avg_price": {"avg": {"field": "price"}}
}
}
}
}
Java 完整聚合代码
java
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.aggregation.Aggregate;
import co.elastic.clients.elasticsearch.core.aggregation.bucket.TermsAggregate;
import co.elastic.clients.elasticsearch.core.aggregation.metrics.AvgAggregate;
public void aggCategoryStat() {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withSize(0); // 不需要原始文档
TermsAggregationBuilder termsAgg = AggregationBuilders
.terms("group_category").field("category")
.subAggregation(AggregationBuilders.avg("avg_price").field("price"));
builder.withAggregations(termsAgg);
SearchResponse<GoodsDoc> resp = restTemplate.search(builder.build(), GoodsDoc.class);
Aggregate agg = resp.aggregations().get("group_category");
TermsAggregate<?> terms = agg.terms();
for (var bucket : terms.buckets().array()) {
String category = bucket.keyAsString();
long count = bucket.docCount();
AvgAggregate avgPriceAgg = bucket.aggregations().get("avg_price").avg();
double avgPrice = avgPriceAgg.value();
System.out.printf("分类:%s,商品数:%d,均价:%.2f%n", category, count, avgPrice);
}
}
关键词高亮
java
public List<GoodsDoc> searchHighlight(String keyword) {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.matchQuery("title", keyword));
HighlightBuilder highlight = new HighlightBuilder();
highlight.field("title")
.preTags("<span style='color:red'>")
.postTags("</span>");
builder.withHighlightBuilder(highlight);
SearchHits<GoodsDoc> hits = restTemplate.search(builder.build(), GoodsDoc.class);
List<GoodsDoc> result = new ArrayList<>();
for (SearchHit<GoodsDoc> hit : hits) {
GoodsDoc doc = hit.getContent();
// 取出高亮片段
List<String> highlightTitles = hit.getHighlightFields().get("title");
if (highlightTitles != null && !highlightTitles.isEmpty()) {
doc.setTitle(highlightTitles.get(0));
}
result.add(doc);
}
return result;
}
批量新增 / 批量更新
java
public void bulkInsert(List<GoodsDoc> docList) {
NativeBulkQueryBuilder bulkBuilder = new NativeBulkQueryBuilder();
for (GoodsDoc doc : docList) {
bulkBuilder.withOperation(BulkOperationBuilder.index(
IndexQueryBuilder.withId(doc.getId()).withObject(doc)
));
}
restTemplate.bulk(bulkBuilder.build(), GoodsDoc.class);
}
关键说明
NativeSearchQueryBuilder用来链式拼装 DSL,不用手写 JSON 字符串;termQuery只适用于keyword,matchQuery对应text分词检索;filter不会计算打分,性能优于must,过滤条件尽量放 filter;- 聚合查询设置
size(0),不返回原始文档,减少网络传输; - 深分页不要无限
from/size,改用searchAfter游标分页;
调用测试示例
java
// 精确查询
esSearchService.searchByCategory("手机");
// 多条件组合
esSearchService.searchBoolCombination("华为", 2000, 6000);
// 分页
esSearchService.searchPageSort(1, 10);
// 聚合统计
esSearchService.aggCategoryStat();
// 高亮
esSearchService.searchHighlight("华为");