目录
背景
近日,正在整合SpringBoot与ElasticSearch相关代码,由于对ES的学习时间有限,对其概念和API调用还不够了解,官网的API调用实例看着有许多重复类似的构造模式,于是打算先尝试地把一些已掌握的API接口封装成底层DAO,待后续有进一步的需求,再进一步完善。
Map参数传递的需求
在封装DAO过程中,很难免会遇到需要把一些通用参数组装一起,最后通过某个参数传入,进一步调用相应的API的需求。
这样的需求,我习惯性地采用方法调用前用Map来进行封装,方法内部再取出对应的参数值。
如:在ElasticSearch中有Term、Range、Match等查询,其中有 BoolQueryBuilder能组合以上的查询条件,但是这么多查询,如果要拼接在一起,肯定会出现方法中各种查询的构造方式,如下:
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(fieldName, value);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// includeLower(是否包含下边界)、includeUpper(是否包含上边界)
searchSourceBuilder.query(QueryBuilders.rangeQuery("birthDate")
.gte("now-30y").includeLower(true).includeUpper(true));
searchSourceBuilder.query(QueryBuilders.fuzzyQuery("name", "三").fuzziness(Fuzziness.AUTO));
如果一个service,就调用其中的一个或多个查询方法,那么整个项目就到处都是冗余的代码。
于是,我自己尝试地将所有查询的构造封装在一个方法里,通过一个Map对象进行传递构造的参数(仔细看来,代码真不忍直视)
public List<T> getSearchResultList(Map<EsSearchType, Map<String, Object>> searchParmMap, String orderFieldName, SortOrder sortOrder, Integer from, Integer size, Class<T> clazz) throws Exception {
Map<EsBoolQueryType, QueryBuilder> typeQueryBuilderMap = Maps.newLinkedHashMap();
Map<String, Object> fieldMap;
for (EsSearchType esSearchType : searchParmMap.keySet()) {
switch (esSearchType) {
case TERM:
fieldMap = searchParmMap.get(EsSearchType.TERM);
String fieldName = fieldMap.keySet().iterator().next();
Object value = fieldMap.values().iterator().next();
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(fieldName, value);
typeQueryBuilderMap.put(EsBoolQueryType.MUST, termQueryBuilder);
return getBoolQuerySearchResult(typeQueryBuilderMap, from, size, orderFieldName, sortOrder, clazz);
case BOOL_WITH_TERM_AND_RANGE:
//构造termQuery
fieldMap = searchParmMap.get(EsSearchType.BOOL_WITH_TERM_AND_RANGE);
Map<String, Object> termMap = (Map<String, Object>) fieldMap.get(EsSearchType.TERM.getTypeName());
TermQueryBuilder termQuery = QueryBuilders.termQuery(termMap.keySet().iterator().next(), termMap.values().iterator().next());
//构造rangeQuery
Map<String, Object> rangeMap = (Map<String, Object>) fieldMap.get(EsSearchType.RANGE.getTypeName());
String rangeFieldName = (String) rangeMap.get(ElasticSearchConstants.RANGE_FIELD_NAME);
Map<String, Object> rangeParmMap = (Map<String, Object>) rangeMap.get(ElasticSearchConstants.RANGE_FIELD_MAP);
RangeQueryBuilder rangeQuery = getRangeQuery(rangeFieldName, rangeParmMap);
// 创建 Bool 查询构建器
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 构建查询条件
boolQueryBuilder.must(termQuery)
.filter().add(rangeQuery);
return getQuerySearchResult(boolQueryBuilder, from, size, orderFieldName, sortOrder, clazz);
default:
break;
}
}
return null;
}
这样貌似能节省以上Term、Range等查询的构造方式,但是为了后续方法调用,每次都需要将各个参数用大量可读性查的Map的组装方式将参数传入,如下:
Map<EsSearchType, Map<String, Object>> searchParmMap = Maps.newLinkedHashMap();
Map<String, Object> rangeTermMap = Maps.newHashMap();
//构造精确查询条件
Map<String, Object> termMap = Maps.newHashMap();
termMap.put("state", impalaSqlDto.getState());
rangeTermMap.put(EsSearchType.TERM.getTypeName(), termMap);
//构造范围查询条件
Map<String, Object> rangeMap = Maps.newLinkedHashMap();
rangeMap.put(ElasticSearchConstants.RANGE_FIELD_NAME, "start_time");
Map<String, Object> rangeFieldMap = Maps.newHashMap();
rangeFieldMap.put(ElasticSearchConstants.GTE, DateUtil.formatDate(impalaSqlDto.getStartDate(), DateUtil.DATE_TIME_PATTERN_NANOS));
rangeFieldMap.put(ElasticSearchConstants.LTE, DateUtil.formatDate(impalaSqlDto.getEndDate(), DateUtil.DATE_TIME_PATTERN_NANOS));
rangeMap.put(ElasticSearchConstants.RANGE_FIELD_MAP, rangeFieldMap);
rangeTermMap.put(EsSearchType.RANGE.getTypeName(), rangeMap);
//组装成bool查询条件(TERM+RANGE)
searchParmMap.put(EsSearchType.BOOL_WITH_TERM_AND_RANGE, rangeTermMap);
List<ImpalaSQLTargetModel> resultList = getSearchResultList(searchParmMap, impalaSqlDto.getSortField(), order, impalaSqlDto.getStart(), impalaSqlDto.getLimit(), ImpalaSQLTargetModel.class);
return resultList;
Map封装并不减少代码量
重新回顾了下,自己本来是想节省代码的,但是发现非但代码量没减少,可读性大大降低了,于是必须改改改。。。
回到自己的初心,只是想简单传入参数,构造对应的查询,中间构造代码对用户透明,于是打算引入类似建造者模式(设计模式,貌似实际也不全是该模式)
将Term、Range等封装为某个查询构造对象的内部类
public class EsBoolQueryInfo {
private Integer from;
private Integer size;
private String orderName;
private SortOrder sortOrder;
private Term term;
private Range range;
public EsBoolQueryInfo(String orderName, Integer from, Integer size, SortOrder sortOrder) {
this.from = from;
this.size = size;
this.orderName = orderName;
this.sortOrder = sortOrder;
}
public class Term {
private String fieldName;
private String fieldValue;
public Term(String fieldName, String fieldValue) {
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getFieldValue() {
return fieldValue;
}
public void setFieldValue(String fieldValue) {
this.fieldValue = fieldValue;
}
}
public class Range {
private String fieldName;
private String format;
private String lt;
private String gt;
private String lte;
private String gte;
public Range(String fieldName, String format, String lte, String gte, String lt, String gt) {
this.fieldName = fieldName;
this.format = format;
this.lte = lte;
this.gte = gte;
this.lt = lt;
this.gt = gt;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getLt() {
return lt;
}
public void setLt(String lt) {
this.lt = lt;
}
public String getGt() {
return gt;
}
public void setGt(String gt) {
this.gt = gt;
}
public String getLte() {
return lte;
}
public void setLte(String lte) {
this.lte = lte;
}
public String getGte() {
return gte;
}
public void setGte(String gte) {
this.gte = gte;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
}
提供一个外部构造BoolQueryBuilder的方法
public QueryBuilder buildBoolQueryBuilder(EsSearchType searchType) {
//声明布尔查询条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
switch (searchType) {
case TERM:// 构建精确查询条件
TermQueryBuilder termOnlyQueryBuilder = getTermQueryBuilder();
boolQueryBuilder.must(termOnlyQueryBuilder);
break;
case RANGE:// 构建范围查询条件
RangeQueryBuilder rangeOnlyQueryBuilder = getRangeQueryBuilder();
boolQueryBuilder.filter().add(rangeOnlyQueryBuilder);
break;
case BOOL_WITH_TERM_AND_RANGE:// 构建精确查询与范围查询组合的查询条件
TermQueryBuilder termQueryBuilder = getTermQueryBuilder();
RangeQueryBuilder rangeQueryBuilder = getRangeQueryBuilder();
boolQueryBuilder.must(termQueryBuilder)
.filter().add(rangeQueryBuilder);
break;
default:
break;
}
return boolQueryBuilder;
}
private TermQueryBuilder getTermQueryBuilder() {
return QueryBuilders.termQuery(this.term.getFieldName(), this.term.getFieldValue());
}
private RangeQueryBuilder getRangeQuery() {
RangeQueryBuilder query = QueryBuilders.rangeQuery(this.range.getFieldName())
.includeLower(true).includeUpper(true);
if (StringUtils.isNoneBlank(this.range.getGte())) {
query.gte(this.range.getGte());
}
if (StringUtils.isNoneBlank(this.range.getLte())) {
query.lte(this.range.getLte());
}
if (StringUtils.isNoneBlank(this.range.getGt())) {
query.gt(this.range.getGt());
}
if (StringUtils.isNoneBlank(this.range.getLt())) {
query.lt(this.range.getLt());
}
query.format(this.range.getFormat());
return query;
}
最终方法调用大大精简了,代码可读性也提高了
EsBoolQueryInfo esBoolQueryInfo = new EsBoolQueryInfo(impalaSqlDto.getSortField(), impalaSqlDto.getStart(), impalaSqlDto.getLimit(), order);
esBoolQueryInfo.buildTerm("state", impalaSqlDto.getState());
esBoolQueryInfo.buildRange("start_time", DateUtil.DATE_TIME_PATTERN_NANOS, DateUtil.formatDate(impalaSqlDto.getStartDate(), DateUtil.DATE_TIME_PATTERN_NANOS), DateUtil.formatDate(impalaSqlDto.getEndDate(), DateUtil.DATE_TIME_PATTERN_NANOS), null, null);
QueryBuilder queryBuilder = esBoolQueryInfo.buildBoolQueryBuilder(EsSearchType.BOOL_WITH_TERM_AND_RANGE);
return getQuerySearchResult(queryBuilder, esBoolQueryInfo, ImpalaSQLTargetModel.class);
总结
- API熟悉后再进行代码抽象封装,不熟悉的情况下切勿提早封装代码,否则适得其反
- 封装代码时必须在调用和被调用层面做考虑,可能被调用看起来简洁了,但是调用方实际代码量没减少
- 封装代码必须要考虑可读性,这点在封装前需要考虑采用这样封装方式代码可读性如何,否则封装了别人也看不到白搭
- 需要熟悉设计模式,本次代码改进貌似用到类似构造器模式和策略模式,但也不大像,代码要精进,设计模式必须掌握,还有很大改进空间。