一、前言
在大多数公司产线Elasticsearch还在6.X的时候,ES已经更新到7.10.1了,这更新速度。
目前我手上的项目也做到一套工具类兼容ES6.1到ES7.9+。我用的是原生的ES6.8.12的Java API来实现的工具类。目前功能上线几个月没有出过错。
工作中用ES查询大于插入,所以代码里到处都是拼接ES查询条件的语句,ES本身的Java API已经很强大了,我们利用BoolQueryBuilder和QueryBuilders可以组合很多查询条件。
但是如果查询条件多,代码就会显的非常长,有很多行,对ES老鸟可能非常熟悉的味道,但是对ES新鸟可能非常不舒服。所以我尝试着写ES查询条件的帮助类,刚写了一天,功能还比较简单。
二、工具类
package info.pigg.dict.common.builder;
import info.pigg.dict.common.constant.PiggCharConstant;
import info.pigg.dict.common.constant.PiggFieldConstant;
import static info.pigg.dict.common.constant.PiggRelationConstant.*;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Array;
import java.text.MessageFormat;
import java.util.*;
public class PiggEsQuery {
private int minimumShouldMatch = 1;
private List<BoolQueryBuilder> shoulds = new ArrayList<>();
private Map<String, Object> param = new HashMap<>();
public PiggEsQuery term(String field, Object value) {
String key = initKey(true, TERM, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery notTerm(String field, Object value) {
String key = initKey(false, TERM, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery terms(String field, Object value) {
String key = initKey(true, TERMS, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery notTerms(String field, Object value) {
String key = initKey(false, TERMS, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery ids(Object ids) {
String key = initKey(true, IDS, PiggFieldConstant.ID);
this.param.put(key, ids);
return this;
}
public PiggEsQuery notIds(Object ids) {
String key = initKey(false, IDS, PiggFieldConstant.ID);
this.param.put(key, ids);
return this;
}
public PiggEsQuery prefix(String field, Object value) {
String key = initKey(true, PREFIX, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery notPrefix(String field, Object value) {
String key = initKey(false, PREFIX, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery wildcard(String field, Object value) {
String key = initKey(true, WILDCARD, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery notWildcard(String field, Object value) {
String key = initKey(false, WILDCARD, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery exists(String field) {
String key = initKey(true, EXISTS, field);
this.param.put(key, field);
return this;
}
public PiggEsQuery notExists(String field) {
String key = initKey(false, EXISTS, field);
this.param.put(key, field);
return this;
}
public PiggEsQuery rangeGt(String field, Object value) {
String key = initKey(true, RANGE_GT, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery rangeGte(String field, Object value) {
String key = initKey(true, RANGE_GTE, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery rangeLt(String field, Object value) {
String key = initKey(true, RANGE_LT, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery rangeLte(String field, Object value) {
String key = initKey(true, RANGE_LTE, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery match(String field, Object value) {
String key = initKey(true, MATCH, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery matchPhrase(String field, Object value) {
String key = initKey(true, MATCH_PHRASE, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery matchPhrasePrefix(String field, Object value) {
String key = initKey(true, MATCH_PHRASE_PREFIX, field);
this.param.put(key, value);
return this;
}
public PiggEsQuery withParam(Map<String, Object> param) {
if (param != null && !param.isEmpty()) {
this.param.putAll(param);
}
return this;
}
public PiggEsQuery should(BoolQueryBuilder boolQueryBuilder) {
shoulds.add(boolQueryBuilder);
return this;
}
public PiggEsQuery minimumShouldMatch(int minimumShouldMatch) {
if (minimumShouldMatch > 0) {
this.minimumShouldMatch = minimumShouldMatch;
}
return this;
}
public BoolQueryBuilder getQuery() {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if ((this.param == null || param.isEmpty())
&& CollectionUtils.isEmpty(shoulds)
) {
return boolQueryBuilder;
}
this.param.forEach((k, v) -> {
String isOrNot = "";
String field = "";
String relation = "";
if (StringUtils.hasText(k) && k.contains(PiggCharConstant.COLON)) {
String[] split = k.split(PiggCharConstant.COLON);
if (!(split != null && split.length == 3)) {
//do nothing
}
isOrNot = split[0];
field = split[1];
relation = split[2];
if (StringUtils.hasText(isOrNot)
&& StringUtils.hasText(field)
&& StringUtils.hasText(relation)
) {
QueryBuilder queryBuilder = null;
switch (relation) {
case TERM: {
queryBuilder = QueryBuilders.termQuery(field, v);
break;
}
case TERMS: {
Collection thisValue = getCollectionFromValue(v);
if (thisValue != null && !thisValue.isEmpty()) {
queryBuilder = QueryBuilders.termsQuery(field, thisValue);
}
break;
}
case IDS: {
List<String> thisValueList = new ArrayList<>();
Collection thisValue = getCollectionFromValue(v);
if (!CollectionUtils.isEmpty(thisValue)) {
thisValue.stream().distinct().forEach(a -> thisValueList.add(a.toString()));
String[] ids = thisValueList.toArray(new String[thisValueList.size()]);
queryBuilder = QueryBuilders.idsQuery().addIds(ids);
}
break;
}
case PREFIX: {
queryBuilder = QueryBuilders.prefixQuery(field, v.toString());
break;
}
case WILDCARD: {
queryBuilder = QueryBuilders.wildcardQuery(field, "*" + v.toString() + "*");
break;
}
case EXISTS: {
queryBuilder = QueryBuilders.existsQuery(field);
break;
}
case RANGE_GT: {
queryBuilder = QueryBuilders.rangeQuery(field).gt(v);
break;
}
case RANGE_GTE: {
queryBuilder = QueryBuilders.rangeQuery(field).gte(v);
break;
}
case RANGE_LT: {
queryBuilder = QueryBuilders.rangeQuery(field).lt(v);
break;
}
case RANGE_LTE: {
queryBuilder = QueryBuilders.rangeQuery(field).lte(v);
break;
}
case MATCH: {
queryBuilder = QueryBuilders.matchQuery(field, v);
break;
}
case MATCH_PHRASE: {
queryBuilder = QueryBuilders.matchPhraseQuery(field, v);
break;
}
case MATCH_PHRASE_PREFIX: {
queryBuilder = QueryBuilders.matchPhrasePrefixQuery(field, v);
break;
}
}
if (queryBuilder != null) {
if (isOrNot.equals(IS)) {
boolQueryBuilder.filter(queryBuilder);
} else {
boolQueryBuilder.mustNot(queryBuilder);
}
}
}
}
});
shoulds.forEach(thisShould -> {
boolQueryBuilder.should(thisShould);
});
if (!CollectionUtils.isEmpty(shoulds)) {
boolQueryBuilder.minimumShouldMatch(minimumShouldMatch);
}
return boolQueryBuilder;
}
private String initKey(boolean isOrNot, String relation, String field) {
return MessageFormat.format("{0}:{1}:{2}",
isOrNot ? IS : NOT,
field,
relation);
}
private static Collection getCollectionFromValue(Object v) {
Collection thisValue = null;
if (v instanceof List) {
thisValue = (List) v;
} else if (v instanceof Set) {
thisValue = (Set) v;
} else if (v instanceof Array) {
thisValue = Arrays.asList((Array) v);
} else if (v instanceof String) {
thisValue = Arrays.asList(((String) v).split(","));
}
return thisValue;
}
}
三、测试
//【1】 如果根据条件拼接可以这样new一个PiggEsQuery对象
PiggEsQuery esQuery = new PiggEsQuery();
if (true){
esQuery.term("code", "1");
}
//【2】 直接在new PiggEsQuery()后添加查询条件
BoolQueryBuilder builder = new PiggEsQuery()
.term("code", "1")
.terms("pets", Arrays.asList("dog","cat","cow"))
.notTerm("state", 0)
.notTerms("state", 200)
.prefix("name", "hehe")
.notPrefix("name2", "hehe")
.exists("age")
.ids(Arrays.asList("1","2","3"))
.should(
new PiggEsQuery()
.term("state", "2")
.getQuery()
)
.should(
new PiggEsQuery()
.term("code", "2")
.getQuery()
).minimumShouldMatch(2)
.getQuery();
System.out.println(builder.toString());
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withFilter(builder).build();
SearchHits<PiggDictType> dictTypes = operations.search(searchQuery, PiggDictType.class);
System.out.println(dictTypes.getMaxScore());
四、总结
目前还是感觉Spring Data Elasticsearch使用最方便,功能最强,也紧更ES的最新版本,再项目中可以使用Spring Data Elasticsearch4.1.2,它支持到ES7.9.3,再结合自己写的工具类,可以应对大多数查询语句。对于一些特别复杂的聚合查询语句,得用原生ES Java API处理好。