运用组合模式实现复合搜索条件构建

(点击

上方公众号

,可快速关注)



来源:琴水玉 ,

www.cnblogs.com/lovesqcc/p/8432981.html

在订单搜索中,有时需要实现复合搜索,比如 ( A must B ) or ( C must D ) 或者 (A or C) must ( B or D ) 。 这就需要能够灵活地组合条件,条件可以是原子的或复合的。可以使用组合模式来实现。

思路

要实现复合搜索条件的构建,需要解决两个问题:A. 如何表示复合搜索条件; B. 如何将复合搜索条件转换为合适的ES查询对象。对于A来说,关键就是搜索条件可灵活组合,用组合模式再合适不过;对于B来说,需要知道ES如何表示这些复合搜索。

(A must B ) or ( C must D) 的 ES 表示为:

{"query":{"bool":{"should":[{"bool":{"must":[{"term":{"shop_id":63077}},{"terms":{"state":[1,2,3,4,5]}}]}},{"bool":{"must":[{"term":{"shop_id":63077}},{"range":{"book_time":{"gt":1516550400}}},{"terms":{"order_tags":["IS_SECURED_TRANSACTIONS"]}}]}}],"minimum_should_match":1}},"from":0,"size":10}

( A or B ) must ( C or D ) 的 ES 表示是:

bool:{must:[{bool:{should:[{A},{C}],minimum_should_match: 1}},{bool:{should:[{D},{B}], minimum_should_match: 1}}]}

组合模式的要点是:原子条件和复合条件具备相同的行为接口,从而能够组合和叠加。

实现

组合模式

STEP1: 首先定义 Condition 接口, 目前仅支持 与 和 或 操作,以及查询对象转换。

/**

 * Created by shuqin on 18/2/7.

 */

public interface Condition {

 

  Condition and(Condition c);

  Condition or(Condition c, Integer shouldMinimumMatch);

  Map expr();    // ES 查询对象

 

  default String json() {

    return JSON.toJSONString(this);

  }

 

}

STEP2: 原子条件 EsCondition 实现

package zzz.study.patterns.composite.escondition;

 

import com.google.common.collect.ImmutableMap;

import com.google.common.collect.Lists;

 

import java.io.Serializable;

import java.util.List;

import java.util.Map;

 

import lombok.Data;

 

/**

 * Created by shuqin on 18/2/8.

 */

@Data

public class EsCondition implements Condition, Serializable {

 

  private static final long serialVersionUID = -209082552315760372L;

 

  /** ES 字段名称 */

  private String fieldName;

 

  /** 匹配符 */

  private Op op;

 

  /**

   *

   * 要匹配的值,用于 eq, neq, range, in, match

   *

   * eq 传 单个值对象,比如 Integer, String , etc

   * in 传 List 对象

   * range 传 Range 对象

   * match 传 Match 对象

   *

   */

  private Object value;

 

  public EsCondition() {

  }

 

  public EsCondition(String fieldName, Op op, Object value) {

    this.fieldName = fieldName;

    this.op = op;

    this.value = http://www.gunmi.cn/v/value;

  }

 

  public String getFieldName() {

    return fieldName;

  }

 

  public Op getOp() {

    return op;

  }

 

  public Object getValue() {

    return value;

  }

 

  @Override

  public String toString() {

    return "EsCondition{" +

           "fieldName="" + fieldName + "\"" +

           ", op=" + op +

           ", value="http://www.gunmi.cn/v/+ value +

           "}";

  }

 

  @Override

  public Condition and(Condition c) {

    return new CompositeMustCondition(Lists.newArrayList(c, this));

  }

 

  @Override

  public Condition or(Condition c, Integer shouldMinimumMatch) {

    List<Condition> shouldConditions = Lists.newArrayList(c, this);

    return new CompositeShouldCondition(shouldConditions, shouldMinimumMatch);

  }

 

  private static Map<String, String> op2EsKeyMap = ImmutableMap.of(

      Op.eq.name(), "term",

      Op.neq.name(), "term",

      Op.in.name(), "terms",

      Op.range.name(), "range",

      Op.match.name(), "match"

  );

 

  @Override

  public Map expr() {

    return buildEsExpr(op2EsKeyMap.get(op.name()));

  }

 

  private Map buildEsExpr(String esKey) {

    return ImmutableMap.of(esKey, ImmutableMap.of(fieldName, value));

  }

 

}

STEP3: 复合 must 条件

package zzz.study.patterns.composite.escondition;

 

import com.google.common.collect.ImmutableMap;

import com.google.common.collect.Lists;

 

import java.io.Serializable;

import java.util.List;

import java.util.Map;

import java.util.stream.Collectors;

 

import lombok.Data;

 

/**

 * Created by shuqin on 18/2/8.

 */

@Data

public class CompositeMustCondition implements Condition, Serializable {

 

  private static final long serialVersionUID = 2546838275170403153L;

 

  private List<Condition> multiConditions;

 

  public CompositeMustCondition() { multiConditions = Lists.newArrayList(); }

 

  public CompositeMustCondition(List<Condition> multiConditions) {

    this.multiConditions = multiConditions;

  }

 

  @Override

  public Condition and(Condition c) {

    multiConditions.add(c);

    return new CompositeMustCondition(multiConditions);

  }

 

  @Override

  public Condition or(Condition c, Integer shouldMinimumMatch) {

    List<Condition> shouldConditions = Lists.newArrayList(c, this);

    return new CompositeShouldCondition(shouldConditions, shouldMinimumMatch);

  }

 

  @Override

  public Map expr() {

    List<Map> conditions = multiConditions.stream().map(Condition::expr).collect(Collectors.toList());

    return ImmutableMap.of("bool", ImmutableMap.of("must", conditions));

  }

}

