ElasticSearch基础入门(七)使用Spring Data ElasticSearch查询文档
一、基本查询
ElasticsearchTemplate提供了一些基本的查询方法。
我们可以根据Id来查询或者直接查询所有,即match_all
。
@Test
public void findDoc() {
Iterable<Item> items = itemRepository.findAll();
items.forEach(System.out::println);
}
二、自定义方法
Spring Data的一个很强大的功能,就是根据方法名称就能自动实现功能。
比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
当然,方法名称要符合一定的约定:
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And | findByNameAndPrice | {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or | findByNameOrPrice | {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is | findByName | {"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not | findByNameNot | {"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between | findByPriceBetween | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual | findByPriceLessThan | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual | findByPriceGreaterThan | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before | findByPriceBefore | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After | findByPriceAfter | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like | findByNameLike | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith | findByNameStartingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith | findByNameEndingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing | findByNameContaining | {"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In | findByNameIn(Collection<String>names) | {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn | findByNameNotIn(Collection<String>names) | {"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | {"bool" : {"must" : {"field" : {"available" : true}}}} |
False | findByAvailableFalse | {"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy | findByAvailableTrueOrderByNameDesc | {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
举个例子,我们可以根据价格是否满足一定范围来查询,在接口中定义一个方法。
在测试方法中,我们直接调用这个方法,并没有写这个接口的实现类,直接运行。
@Test
public void findDoc() {
// 测试接口默认实现
itemRepository.findByPriceBetween(3000d, 4000d);
byPriceBetween.forEach(System.out::println);
}
可以看到价格在3000~4000元的两部小米手机被查询到了,而价格为 5499的华为并未找到。
虽然基本查询和自定义方法查询已经很强大了,但在很多场景下还是不够的。比如模糊查询
、通配符
、词条查询等
。因此,我们还需要更多高级查询。
三、高级查询
1. 匹配查询
@Test
public void testQuery() {
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "小米电视");
Iterable<Item> items = itemRepository.search(queryBuilder);
items.forEach(System.out::println);
}
我们title
匹配的是小米电视,但在指定映射的时候我们指定了该字段使用ik分词器,所以会被分成小米
、电视
连个词条。这个方法相当于使用了match
做匹配。所以只要包含了小米或者电视,就都可以被查询到。
Repository的search方法需要QueryBuilder参数,elasticSearch为我们提供了一个QueryBuilders工具类。
QueryBuilders提供了大量的静态方法,用于生成各种不同类型的查询对象,例如:词条、模糊、通配符等QueryBuilder对象。
elasticsearch提供很多可用的查询方式,但是不够灵活。如果想玩过滤或者聚合查询等就很难了。
2. 自定义查询
NativeSearchQueryBuilder
:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
@Test
public void testNativeQuery() {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 添加分词查询
NativeSearchQueryBuilder queryBuilder = nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米电视"));
// 添加分页参数
queryBuilder.withPageable(PageRequest.of(0, 1));
// 构建
NativeSearchQuery searchQuery = queryBuilder.build();
// 执行搜索,返回分页结果
Page<Item> itemPage = itemRepository.search(searchQuery);
// 获取总条数
long totalElements = itemPage.getTotalElements();
// 获取总页数
int totalPages = itemPage.getTotalPages();
// 获取内容
List<Item> items = itemPage.getContent();
items.forEach(System.out::println);
}
3. 排序
排序也通用通过NativeSearchQueryBuilder
完成:
我们直接在2.自定义查询的基础上,直接添加一个按价格进行升序排序。
// 添加排序信息
queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
这时,结果集就会按照价格升序排序了。
四、聚合
1. 聚合为桶,再嵌套子聚合计算平均值。
聚合为桶就是分组,比我我们按照品牌将文档分组。
@Test
public void testAggregations() {
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 不查询任何结果,过滤结果集。
FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder();
SourceFilter sourceFilter = fetchSourceFilterBuilder.withIncludes(new String[]{}).build();
nativeSearchQueryBuilder.withSourceFilter(sourceFilter);
// 添加聚合
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("group_by_brand").field("brand");
// 分组后添加子聚合, 即计算组内的价格平均值
TermsAggregationBuilder subAggregationBuilder = termsAggregationBuilder.
subAggregation(AggregationBuilders.avg("price_avg").field("price"));
nativeSearchQueryBuilder.addAggregation(subAggregationBuilder);
NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
// 执行查询
Page<Item> itemsPage = itemRepository.search(nativeSearchQuery);
// 先将结果转型为带聚合信息的分页
AggregatedPage aggregatedPage = (AggregatedPage) itemsPage;
// 因为使用的是terms, 根据词条聚合, 所以将Aggregation强转为StringTerms类型.
StringTerms group_by_brand = (StringTerms)aggregatedPage.getAggregation("group_by_brand");
// 取出所有buckets
List<StringTerms.Bucket> buckets = group_by_brand.getBuckets();
buckets.forEach(bucket -> {
InternalAvg price_avg = (InternalAvg) bucket.getAggregations().get("price_avg");
System.out.println(bucket.getKeyAsString());
System.out.println(bucket.getDocCount());
System.out.println("平均售价: " + price_avg.getValue());
});
}
聚合操作比较繁琐,尤其是在获取聚合结果的时候,需要将返回值转型才能获取到结果。