原文出处:
琴水玉
在订单搜索中,有时需要实现复合搜索,比如 ( 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 = 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) { r