10.5 DSL查询语法
10.5.1 DSL Query的分类
ES提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见查询类型:
- 查询所有:一般测试用 eg.match_all
- 全文检索查询:利用分词器对用户输入分词,去倒排索引库中匹配 eg.match_query/multi_match_query
- 精确查询:根据精确词条值查找数据,一般是keyword、数值、日期、boolean等字段eg.ids/range/term
- 地理查询:eg.geo_distance/geo_bounding_box
- 复合查询:组合查询条件 eg.bool/funciton_score


不算分效率要高于算分,所以可以尽量把查询条件放在must_not
和filter
里。

10.5.2 Java API
Java API Client提供的接口方便很多,和原生请求高度一致


10.6 搜索结果处理
排序
RESTful请求
es支持对搜索结果排序,默认根据相关度算分(_score)排序,可排序字段类型:keyword、数值、geo、日期等。

Java API

高亮
搜索结果中的关键字突出显示。
原理:
- 将搜索结果中的关键字用标签标记出来
- 在页面中给标签添加css样式
RESTful请求

Java API


分页
RESTful请求
es默认返回top10数据,若查询更多数据则需要分页处理,es通过修改form、size参数控制返回的分页结果。

深度分页问题
es分页原理:eg.from=990,size=10,则es需要查出前1000条数据,然后截取990-1000的数据。

解决方案:
- search after:分页时需要排序,原理是从上一次的排序值开始查询下一页数据。官方推荐。
- scroll:原理是将排序数据形成快照保存在内存。官方不推荐。

Java API

Demo
实现功能如下:
1.酒店搜索和分页
分页页码和大小从前端请求中获取,使用RestParams
接收前端参数。
2.酒店结果过滤
根据RestParam
接收的前端请求参数过滤
3.周边酒店
前端使用高德接口获取当前定位并将位置通过RestParams
传到后端,后端通过geoDistance
排序。
4.酒店竞价排名
ES的实体类添加isAD
字段,更改isAD=true
的算分权重。
核心代码如下:
@Slf4j
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private ElasticsearchClient esClient;
@Override
public PageResult search(RequestParams params) {
try {
SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
searchRequestBuilder.index("hotel");
SearchResponse<HotelDoc> searchResponse = esClient.search(s -> {
s.index("hotel");
//添加查询条件和广告算分/排序
buildBasicQuery(params,s);
//分页
s.from((params.getPage()-1)* params.getSize()).size(params.getSize());
return s;
}
,HotelDoc.class);
return handleResponse(searchResponse);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void buildBasicQuery(RequestParams params, SearchRequest.Builder searchRequestBuilder) {
//搜索条件
BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
//关键字搜索
String key = params.getKey();
if(null ==key|| "".equals(key)){
boolQueryBuilder.must(m->m.matchAll(new MatchAllQuery.Builder().build()));
}else{
boolQueryBuilder.must(m->m.match(mat->mat.field("all").query(key)));
}
//城市过滤条件
if(null!= params.getCity() && !params.getCity().equals("")){
boolQueryBuilder.filter(f->f.term(t->t.field("city").value(params.getCity())));
}
//品牌过滤条件
if(params.getBrand()!=null && "".equals(params.getBrand())){
boolQueryBuilder.filter(f->f.term(t->t.field("brand").value(params.getBrand())));
}
//星级过滤条件
if(params.getStarName()!=null && "".equals(params.getStarName())){
boolQueryBuilder.filter(f->f.term(t->t.field("starName").value(params.getStarName())));
}
//价格过滤条件
if(params.getMinPrice()!=null && params.getMaxPrice()!=null){
boolQueryBuilder.filter(f->f.range(r->r
.field("price")
.gte(JsonData.of(params.getMinPrice()))
.lte(JsonData.of(params.getMaxPrice()))));
}
// searchRequestBuilder.query(q->q.bool(boolQueryBuilder.build()));
//广告算分
FunctionScoreQuery.Builder functionScoreBuilder = new FunctionScoreQuery.Builder();
functionScoreBuilder
//原始查询
.query(q->q.bool(boolQueryBuilder.build()))
//算分条件数组
.functions(f->f
//其中一个算分条件
.filter(fl->fl.term(t->t.field("isAD").value(true))).weight(10d)
)
//算分函数
.boostMode(FunctionBoostMode.Multiply);
searchRequestBuilder.query(q->q.functionScore(functionScoreBuilder.build()));
//排序
if(null!= params.getLocation() && !params.getLocation().equals("")){
SortOptions.Builder sortBuilder = new SortOptions.Builder();
sortBuilder.geoDistance(geo->geo
.field("location")
.location(new GeoLocation.Builder().text(params.getLocation()).build())
.order(SortOrder.Asc)
.unit(DistanceUnit.Kilometers)
);
searchRequestBuilder.sort(sortBuilder.build());
}
}
private PageResult handleResponse(SearchResponse<HotelDoc> response) {
HitsMetadata<HotelDoc> searchHits = response.hits();
assert searchHits.total() != null;
long total = searchHits.total().value();
List<HotelDoc> hotels = new ArrayList<>();
for (Hit<HotelDoc> hit : searchHits.hits()) {
HotelDoc hotelDoc = hit.source();
//判断是否有距离排序
List<FieldValue> sort = hit.sort();
if(sort.size()>0){
assert hotelDoc != null;
hotelDoc.setDistance(sort.get(0).doubleValue());
}
hotels.add(hotelDoc);
}
return new PageResult(total,hotels);
}
}
@RestController
@RequestMapping("hotel")
public class HotelController {
@Autowired
private IHotelService hotelService;
@PostMapping("list")
public PageResult search(@RequestBody RequestParams params) {
return hotelService.search(params);
}
}