代码精进篇之封装ElasticSearch通用Dao(去除掉方法间调用的Map传递)

目录

 

背景

Map参数传递的需求

Map封装并不减少代码量

将Term、Range等封装为某个查询构造对象的内部类

提供一个外部构造BoolQueryBuilder的方法

总结


背景

近日,正在整合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);

总结

  1. API熟悉后再进行代码抽象封装,不熟悉的情况下切勿提早封装代码,否则适得其反
  2. 封装代码时必须在调用和被调用层面做考虑,可能被调用看起来简洁了,但是调用方实际代码量没减少
  3. 封装代码必须要考虑可读性,这点在封装前需要考虑采用这样封装方式代码可读性如何,否则封装了别人也看不到白搭
  4. 需要熟悉设计模式,本次代码改进貌似用到类似构造器模式和策略模式,但也不大像,代码要精进,设计模式必须掌握,还有很大改进空间。
发布了40 篇原创文章 · 获赞 12 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/JacksonKing/article/details/104730760