在訂單搜索中,有時須要實現複合搜索,好比 ( A must B ) or ( C must D ) 或者 (A or C) must ( B or D ) 。 這就須要可以靈活地組合條件,條件能夠是原子的或複合的。能夠使用組合模式來實現。java
要實現複合搜索條件的構建,須要解決兩個問題:A. 如何表示複合搜索條件; B. 如何將複合搜索條件轉換爲合適的ES查詢對象。對於A來講,關鍵就是搜索條件可靈活組合,用組合模式再合適不過;對於B來講,須要知道ES如何表示這些複合搜索。json
(A must B ) or ( C must D) 的 ES 表示爲:ide
{"query":{"bool":{"should":[{"bool":{"must":[{"term":{"shop_id":1}},{"terms":{"state":[1,2,3,4,5]}}]}},{"bool":{"must":[{"term":{"shop_id":1}},{"range":{"time":{"gt":1516550400}}},{"terms":{"tags":["IS_XXX"]}}]}}],"minimum_should_match":1}},"from":0,"size":10}
( A or C ) must ( B or D ) 的 ES 表示是:ui
bool:{must:[{bool:{should:[{A},{C}],minimum_should_match: 1}},{bool:{should:[{D},{B}], minimum_should_match: 1}}]}
組合模式的要點是:原子條件和複合條件具有相同的行爲接口,從而可以組合和疊加。this
STEP1: 首先定義 Condition 接口, 目前僅支持 與 和 或 操做,以及查詢對象轉換。google
/** * 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 實現code
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 = 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=" + 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 的實現基本相同,能夠寫在接口的默認方法裏:get
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()); } }
經過組合模式,清晰地實現了複合搜索條件的構建;經過工廠模式,建立條件對象更加簡潔友好。