STEP4: 复合 或 查询

package zzz.study.patterns.composite.escondition;

 

import com.google.common.collect.ImmutableMap;

import com.google.common.collect.Lists;

 

import java.io.Serializable;

import java.util.List;

import java.util.Map;

import java.util.stream.Collectors;

 

import lombok.Data;

 

/**

 * Created by shuqin on 18/2/8.

 */

@Data

public class CompositeShouldCondition implements Condition, Serializable {

 

  private static final long serialVersionUID = -3706269911758312468L;

 

  private List<Condition> conditions;

 

  private Integer shouldMatchMinimum = 1;

 

  public CompositeShouldCondition() {

    this.conditions = Lists.newArrayList();

    this.shouldMatchMinimum = 1;

  }

 

  public CompositeShouldCondition(List<Condition> conditions, Integer shouldMinimumMatch) {

    this.conditions = conditions;

    this.shouldMatchMinimum = shouldMinimumMatch;

  }

 

  @Override

  public Condition and(Condition c) {

    return new CompositeMustCondition(Lists.newArrayList(c, this));

  }

 

  @Override

  public Condition or(Condition c, Integer shouldMinimumMatch) {

    return new CompositeShouldCondition(Lists.newArrayList(c, this),

                                        shouldMinimumMatch);

  }

 

  @Override

  public Map expr() {

    List<Map> conditions = this.conditions.stream().map(Condition::expr).collect(

        Collectors.toList());

    return ImmutableMap.of("bool", ImmutableMap.of("should", conditions, "minimum_should_match", shouldMatchMinimum));

  }

}

通用方法抽离

事实上,发现 or 的实现基本相同,可以写在接口的默认方法里:

package zzz.study.patterns.composite.escondition;

 

import com.alibaba.fastjson.JSON;

import com.google.common.collect.Lists;

 

import java.util.List;

import java.util.Map;

 

/**

 * Created by shuqin on 18/2/8.

 */

public interface Condition {

 

  Condition and(Condition c);

  Map expr();    // ES 查询对象

 

  default String json() {

    return JSON.toJSONString(this);

  }

 

  default Condition or(Condition c, Integer shouldMinimumMatch) {

    List<Condition> shouldConditions = Lists.newArrayList(c, this);

    return new CompositeShouldCondition(shouldConditions, shouldMinimumMatch);

  }

 

  default Condition or(List<Condition> conds, Integer shouldMinimumMatch) {

    List<Condition> shouldConditions = Lists.newArrayList(this);

    shouldConditions.addAll(conds);

    return new CompositeShouldCondition(shouldConditions, shouldMinimumMatch);

  }

 

}

工厂模式

使用 new EsCondition 显得比较“硬”一点,可以使用工厂模式使得API更加友好一点。

package zzz.study.patterns.composite.escondition;

 

import java.util.List;

 

/**

 * Created by shuqin on 18/2/11.

 */

public class ConditionFactory {

 

  public static Condition eq(String fieldName, Object value) {

    return new EsCondition(fieldName, Op.eq, value);

  }

 

  public static Condition neq(String fieldName, Object value) {

    return new EsCondition(fieldName, Op.neq, value);

  }

 

  public static Condition in(String fieldName, List value) {

    return new EsCondition(fieldName, Op.in, value);

  }

 

  public static Condition range(String fieldName, Range range) {

    return new EsCondition(fieldName, Op.range, range);

  }

 

  public static Condition match(String fieldName, Match match) {

    return new EsCondition(fieldName, Op.match, match);

  }

 

}

示例

package zzz.study.patterns.composite.escondition;

 

import com.google.common.collect.Lists;

import static zzz.study.patterns.composite.escondition.ConditionFactory.*;

 

/**

 * Created by shuqin on 18/2/8.

 */

public class ComplexConditionTest2 {

 

  public static void main(String[] args) {

    Condition c1 = eq("shop_id", "55");

    Condition c2 = eq("order_no", "E2001");

    Condition c3 = range("book_time", new Range(15100000000L, 15200000000L));

    Condition c4 = in("state", Lists.newArrayList(5, 6));

    Condition c5 = match("goods_title", new Match("商品标题的一部分", "90%"));

 

    Condition c1mustc2mustc3 = c1.and(c2).and(c3);

 

    //System.out.println("c1 must c2 must c3 json: \n" + c1mustc2mustc3.json());

    System.out.println("c1 must c2 must c3 expr: \n" + c1mustc2mustc3.expr());

 

    Condition c1c2orc3c4 = c1.and(c2).or(c3.and(c4), 1);

 

    //System.out.println("( c1 must c2 )or( c3 must c4 ) json:\n" + c1c2orc3c4.json());

    System.out.println("\n( c1 must c2 )or( c3 must c4 ) expr:\n" + c1c2orc3c4.expr());

 

    Condition c2orc3mustc1orc4orc5 = (c2.or(c3, 1)).and(c4.or(Lists.newArrayList(c1,c5),2));

 

    //System.out.println("( c2 or c3 ) must ( c4 or c5 ) json:\n" + c2orc3mustc4orc5.json());

    System.out.println("\n( c2 or c3 ) must ( c1 or c4 or c5 ) expr:\n" + c2orc3mustc1orc4orc5.expr());

 

    Condition complexCond = ((c1.and(c2)).or(c3.and(c4), 1)).and(c5.or(Lists.newArrayList(c2,c3), 2));

    System.out.println("\n(( c1 must c2 ) or ( c3 must c4 )) must (c5 or c2 or c3) expr:\n" + complexCond.expr());

 

  }

}

小结

通过组合模式,清晰地实现了复合搜索条件的构建;通过工厂模式,创建条件对象更加简洁友好。

看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

运用组合模式实现复合搜索条件构建