需求分析,如上图
对基金经理数据进行分页查询,并可根据年化回报率,综合得分进行排序
Seach After 两点比较重要:排序字段 排序字段值
1.排序字段一定要包含唯一值字段,即不重复,可用es自带id,因为排序的原理,类似数据库深度分页,条件包含上一页最后一条数据ID,效率比较高,同理要把这个唯一值作为下一页查询条件
2.如果没有业务相关的排序字段(无上年化回报,综合得分)怎么查询
排序字段默认为es默认_id,且倒序,srollId作为下页查询条件
3.查询包含业务条件时,需要注意排序字段包含业务条件,且排序顺序,先根据业务条件排序,再根据_id排序,同时要传入上一页排序字段的值及srollId
4.首页查询时排序字段值怎么传,如果不传会有问题,可以根据排序是升序,降序传入固定值,
如果是desc,其他页排序字段值肯定比它小,排序字段值传最大,可传101
如果是asc,其他页排序字段值肯定比它大,排序字段值传最小,可传-1
其他需要注意的地方
对比
使用search_after 进行分页 相比from&size的方式要更加高效,而且在不断有新数据入库的时候仅仅使用from和size分页会有重复的情况
相比使用scroll分页,search_after可以进行实时的查询
不过search_after不适合跳跃式的分页
注意事项
1,类似mysql limit offset size,进行分页查询时,取上一页最后一条数据的id;
2,如果是多个字段排序的话,search_after值的顺序要与我们排序条件的顺序一致,此处ID倒序
3,使用search_after时 from设置为0,-1或者直接不加from
代码
@Override
public Page<FundManagerDTO> page(ManagerEsReqDto reqDto) {
if (StringUtils.isBlank(reqDto.getOrderBy())) {
reqDto.setOrderBy("returnProportion");
}
if (StringUtils.isBlank(reqDto.getSort())) {
reqDto.setSort("desc");
}
/*首页可不传,不影响结果*/
if (StringUtils.isBlank(reqDto.getScrollId())) {
log.info("manager The paging query failed, Param is empty. req: {}", JSON.toJSONString(reqDto));
}
/*回报率可能为0或负值,首页查询赋值,前端可不传*/
if (1 == reqDto.getPage() && "desc".equalsIgnoreCase(reqDto.getSort())) {
reqDto.setOrderByValue(101);
}
if (1 == reqDto.getPage() && "asc".equalsIgnoreCase(reqDto.getSort())) {
reqDto.setOrderByValue(-1);
}
com.zgzt.common.util.Page<FundManagerDTO> page = new com.zgzt.common.util.Page<>();
BoolQueryBuilder params = buildQueryBuilder();
SearchRequest searchRequest = buildSearchRequest(EsOperateTables.manager, params, reqDto.getSize(),
reqDto.getOrderBy(), reqDto.getOrderByValue(), reqDto.getSort(), reqDto.getScrollId());
try {
List<FundManagerDTO> list = search(searchRequest, page);
page.setResult(list);
page.setPageNo(reqDto.getPage());
page.setPageSize(reqDto.getSize());
return page;
} catch (Exception e) {
log.error("manager The paging query exception", e);
throw ExceptionUtils.throwException(MANAGER_QUERY_ERROR);
}
}
private BoolQueryBuilder buildQueryBuilder() {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
return boolQueryBuilder;
}
private SearchRequest buildSearchRequest(EsOperateTables esOperateTables, QueryBuilder params, int pageSize,
String orderBy, double orderByValue, String sort, String scrollId) {
if (pageSize > 10000) {
pageSize = 10000;
}
SearchRequest searchRequest = new SearchRequest(esOperateTables.getValue());
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(params);
sourceBuilder.from(0);
sourceBuilder.size(pageSize);
sourceBuilder.timeout(new TimeValue(10, TimeUnit.SECONDS));
if (esOperateTables.getSelectCols() != null && esOperateTables.getSelectCols().length > 0) {
sourceBuilder.fetchSource(esOperateTables.getSelectCols(), new String[] {});
}
FieldSortBuilder fieldOderby;
if (SortOrder.ASC.toString().equalsIgnoreCase(sort)) {
fieldOderby = SortBuilders.fieldSort(orderBy).order(SortOrder.ASC);
} else {
fieldOderby = SortBuilders.fieldSort(orderBy).order(SortOrder.DESC);
}
FieldSortBuilder fieldId = SortBuilders.fieldSort("_id").order(SortOrder.DESC);
sourceBuilder.sort(fieldOderby).sort(fieldId);
sourceBuilder.searchAfter(new Object[] { orderByValue, scrollId });
searchRequest.source(sourceBuilder);
return searchRequest;
}
@Data
public class ManagerEsReqDto extends BasePageDto {
@ApiModelProperty("上一页最后数据_ID,必填字段,首页可不存")
private String scrollId;
@ApiModelProperty("上一页最后数据_业务排序字段值,必填字段,首页可不传")
private double orderByValue;
}
@Data
public class BasePageDto {
@ApiModelProperty("当前页数")
private Integer page = 1;
@ApiModelProperty("每页条数")
private Integer size = 10;
@ApiModelProperty("排序字段")
private String orderBy;
@ApiModelProperty("排序规则(升序:asc/倒序:desc)")
private String sort;
}
Elasticsearch 深入理解search After 处理深度分页问题_wangxuelei036的博客
这篇文章实现思路也可以,DSL语句写法,注意两点
1.排序字段,业务排序字段在前,文档_id在后
2.排序值,业务排序值在前,主键值在后