递归实现ElasticSearch 嵌套查询

0 概述

在实际工作中,我们会经常遇到一些and 与 or 以及非相关嵌套的查询方式,本文采用递归方式来实现这种相对复杂的嵌套查询。

1 实例分析

如下我们要查询用户表中name 为小红 且他的年龄等于10 或者大于15

select * from user where name='小红' and  (age=10 or  age >15)

or 查询写法实例

select * from user where objectType=13 and  (userId=1234 or  userType =2 )
query 写法
{
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "objectType": 13
                    }
                },
                {
                    "bool": {
                        "should": [
                            {
                                "bool": {
                                    "must": [
                                        {
                                            "term": {
                                                "userId": 1234
                                            }
                                        }
                                    ]
                                }
                            },
                            {
                                "bool": {
                                    "must": [
                                        {
                                            "term": {
                                                "userType": 2
                                            }
                                        }
                                    ]
                                }
                            }
                        ]
                    }
                }
            ]
        }
    }
}

//输出JSON

import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;


public class QueryBuilderJSON {
  

  public static void main(String[] args) {
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
    queryBuilder.must(QueryBuilders.termQuery("objectType", 13));

    final BoolQueryBuilder and = QueryBuilders.boolQuery();
    queryBuilder.must(and);
    final BoolQueryBuilder or1 = QueryBuilders.boolQuery();
    final BoolQueryBuilder or2 = QueryBuilders.boolQuery();
    and.should(or1);
    and.should(or2);
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("created").from(12);

    or1.must(QueryBuilders.termQuery("useType", 1));
    or2.filter(rangeQueryBuilder);

    System.out.print(searchSourceBuilder.query(queryBuilder).toString());
  }
}

2 实现

ElasticSearch 基本查询方式写法可以参考我之前写的ElasticSearch数据查询,这里实现在之前写的代码上进行了重构,以更好的支持这种复杂的嵌套查询。
定义查询条件(对于某一列进行的操作)

public class Condition {
  private String fieldName;//字段名称
  private Object fieldValue;//字段值
  private FieldOperator operator;//操作类型

  public String getFieldName() {
    return fieldName;
  }

  public void setFieldName(String fieldName) {
    this.fieldName = fieldName;
  }

  public Object getFieldValue() {
    return fieldValue;
  }

  public void setFieldValue(Object fieldValue) {
    this.fieldValue = fieldValue;
  }

  public FieldOperator getOperator() {
    return operator;
  }

  public void setOperator(FieldOperator operator) {
    this.operator = operator;
  }
}

嵌套查询,含有子查询的

public class HybridCondition extends Condition {

  private Connector connector;
  private List subConditions=new ArrayList<>();

  public HybridCondition(Connector connector) {
    this.connector = connector;
  }

  public Connector getConnector() {
    return connector;
  }

  public void setConnector(Connector connector) {
    this.connector = connector;
  }

  public List getSubConditions() {
    return subConditions;
  }
  //嵌套的查询
  public void setSubConditions(
      List subConditions) {
    this.subConditions = subConditions;
  }
  public HybridCondition addCondition(Condition condition) {
    //将此查询条件添加到查询添加列表中去,避免死循环
    if (condition == this) {
      return this;
    }
    subConditions.add(condition);
    return this;
  }
}

public enum Connector {
  AND("and"),
  OR("or"),
  EMPTY("");

  private String connName;

  Connector(String connName) {
    this.connName = connName;
  }

  public String getConnName() {
    return connName;
  }

  public static boolean isEmpty(Connector conn) {
    return conn == null || conn == EMPTY;
  }

}

递归实现这种嵌套查询

@Component
public class ElasticSearchQueryBuilder {

  @Autowired
  private FieldObjectHandlerRegistry registry;
  private final static int MAX_DEPTH=200;

  public ESSearchRequest buildQuery(QueryContext queryContext) {
    final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
    final String esIndex = queryContext.getIndex();
    //构建查询语法
    buildQuery(queryBuilder, queryContext.getFieldObjects());
    ESSearchRequest esSearchRequest = queryContext.getEsClient().buildingESSearchRequest()
        .setIndices(esIndex)
        .setTypes(queryContext.getType())
        .setSize(queryContext.getPageSize())
        .setFrom(queryContext.getOffset())
        .setQuery(queryBuilder);
    //构建排序
    setSearchRequestBuilderSort(esSearchRequest, queryContext);
    return esSearchRequest;
  }

