【ElasticSearch】json查询语法
- 【一】Elasticsearch查询语法详解
-
- 【1】基本查询结构
- 【2】关键字
-
- (1)一般查询(Query)
- [(2)指标聚合(Metric Aggregations)](#(2)指标聚合(Metric Aggregations))
- [(3)桶聚合(Bucket Aggregations)](#(3)桶聚合(Bucket Aggregations))
- [(4)管道聚合(Pipeline Aggregations)](#(4)管道聚合(Pipeline Aggregations))
- 【3】常用查询类型
- 【4】聚合语法
-
- (1)基本词项聚合-Terms(按商品分类分组)
- (2)指标聚合-Metrics(计算平均价格)
- (3)多级聚合-Metrics(按分类分组后计算平均价格)
- (4)范围聚合-Range(按价格区间分组)
- (5)复杂多级聚合(按产地分组后按分类分组)
- (6)排序聚合-Order(按平均价格升序排列)
- (7)嵌套聚合-Nested
- (8)统计聚合-Stats
- [(9)扩展统计聚合-Extended Stats](#(9)扩展统计聚合-Extended Stats)
- 【二】完整查询与聚合组合案例
-
- 【1】聚合案例
-
- (1)案例1:带过滤条件的聚合分析
- (2)案例2:高亮搜索与聚合结合
- (3)案例3:多条件过滤聚合(中国产电子产品价格分析)
- (4)案例4:日期范围聚合(按创建月份分组)
- [(5)案例5:指标聚合(Metric Aggregations)](#(5)案例5:指标聚合(Metric Aggregations))
- [(6)案例6:桶聚合(Bucket Aggregations)](#(6)案例6:桶聚合(Bucket Aggregations))
- [(7)案例7:范围聚合(Range Aggregation)](#(7)案例7:范围聚合(Range Aggregation))
- [(8)案例8:嵌套聚合(Nested Aggregation)](#(8)案例8:嵌套聚合(Nested Aggregation))
- [(9)案例9:管道聚合(Pipeline Aggregation)](#(9)案例9:管道聚合(Pipeline Aggregation))
- (10)案例10:组合查询和聚合
- 【2】实践技巧
- 【3】常见问题
- 【三】springboot整合es的工具
-
- 【1】可选工具和客户端
-
- (1)ElasticsearchRepository
- (2)ElasticsearchRestTemplate(或旧版的ElasticsearchTemplate):
- [(3)High Level REST Client](#(3)High Level REST Client)
- [(4)Java API Client](#(4)Java API Client)
- 【2】如何选择
- 【3】如何使用
-
- [(1)ElasticsearchRepository 使用](#(1)ElasticsearchRepository 使用)
- [(2)ElasticsearchRestTemplate 使用](#(2)ElasticsearchRestTemplate 使用)
- [(3)新的Java API Client使用(Elasticsearch 7.17+)](#(3)新的Java API Client使用(Elasticsearch 7.17+))
【一】Elasticsearch查询语法详解
【1】基本查询结构
java
GET /index_name/_search
{
"query": {
// 查询条件
},
"aggs": {
// 聚合条件
},
"sort": [
// 排序条件
],
"from": 0,
"size": 10,
"highlight": {
// 高亮设置
}
}
【2】关键字
(1)一般查询(Query)
匹配查询(Match Query):用于全文搜索。
词条查询(Term Query):用于精确值匹配。
范围查询(Range Query):用于范围过滤。
布尔查询(Bool Query):组合多个查询条件(must, should, must_not, filter)。
多匹配查询(Multi Match Query):在多个字段上执行匹配查询。
前缀查询(Prefix Query):匹配以指定前缀开头的词条。
通配符查询(Wildcard Query):使用通配符匹配。
正则表达式查询(Regexp Query):使用正则表达式匹配。
模糊查询(Fuzzy Query):匹配与指定词条相似的词条。
嵌套查询(Nested Query):查询嵌套对象。
(2)指标聚合(Metric Aggregations)
avg:平均值。
sum:求和。
min:最小值。
max:最大值。
count:计数。
stats:包含count, min, max, avg, sum。
extended_stats:扩展的统计信息,包括方差、标准差等。
cardinality:基数统计(类似distinct count)
(3)桶聚合(Bucket Aggregations)
terms:按词条分组。
range:按范围分组。
date_range:日期范围分组。
histogram:直方图(数值间隔分组)。
date_histogram:日期直方图。
nested:嵌套聚合。
reverse_nested:从嵌套聚合返回父级。
filter:按过滤条件分组。
filters:多个过滤条件分组。
missing:处理缺失字段。
(4)管道聚合(Pipeline Aggregations)
对其它聚合的结果进行再聚合。
avg_bucket:计算多个桶的平均值。
sum_bucket:计算多个桶的总和。
min_bucket:计算多个桶的最小值。
max_bucket:计算多个桶的最大值。
【3】常用查询类型
(1)匹配查询-match
基本匹配查询(查找所有华为手机)
java
GET /product_info/_search
{
"query": {
"match": {
"product_name": "华为手机"
}
}
}
(2)多字段搜索-multi_match(在名称和描述中搜索)
java
GET /product_info/_search
{
"query": {
"multi_match": {
"query": "防水 智能",
"fields": ["product_name", "description"],
"type": "best_fields"
}
}
}
(3)精确过滤-must(查找中国产电子产品)
java
GET /product_info/_search
{
"query": {
"bool": {
"must": [
{"match": {"category": "电子产品"}}
],
"filter": [
{"term": {"origin": "中国"}}
]
}
}
}
(4)范围查询-range(价格在1000-5000之间的商品)
java
GET /product_info/_search
{
"query": {
"range": {
"price": {
"gte": 1000,
"lte": 5000
}
}
}
}
(5)组合查询(查找库存大于100的服装)
java
GET /product_info/_search
{
"query": {
"bool": {
"must": [
{"match": {"category": "服装"}}
],
"filter": [
{"range": {"stock": {"gt": 100}}}
]
}
}
}
(6)分页排序(按价格降序排列)
java
GET /product_info/_search
{
"query": {"match_all": {}},
"sort": [
{"price": {"order": "desc"}}
],
"from": 0,
"size": 10
}
(7)高亮显示(高亮搜索结果)
java
GET /product_info/_search
{
"query": {
"match": {
"description": "防水"
}
},
"highlight": {
"fields": {
"description": {}
},
"pre_tags": ["<strong>"],
"post_tags": ["</strong>"]
}
}
(8)布尔查询(Bool)
java
{
"query": {
"bool": {
"must": [
{ "match": { "field1": "value1" } }
],
"should": [
{ "match": { "field2": "value2" } }
],
"must_not": [
{ "term": { "field3": "value3" } }
],
"filter": [
{ "range": { "price": { "gte": 100 } } }
]
}
}
}
java
GET /product_info/_search
{
"query": {
"bool": {
"must": [
{ "match": { "product_name": "手机" } }
],
"filter": [
{ "term": { "status": "active" } },
{ "range": { "price": { "gte": 1000, "lte": 5000 } } }
]
}
}
}
(9)地理位置搜索
java
GET /stores/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "咖啡" } }
],
"filter": {
"geo_distance": {
"distance": "2km",
"location": {
"lat": 31.2304,
"lon": 121.4737
}
}
}
}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 31.2304,
"lon": 121.4737
},
"order": "asc",
"unit": "km"
}
}
]
}
【4】聚合语法
(1)基本词项聚合-Terms(按商品分类分组)
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 5
}
}
}
}
java
GET /product_info/_search
{
"query": {
"bool": {
"must": [
{ "match": { "product_name": "智能手机" } }
],
"filter": [
{ "term": { "status": "active" } },
{ "range": { "price": { "gte": 1000, "lte": 5000 } } }
]
}
},
"sort": [
{ "price": { "order": "asc" } }
],
"from": 0,
"size": 10,
"highlight": {
"fields": {
"product_name": {},
"description": {}
}
}
}
(2)指标聚合-Metrics(计算平均价格)
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
java
GET /product_info/_search
{
"query": {
"multi_match": {
"query": "防水 智能",
"fields": ["product_name", "description"],
"type": "best_fields"
}
},
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 5
},
"aggs": {
"avg_price": {
"avg": { "field": "price" }
}
}
},
"price_stats": {
"stats": { "field": "price" }
}
},
"size": 0
}
(3)多级聚合-Metrics(按分类分组后计算平均价格)
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 5
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
java
{
"aggs": {
"avg_price": {
"avg": { "field": "price" }
},
"max_price": {
"max": { "field": "price" }
},
"min_price": {
"min": { "field": "price" }
},
"sum_price": {
"sum": { "field": "price" }
}
}
}
(4)范围聚合-Range(按价格区间分组)
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{"to": 1000},
{"from": 1000, "to": 5000},
{"from": 5000}
]
}
}
}
}
java
GET /product_info/_search
{
"query": {
"range": {
"stock": {
"gt": 0
}
}
},
"aggs": {
"by_category_origin": {
"terms": {
"field": "category",
"size": 10
},
"aggs": {
"by_origin": {
"terms": {
"field": "origin",
"size": 5,
"order": { "avg_price": "asc" }
},
"aggs": {
"avg_price": {
"avg": { "field": "price" }
},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 1000 },
{ "from": 1000, "to": 3000 },
{ "from": 3000 }
]
}
}
}
}
}
},
"global_price_stats": {
"extended_stats": { "field": "price" }
}
},
"size": 0
}
(5)复杂多级聚合(按产地分组后按分类分组)
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_origin": {
"terms": {
"field": "origin",
"size": 5
},
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 3
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
}
}
(6)排序聚合-Order(按平均价格升序排列)
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_origin": {
"terms": {
"field": "origin",
"size": 5,
"order": {
"avg_price": "asc"
}
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
(7)嵌套聚合-Nested
java
{
"aggs": {
"nested_agg": {
"nested": {
"path": "specifications"
},
"aggs": {
"by_spec": {
"terms": {
"field": "specifications.name"
}
}
}
}
}
}
(8)统计聚合-Stats
java
{
"aggs": {
"price_stats": {
"stats": { "field": "price" }
}
}
}
(9)扩展统计聚合-Extended Stats
java
{
"aggs": {
"price_extended_stats": {
"extended_stats": { "field": "price" }
}
}
}
【二】完整查询与聚合组合案例
【1】聚合案例
(1)案例1:带过滤条件的聚合分析
java
GET /product_info/_search
{
"query": {
"bool": {
"filter": [
{"term": {"status": "active"}},
{"range": {"stock": {"gt": 0}}}
]
}
},
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 5
},
"aggs": {
"by_origin": {
"terms": {
"field": "origin",
"size": 3,
"order": {
"avg_price": "asc"
}
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
},
"min_price": {
"min": {
"field": "price"
}
},
"max_price": {
"max": {
"field": "price"
}
}
}
}
}
},
"global_stats": {
"stats": {
"field": "price"
}
}
}
}
(2)案例2:高亮搜索与聚合结合
java
GET /product_info/_search
{
"query": {
"match": {
"description": "智能"
}
},
"highlight": {
"fields": {
"description": {}
}
},
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
},
"size": 5
}
(3)案例3:多条件过滤聚合(中国产电子产品价格分析)
java
GET /product_info/_search
{
"query": {
"bool": {
"must": [
{"match": {"category": "电子产品"}}
],
"filter": [
{"term": {"origin": "中国"}},
{"range": {"price": {"gte": 1000}}}
]
}
},
"size": 0,
"aggs": {
"by_brand": {
"terms": {
"field": "product_name.keyword",
"size": 5
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
},
"price_distribution": {
"histogram": {
"field": "price",
"interval": 1000
}
}
}
}
}
}
(4)案例4:日期范围聚合(按创建月份分组)
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_month": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month",
"format": "yyyy-MM"
},
"aggs": {
"total_sales": {
"sum": {
"field": "sales"
}
}
}
}
}
}
(5)案例5:指标聚合(Metric Aggregations)
计算商品的平均价格、最高价格、最低价格:
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"max_price": { "max": { "field": "price" } },
"min_price": { "min": { "field": "price" } }
}
}
(6)案例6:桶聚合(Bucket Aggregations)
按商品类别分组,并计算每组的平均价格:
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category" },
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
}
}
(7)案例7:范围聚合(Range Aggregation)
按价格范围分组:
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 1000 },
{ "from": 1000, "to": 5000 },
{ "from": 5000 }
]
},
"aggs": {
"avg_rating": { "avg": { "field": "rating" } }
}
}
}
}
(8)案例8:嵌套聚合(Nested Aggregation)
对嵌套的规格属性进行聚合,统计内存大小的分布:
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"specs": {
"nested": { "path": "specifications" },
"aggs": {
"memory_sizes": {
"filter": { "term": { "specifications.name": "内存" } },
"aggs": {
"sizes": { "terms": { "field": "specifications.value" } }
}
}
}
}
}
}
(9)案例9:管道聚合(Pipeline Aggregation)
按类别分组后,再计算每个类别的平均价格,然后按平均价格排序:
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category",
"order": { "avg_price": "desc" } // 按子聚合avg_price降序排序
},
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
}
}
(10)案例10:组合查询和聚合
查询价格在1000到5000之间的活跃商品,按类别分组,并计算每组的平均价格和商品数量,并按平均价格降序排序:
java
GET /product_info/_search
{
"size": 0,
"query": {
"bool": {
"filter": [
{ "term": { "status": "active" } },
{ "range": { "price": { "gte": 1000, "lte": 5000 } } }
]
}
},
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 10,
"order": { "avg_price": "desc" }
},
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"product_count": { "value_count": { "field": "id" } }
}
}
}
}
【2】实践技巧
(1)性能优化
java
GET /product_info/_search
{
"query": {
"bool": {
"filter": [
{"term": {"category": "电子产品"}}
]
}
},
"size": 0,
"aggs": {
"by_origin": {
"terms": {
"field": "origin",
"execution_hint": "map", -- 使用map执行模式
"size": 5
}
}
}
}
(2)复合聚合(处理大数据集)
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_category_origin": {
"composite": {
"sources": [
{"category": {"terms": {"field": "category"}}},
{"origin": {"terms": {"field": "origin"}}}
],
"size": 100
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
(3)使用搜索模板
java
POST /_scripts/product_search_template
{
"script": {
"lang": "mustache",
"source": {
"query": {
"bool": {
"must": [
{"match": {"category": "{{category}}"}}
],
"filter": [
{"term": {"origin": "{{origin}}"}}
]
}
},
"aggs": {
"price_stats": {
"stats": {
"field": "price"
}
}
},
"size": "{{size}}"
}
}
}
-- 调用模板
GET /product_info/_search/template
{
"id": "product_search_template",
"params": {
"category": "电子产品",
"origin": "中国",
"size": 10
}
}
(4)使用索引别名
java
-- 创建别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "product_info",
"alias": "current_products",
"filter": {
"term": {"status": "active"}
}
}
}
]
}
-- 使用别名查询
GET /current_products/_search
{
"query": {
"match_all": {}
}
}
【3】常见问题
(1)查询性能慢
优化方案:添加路由和过滤条件
java
GET /product_info/_search?routing=category
{
"query": {
"bool": {
"filter": [
{"term": {"category": "电子产品"}}
]
}
}
}
(2)聚合桶数量不足
解决方案:增加size参数
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 20 -- 默认是10
}
}
}
}
(3)处理空值聚合
解决方案:使用missing参数
java
GET /product_info/_search
{
"size": 0,
"aggs": {
"by_origin": {
"terms": {
"field": "origin",
"missing": "未知产地"
}
}
}
}
【三】springboot整合es的工具
【1】可选工具和客户端
(1)ElasticsearchRepository
这是Spring Data Elasticsearch提供的一个接口,类似于Spring Data JPA的Repository。它提供了基本的CRUD操作和简单的查询方法(通过方法名或注解定义查询)。适用于简单的CRUD操作和查询,能够快速开发。
(2)ElasticsearchRestTemplate(或旧版的ElasticsearchTemplate):
(1)ElasticsearchTemplate是Spring Data Elasticsearch早期版本中的主要类,基于TransportClient(已弃用)。
(2)ElasticsearchRestTemplate是Spring Data Elasticsearch 3.2.x及以上版本推荐的类,基于High Level REST Client。它提供了更底层的操作,可以执行复杂的查询和聚合,适用于需要高度自定义查询的场景。
(3)High Level REST Client
Elasticsearch官方提供的Java高级 REST 客户端,用于与Elasticsearch集群通信。它提供了所有Elasticsearch操作的方法,但使用起来相对繁琐,需要手动构建请求和解析响应。在Spring Data Elasticsearch中,通常不需要直接使用,因为ElasticsearchRestTemplate已经对其进行了封装。
(4)Java API Client
Elasticsearch 7.15及以上版本引入了新的Java API客户端,这是一个基于Jackson的强类型客户端,提供了更好的类型安全和性能。但是,Spring Data Elasticsearch目前(截至3.2.x)还没有完全整合这个新客户端。
【2】如何选择
(1)如果只需要基本的CRUD和简单查询,推荐使用ElasticsearchRepository,因为它使用简单,代码量少。
(2)如果需要执行复杂的查询、聚合、或者需要更灵活地控制查询过程,那么应该使用ElasticsearchRestTemplate。
(3)如果Spring Data Elasticsearch提供的功能无法满足需求(例如,使用一些非常新的Elasticsearch特性),可以考虑直接使用Elasticsearch的Java API Client,但这样会失去Spring Data的便利性。

