1. DSL分类查询
Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:
-
查询所有:查询出所有数据,一般测试用。例如:match_all
-
全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match_query
- multi_match_query
-
精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids
- range
- term
-
地理(geo)查询:根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
-
复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool
- function_score
2. 深度分页问题
针对深度分页,ES提供了两种解决方案,官方文档:
- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
- scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。
分页查询的常见实现方案以及优缺点:
-
from + size
:- 优点:支持随机翻页
- 缺点:深度分页问题,默认查询上限(from + size)是10000
- 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
-
search after
:- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:只能向后逐页查询,不支持随机翻页
- 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
-
scroll
:- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:会有额外内存消耗,并且搜索结果是非实时的
- 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用search after方案。
3. DSL分类查询代码实现
- 数组遍历使用stream流
List<HotelDoc> hotelDocList = Stream.of(hits).map(hit -> {
//得到每条记录对应的json串
String json = hit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
return hotelDoc;
}).collect(Collectors.toList());
- DSL分类查询代码实现
@Autowired
private RestHighLevelClient client;
/**
* 查询所有
* match_all
*
* @throws Exception
*/
@Test
public void matchAllTest() throws Exception {
//1.创建请求对象
SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
//2..设置条件,准备DSL
searchRequest.source().query(
QueryBuilders.matchAllQuery()
);
//3.执行查询, search
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//4.解析得到的结果
//4.1得到的总条数
Long count = searchResponse.getHits().getTotalHits().value;
System.out.println("count = " + count);
//4.2得到hits数组(记录列表)
SearchHit[] hits = searchResponse.getHits().getHits();
List<HotelDoc> hotelDocList = Stream.of(hits).map(hit -> {
//得到每条记录对应的json串
String json = hit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
return hotelDoc;
}).collect(Collectors.toList());
/*for (SearchHit hit : hits) {
//得到每条记录对应的json串
String json = hit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
}*/
}
/**
* match
* multi_match
*
* @throws Exception
*/
@Test
public void matchTest() throws Exception {
//1.创建请求对象
SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
//2.设置请求条件,DSL
searchRequest.source().query(
//QueryBuilders.matchQuery("all","外滩如家")
QueryBuilders.multiMatchQuery("外滩如家", "name", "brand", "business")
);
//3.执行查询
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
//4.解析得到的结果
Long count = response.getHits().getTotalHits().value;
System.out.println("count = " + count);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println("json = " + json);
}
}
/**
* term精确查询
*
* @throws Exception
*/
@Test
public void termTest() throws Exception {
//1.创建请求对象
SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
//2.设置请求条件,DSL
searchRequest.source()
.query(QueryBuilders.termQuery("city", "上海"));
//3.执行查询
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
//4.解析得到的结果
Long count = response.getHits().getTotalHits().value;
System.out.println("count = " + count);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println("json = " + json);
}
}
/**
* range
* 范围查询
*
* @throws Exception
*/
@Test
public void rangeTest() throws Exception {
//1.创建请求对象
SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
//2.设置请求条件,DSL
searchRequest.source().query(QueryBuilders.rangeQuery("price").gte(1000).lt(3000));
//3.执行查询
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
//4.解析得到的结果
Long count = response.getHits().getTotalHits().value;
System.out.println("count = " + count);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println("json = " + json);
}
}
/**
* 经纬度查询
*
* @throws Exception
*/
@Test
public void geoDistanceTest() throws Exception {
//1.创建请求对象
SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
//2.设置请求条件,DSL
searchRequest.source()
.query(QueryBuilders.geoDistanceQuery("location")
//.point(new GeoPoint(31.242201,121.509106))
.point(31.242201, 121.509106)
.distance(15, DistanceUnit.KILOMETERS));
//3.执行查询
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
//4.解析得到的结果
Long count = response.getHits().getTotalHits().value;
System.out.println("count = " + count);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println("json = " + json);
}
}
/**
* bool查询
*
* @throws Exception
*/
@Test
public void Test() throws Exception {
//1.创建请求对象
SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
//2.设置请求条件,DSL
searchRequest.source()
.query(QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "如家"))
.mustNot(QueryBuilders.rangeQuery("price").gt(400))
.filter(QueryBuilders.geoDistanceQuery("location")
.point(31.21, 121.5)
.distance(15, DistanceUnit.KILOMETERS)));
//3.执行查询
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
//4.解析得到的结果
Long count = response.getHits().getTotalHits().value;
System.out.println("count = " + count);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println("json = " + json);
}
}
过滤+算分+排序+距离排序+高亮+分页
得到的距离数据和高亮数据需要特殊处理
@Autowired
private RestHighLevelClient client;
@Override
public PageResult<HotelDoc> searchHotel(SearchParam searchParam) {
//1.校验参数
//第几条数据
Integer page = searchParam.getPage();
//页面大小
Integer size = searchParam.getSize();
//搜索的内容
String key = searchParam.getKey();
//创建封装结果对象
PageResult<HotelDoc> hotelDocPageResultge = new PageResult<>();
//创建集合
List<HotelDoc> hotelDocList = new ArrayList<>();
try {
if (page == null || size == null) {
throw new RuntimeException("非法参数");
}
//2.业务处理
//2.1创建请求对象
SearchRequest searchRequest = new SearchRequest(ESConstant.HOTEL_INDEX);
//2.2准备查询条件
//创建查询条件对象---->使用bool查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//------>搜索框 判断null和空串
if (StringUtils.isBlank(key)) {
//2.2.1如果是空,就查询所有
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
//2.2.2如果不是空,就根据条件查询
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
//------>城市
String city = searchParam.getCity();
if (StringUtils.isNoneBlank(city)) {
boolQuery.filter(QueryBuilders.termQuery("city", city));
}
//------------>品牌
String brand = searchParam.getBrand();
if (StringUtils.isNoneBlank(brand)) {
boolQuery.filter(QueryBuilders.termQuery("brand", brand));
}
//------------>星级
String starName = searchParam.getStarName();
if (StringUtils.isNoneBlank(starName)) {
boolQuery.filter(QueryBuilders.termQuery("starName", starName));
}
//--------->价格
Integer minPrice = searchParam.getMinPrice();
Integer maxPrice = searchParam.getMaxPrice();
if (null != minPrice && null != maxPrice) {
boolQuery.filter(QueryBuilders
.rangeQuery("price")
.gte(minPrice)
.lte(maxPrice));
}
//-----------> 算分 isAD=true 加分用于广告
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(boolQuery,
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("isAD", true),
ScoreFunctionBuilders.weightFactorFunction(100F)
)}).boostMode(CombineFunction.SUM);
//searchRequest.source().query(boolQuery);
//================>排序 在分页之前,在条件之后
String sortBy = searchParam.getSortBy();
if ("score".equals(sortBy)) {
searchRequest.source().sort("score", SortOrder.DESC);
} else if ("price".equals(sortBy)) {
searchRequest.source().sort("price", SortOrder.DESC);
}
//===============> 根据距离排序 -->得到查询的距离后还需要处理
String location = searchParam.getLocation();
if (StringUtils.isNoneBlank(location)) {
searchRequest.source().sort(
SortBuilders.geoDistanceSort("location", new GeoPoint(location))
.unit(DistanceUnit.KILOMETERS));
}
//2.3准备分页参数
//==========================>高亮显示 -->高亮数据也需要处理返回给前端
//functionScoreQueryBuilder中封装的有boolQuery
searchRequest.source()
.query(functionScoreQueryBuilder).highlighter(
new HighlightBuilder()
.field("name")
.requireFieldMatch(false)
.preTags("<font style='color:red'>")
.postTags("</font>")
)
.from((page - 1) * size)
.size(size);
//2.4执行查询
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
//2.5解析结果并封装数据
//2.5.1得到符合条件的总记录数
long total = response.getHits().getTotalHits().value;
//2.5.2得到符合条件的记录数
for (SearchHit hit : response.getHits().getHits()) {
String hotelDocJson = hit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(hotelDocJson, HotelDoc.class);
//处理得到的距离数据
Object[] sortValues = hit.getSortValues();
if (sortValues != null && sortValues.length > 0) {
//得到当前位置与location的距离
Object distance = sortValues[0];
hotelDoc.setDistance(distance);
}
//处理得到的高亮的数据
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//判断得到的高亮数据是否为空
if (highlightFields!=null && highlightFields.size()>0) {
HighlightField highlightField = highlightFields.get("name");
Text[] fragments = highlightField.getFragments();
String name = fragments[0].toString();
hotelDoc.setName(name);
}
hotelDocList.add(hotelDoc);
}
//2.5.3封装数据
hotelDocPageResultge.setTotal(total);
hotelDocPageResultge.setHotels(hotelDocList);
} catch (IOException e) {
e.printStackTrace();
}
return hotelDocPageResultge;
}