  private void setSearchRequestBuilderSort(ESSearchRequest esSearchRequest,
      QueryContext queryContext) {
    if (queryContext.getSortRules() == null) {
      return;
    }
    for (SortRule sortRule : queryContext.getSortRules()) {
      if (ContentConstant.SORT_FIELD.contains(sortRule.getField())) {
        esSearchRequest.addSort(sortRule.getField(), sortOrder(sortRule.getSort()));
      }
    }
  }

  private SortOrder sortOrder(final String order) {
    return "asc".equalsIgnoreCase(order) ? SortOrder.ASC : SortOrder.DESC;
  }

  private void buildQuery(BoolQueryBuilder queryBuilder, List conditions) {
    if (CollectionUtils.isEmpty(conditions)) {
      return;
    }
    for (Condition object : conditions) {
      buildQueryBuilder(object,queryBuilder,0);
    }
  }

  private void buildQueryBuilder(Condition queryCondition,BoolQueryBuilder queryBuilder,int depth) {
    if (depth > MAX_DEPTH) {
      //因为此函数是嵌套调用的,防止使用不规范造成死循环,此处需要对嵌套深度进行判断
      throw new IllegalArgumentException("query condition loop depth is too big.");
    }
    if (queryCondition instanceof HybridCondition) {
      final BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
      HybridCondition hybridCondition = (HybridCondition) queryCondition;
      if (Connector.AND.equals(hybridCondition.getConnector())) {
        for (Condition condition : hybridCondition.getSubConditions()) {
          buildQueryBuilder(condition,boolQueryBuilder,depth+1);
        }
        queryBuilder.must(boolQueryBuilder);
      }else if (Connector.OR.equals(hybridCondition.getConnector())) {
        for (Condition condition : hybridCondition.getSubConditions()) {
          buildQueryBuilder(condition,boolQueryBuilder,depth+1);
        }
        queryBuilder.should(boolQueryBuilder);
      }
      return;
    }
    registry.getHandler(queryCondition.getOperator().getOperator()).
        buildQuerySyntaxForElasticSearch(queryBuilder, queryCondition);
  }


}

各个查询条件采用builder模式实现

public class ConditionBuilder {

  private List conditions;

  public ConditionBuilder() {
    conditions = new ArrayList<>();
  }

  public ConditionBuilder buildName(String name) {
    if (name != null) {
      conditions.add(buildCondition("name",name, FieldOperator.EQ));
    }
    return this;
  }
  
  public ConditionBuilder builderUserIds(List userIds) {
    if (CollectionUtils.isNotEmpty(userIds)) {
      conditions.add(buildCondition("userId", userIds, FieldOperator.IN));
    }
    return this;
  }
  
  //之间是或的关系
  public ConditionBuilder builderAge(Integer equalAge,Integer largeAge) {
    HybridCondition hybridCondition = new HybridCondition(Connector.AND);
    if (equalAge!=null) {
      HybridCondition temp = new HybridCondition(Connector.OR);
      temp.addCondition(buildCondition("age", equalAge, FieldOperator.EQ));
      hybridCondition.addCondition(temp);
    }

    if (largeAge!=null) {
      HybridCondition temp = new HybridCondition(Connector.OR);
      temp.addCondition(buildCondition("age", equalAge, FieldOperator.GE));
      hybridCondition.addCondition(temp);
    }
      if (!hybridCondition.getSubConditions().isEmpty()) {
      this.fieldObjects.add(hybridCondition);
    }
    return this;

  }
  

  public List getConditions() {
    return conditions;
  }

  private Condition buildCondition(String fieldName, Object value, FieldOperator operator) {
    Condition condition = new Condition();

    condition.setFieldName(fieldName);
    condition.setFieldValue(value);
    condition.setOperator(operator);
    return condition;
  }


}

你可能感兴趣的:(ElasticSearch)