【3】如何使用
(1)ElasticsearchRepository 使用
(1)创建Repository接口
java
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
// 自定义查询方法
List<Product> findByName(String name);
List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
Page<Product> findByCategory(String category, Pageable pageable);
// 使用@Query注解自定义DSL
@Query("{\"match\": {\"name\": \"?0\"}}")
Page<Product> findByNameCustom(String name, Pageable pageable);
}
(2)使用示例
java
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
public Page<Product> searchProducts(String keyword, int page, int size) {
return productRepository.findByNameCustom(
keyword,
PageRequest.of(page, size, Sort.by("price").descending())
);
}
public List<Product> findProductsByPriceRange(double min, double max) {
return productRepository.findByPriceBetween(min, max);
}
}
(2)ElasticsearchRestTemplate 使用
(1)配置类
java
@Configuration
public class ElasticsearchConfig {
@Bean
public ElasticsearchRestTemplate elasticsearchRestTemplate(
ElasticsearchRestClient elasticsearchRestClient) {
return new ElasticsearchRestTemplate(elasticsearchRestClient);
}
}
(2)复杂查询实现
java
@Service
@RequiredArgsConstructor
public class ProductSearchService {
private final ElasticsearchRestTemplate elasticsearchRestTemplate;
public SearchPage<Product> complexSearch(String keyword, String category,
Double minPrice, Double maxPrice,
int page, int size) {
// 构建布尔查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
if (StringUtils.hasText(keyword)) {
boolQuery.must(QueryBuilders.matchQuery("name", keyword).boost(2.0f));
}
if (StringUtils.hasText(category)) {
boolQuery.must(QueryBuilders.termQuery("category", category));
}
if (minPrice != null || maxPrice != null) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
if (minPrice != null) rangeQuery.gte(minPrice);
if (maxPrice != null) rangeQuery.lte(maxPrice);
boolQuery.must(rangeQuery);
}
// 构建分页和排序
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withPageable(PageRequest.of(page, size))
.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC))
.build();
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
return SearchHitSupport.searchPageFor(searchHits, searchQuery.getPageable());
}
// 聚合查询示例
public Map<String, Long> getCategoryStats() {
TermsAggregationBuilder aggregation = AggregationBuilders
.terms("category_agg")
.field("category")
.size(10);
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.addAggregation(aggregation)
.build();
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
Terms terms = searchHits.getAggregations().get("category_agg");
return terms.getBuckets().stream()
.collect(Collectors.toMap(
Terms.Bucket::getKeyAsString,
Terms.Bucket::getDocCount
));
}
}
(3)新的Java API Client使用(Elasticsearch 7.17+)
(1)添加依赖
xml
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.11.4</version>
</dependency>
(2)配置客户端
java
@Configuration
public class ElasticsearchClientConfig {
@Value("${spring.elasticsearch.uris}")
private String[] elasticsearchUris;
@Bean
public ElasticsearchClient elasticsearchClient() {
// 创建低级客户端
RestClient restClient = RestClient
.builder(HttpHost.create(elasticsearchUris[0]))
.build();
// 创建传输层
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
// 创建API客户端
return new ElasticsearchClient(transport);
}
}
(3)使用Java API Client
java
@Service
@RequiredArgsConstructor
public class ProductJavaClientService {
private final ElasticsearchClient elasticsearchClient;
public void createProduct(Product product) throws IOException {
elasticsearchClient.index(i -> i
.index("products")
.id(product.getId())
.document(product));
}
public List<Product> searchProducts(String keyword) throws IOException {
SearchResponse<Product> response = elasticsearchClient.search(s -> s
.index("products")
.query(q -> q
.match(m -> m
.field("name")
.query(keyword)
)
),
Product.class);
return response.hits().hits().stream()
.map(Hit::source)
.collect(Collectors.toList());
}
}