天天看點

遞歸實作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<Condition> 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<Condition> getSubConditions() {
    return subConditions;
  }
  //嵌套的查詢
  public void setSubConditions(
      List<Condition> 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<Condition> 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<Condition> 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<Long> 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<Condition> 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;
  }


}

           

繼續閱讀