經過ES進行查詢,若是須要新增查詢條件,則每次都須要進行硬編碼,而後實現對應的查詢功能。這樣不只開發工做量大,並且若是有多個不一樣的索引對象須要進行一樣的查詢,則須要開發屢次,代碼複用性不高。php
想要解決這個問題,那麼就須要一種可以模塊化、配置化的解決方案。java
經過配置參數的方式來配置參數映射、查詢方式等,代碼讀取配置文件,根據配置文件構建查詢語句。git
優勢:可配置化,新增查詢字段基本不須要改動代碼,除非增長新的查詢方式。github
缺點:配置文件太多、太複雜,配置文件配置錯誤將會致使整個查詢不可用。spring
和方案一相似,經過註解的方式來配置參數映射等,而後讀取註解,根據註解構建查詢語句。apache
優勢:可配置化,代碼清晰、明確,可讀性高。json
缺點:每次新增查詢字段都須要改動代碼(在指定字段增長註解)app
目前只有這兩種能夠說大同小異的解決思路,不過不喜歡配置文件太多,因此我就選擇了第二種思路。dom
首先須要建立一個查詢方式的枚舉類,來區分有哪些查詢方式,目前只實現了一些經常使用的查詢類型。curl
源碼以下:
package com.lifengdi.search.enums; /** * @author 李鋒鏑 * @date Create at 19:17 2019/8/27 */ public enum QueryTypeEnum { /** * 等於 */ EQUAL, /** * 忽略大小寫相等 */ EQUAL_IGNORE_CASE, /** * 範圍 */ RANGE, /** * in */ IN, IGNORE, /** * 搜索 */ FULLTEXT, /** * 匹配 和q搜索區分開 */ MATCH, /** * 模糊查詢 */ FUZZY, /** * and */ AND, /** * 多個查詢字段匹配上一個即符合條件 */ SHOULD, /** * 前綴查詢 */ PREFIX, ; }
而後開始自定義註解,經過註解來定義字段的查詢方式、映射字段、嵌套查詢的path
以及其餘的一些參數;經過@Repeatable
註解來聲明這是一個重複註解類。
源碼以下:
package com.lifengdi.search.annotation; import com.lifengdi.search.enums.QueryTypeEnum; import java.lang.annotation.*; /** * 定義查詢字段的查詢方式 * @author 李鋒鏑 * @date Create at 19:07 2019/8/27 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) @Repeatable(DefinitionQueryRepeatable.class) public @interface DefinitionQuery { /** * 查詢參數 * * @return 查詢字段 */ String key() default ""; /** * 查詢類型 see{@link QueryTypeEnum} * * @return QueryTypeEnum */ QueryTypeEnum type() default QueryTypeEnum.EQUAL; /** * 範圍查詢 from後綴 * * @return from後綴 */ String fromSuffix() default "From"; /** * 範圍查詢 to後綴 * * @return to後綴 */ String toSuffix() default "To"; /** * 多個字段分隔符 * * @return 分隔符 */ String separator() default ","; /** * 指定對象的哪一個字段將應用於查詢映射 * 例如: * 同一個文檔下有多個User對象,對象名分別爲createdUser、updatedUser,該User對象的屬性有name等字段, * 若是要根據查詢createdUser的name來進行查詢, * 則能夠這樣定義DefinitionQuery:queryField = cName, mapped = createdUser.name * * @return 映射的實體的字段路徑 */ String mapped() default ""; /** * 嵌套查詢的path * * @return path */ String nestedPath() default ""; }
同時定義@DefinitionQueryRepeatable
註解,聲明這是上邊註解的容器註解類,源碼以下:
package com.lifengdi.search.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author 李鋒鏑 * @date Create at 19:11 2019/8/27 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) public @interface DefinitionQueryRepeatable { DefinitionQuery[] value(); }
如何使用註解?
源碼以下:
package com.lifengdi.document; import com.lifengdi.document.store.*; import com.lifengdi.search.annotation.DefinitionQuery; import com.lifengdi.search.enums.QueryTypeEnum; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import java.util.List; /** * 門店Document * * @author 李鋒鏑 * @date Create at 19:31 2019/8/22 */ @Document(indexName = "store", type = "base") @Data @DefinitionQuery(key = "page", type = QueryTypeEnum.IGNORE) @DefinitionQuery(key = "size", type = QueryTypeEnum.IGNORE) @DefinitionQuery(key = "q", type = QueryTypeEnum.FULLTEXT) public class StoreDocument { @Id @DefinitionQuery(type = QueryTypeEnum.IN) @DefinitionQuery(key = "id", type = QueryTypeEnum.IN) @Field(type = FieldType.Keyword) private String id; /** * 基礎信息 */ @Field(type = FieldType.Object) private StoreBaseInfo baseInfo; /** * 標籤 */ @Field(type = FieldType.Nested) @DefinitionQuery(key = "tagCode", mapped = "tags.key", type = QueryTypeEnum.IN) @DefinitionQuery(key = "tagValue", mapped = "tags.value", type = QueryTypeEnum.AND) @DefinitionQuery(key = "_tagValue", mapped = "tags.value", type = QueryTypeEnum.IN) private List<StoreTags> tags; }
package com.lifengdi.document.store; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.lifengdi.search.annotation.DefinitionQuery; import com.lifengdi.search.enums.QueryTypeEnum; import com.lifengdi.serializer.JodaDateTimeDeserializer; import com.lifengdi.serializer.JodaDateTimeSerializer; import lombok.Data; import org.joda.time.DateTime; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; /** * 門店基礎信息 * */ @Data public class StoreBaseInfo { /** * 門店id */ @Field(type = FieldType.Keyword) private String storeId; /** * 門店名稱 */ @Field(type = FieldType.Text, analyzer = "ik_smart") @DefinitionQuery(type = QueryTypeEnum.FUZZY) @DefinitionQuery(key = "name", type = QueryTypeEnum.SHOULD) private String storeName; /** * 門店簡稱 */ @Field(type = FieldType.Text, analyzer = "ik_smart") private String shortName; /** * 門店簡介 */ @Field(type = FieldType.Text, analyzer = "ik_smart") private String profile; /** * 門店屬性 */ @Field(type = FieldType.Integer) private Integer property; /** * 門店類型 */ @Field(type = FieldType.Integer) private Integer type; /** * 詳細地址 */ @Field(type = FieldType.Text, analyzer = "ik_smart") private String address; /** * 所在城市 */ @Field(type = FieldType.Keyword) @DefinitionQuery(type = QueryTypeEnum.IN) private String cityCode; /** * 城市名稱 */ @Field(type = FieldType.Keyword) private String cityName; /** * 所在省份 */ @Field(type = FieldType.Keyword) private String provinceCode; /** * 省份名稱 */ @Field(type = FieldType.Keyword) private String provinceName; /** * 所在地區 */ @Field(type = FieldType.Keyword) private String regionCode; /** * 地區名稱 */ @Field(type = FieldType.Keyword) private String regionName; /** * 所屬市場id */ @Field(type = FieldType.Long) @DefinitionQuery(type = QueryTypeEnum.IN) private Integer marketId; /** * 所屬市場key */ @Field(type = FieldType.Keyword) @DefinitionQuery(type = QueryTypeEnum.IN) private String marketKey; /** * 所屬市場名稱 */ @Field(type = FieldType.Keyword) private String marketName; /** * 攤位號 */ @Field(type = FieldType.Text) private String marketStall; /** * 門店狀態 */ @Field(type = FieldType.Keyword) @DefinitionQuery(key = "storeStatus", type = QueryTypeEnum.IN) @DefinitionQuery(key = "_storeStatus", type = QueryTypeEnum.IN) private String status; /** * 刪除標示 */ @Field(type = FieldType.Integer) @DefinitionQuery(key = "deleted") private Integer deleted; /** * 建立時間 */ @Field(type = FieldType.Date) @JsonDeserialize(using = JodaDateTimeDeserializer.class) @JsonSerialize(using = JodaDateTimeSerializer.class) @DefinitionQuery(type = QueryTypeEnum.RANGE) public DateTime createdTime; /** * 建立人id */ @Field(type = FieldType.Keyword) @DefinitionQuery private String createdUserId; /** * 建立人名稱 */ @Field(type = FieldType.Keyword) private String createdUserName; /** * 修改時間 */ @Field(type = FieldType.Date) @JsonDeserialize(using = JodaDateTimeDeserializer.class) @JsonSerialize(using = JodaDateTimeSerializer.class) private DateTime updatedTime; /** * 修改人ID */ @Field(type = FieldType.Keyword) private String updatedUserId; /** * 修改人姓名 */ @Field(type = FieldType.Keyword) private String updatedUserName; /** * 業務類型 */ @Field(type = FieldType.Long) private Long businessType; /** * storeNo */ @Field(type = FieldType.Keyword) @DefinitionQuery(type = QueryTypeEnum.SHOULD) private String storeNo; }
package com.lifengdi.document.store; import lombok.Data; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; /** * @author 李鋒鏑 * @date Create at 18:15 2019/2/18 */ @Data public class StoreTags { @Field(type = FieldType.Keyword) private String key; @Field(type = FieldType.Keyword) private String value; private String showName; }
解釋一下上面的源碼:
@DefinitionQuery(key = "tagCode", mapped = "tags.key", type = QueryTypeEnum.IN)
這行代碼的意思是指定一個查詢參數tagCode
,該參數映射到tags
的key
字段,查詢方式爲IN
,調用接口入參查詢的時候只須要入參tagCode={tagCode}
便可。
請求體:
curl -X POST \ http://localhost:8080/search/store/search \ -H 'Content-Type: application/json' \ -d '{ "tagCode": "1" }'
構建的ES查詢語句:
{ "query": { "bool": { "must": [ { "nested": { "query": { "bool": { "must": [ { "terms": { "tags.key": [ "1" ], "boost": 1 } } ], "adjust_pure_negative": true, "boost": 1 } }, "path": "tags", "ignore_unmapped": false, "score_mode": "none", "boost": 1 } } ], "adjust_pure_negative": true, "boost": 1 } } }
繼續說源碼
使用了註解,就須要將註解中的參數提取出來,並生成映射數據,目前實現的是將全部的字段全都封裝到Map中,查詢的時候遍歷取值。
源碼以下:
package com.lifengdi.search.mapping; import com.lifengdi.SearchApplication; import com.lifengdi.model.FieldDefinition; import com.lifengdi.model.Key; import com.lifengdi.search.annotation.DefinitionQuery; import com.lifengdi.search.annotation.DefinitionQueryRepeatable; import org.apache.commons.lang3.StringUtils; import org.springframework.data.elasticsearch.annotations.FieldType; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * @author 李鋒鏑 * @date Create at 09:15 2019/8/28 */ public class KeyMapping { // 啓動類所在包 private static final String BOOTSTRAP_PATH = SearchApplication.class.getPackage().getName(); /** * 字段映射 * @param clazz Class * @return Map */ public static Map<Key, FieldDefinition> mapping(Class clazz) { Map<Key, FieldDefinition> mappings = mapping(clazz.getDeclaredFields(), ""); mappings.putAll(typeMapping(clazz)); return mappings; } /** * 字段映射 * * @param fields 字段 * @param parentField 父級字段名 * @return Map */ public static Map<Key, FieldDefinition> mapping(Field[] fields, String parentField) { Map<Key, FieldDefinition> mappings = new HashMap<>(); for (Field field : fields) { org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = field.getAnnotation (org.springframework.data.elasticsearch.annotations.Field.class); String nestedPath = null; if (Objects.nonNull(fieldAnnotation) && FieldType.Nested.equals(fieldAnnotation.type())) { nestedPath = parentField + field.getName(); } DefinitionQuery[] definitionQueries = field.getAnnotationsByType(DefinitionQuery.class); // 若是屬性非BOOTSTRAP_PATH包下的類,說明屬性爲基礎字段 即跳出循環,不然遞歸調用mapping if (!field.getType().getName().startsWith(BOOTSTRAP_PATH)) { for (DefinitionQuery definitionQuery : definitionQueries) { buildMapping(parentField, mappings, field, nestedPath, definitionQuery); } } else { for (DefinitionQuery definitionQuery : definitionQueries) { if (StringUtils.isNotBlank(definitionQuery.mapped())) { buildMapping(parentField, mappings, field, nestedPath, definitionQuery); } } mappings.putAll(mapping(field.getType().getDeclaredFields(), parentField + field.getName() + ".")); } } return mappings; } /** * 構建mapping * @param parentField 父級字段名 * @param mappings mapping * @param field 字段 * @param nestedPath 默認嵌套路徑 * @param definitionQuery 字段定義 */ private static void buildMapping(String parentField, Map<Key, FieldDefinition> mappings, Field field, String nestedPath, DefinitionQuery definitionQuery) { FieldDefinition fieldDefinition; nestedPath = StringUtils.isNotBlank(definitionQuery.nestedPath()) ? definitionQuery.nestedPath() : nestedPath; String key = StringUtils.isBlank(definitionQuery.key()) ? field.getName() : definitionQuery.key(); String filedName = StringUtils.isBlank(definitionQuery.mapped()) ? field.getName() : definitionQuery.mapped(); switch (definitionQuery.type()) { case RANGE: buildRange(parentField, mappings, definitionQuery, key, filedName); break; default: fieldDefinition = FieldDefinition.builder() .key(key) .queryField(parentField + filedName) .queryType(definitionQuery.type()) .separator(definitionQuery.separator()) .nestedPath(nestedPath) .build(); mappings.put(new Key(key), fieldDefinition); break; } } /** * 構建範圍查詢 * @param parentField 父級字段名 * @param mappings mapping * @param definitionQuery 字段定義 * @param key 入參查詢字段 * @param filedName 索引文檔中字段名 */ private static void buildRange(String parentField, Map<Key, FieldDefinition> mappings, DefinitionQuery definitionQuery, String key, String filedName) { FieldDefinition fieldDefinition; String queryField = parentField + filedName; String rangeKeyFrom = key + definitionQuery.fromSuffix(); String rangeKeyTo = key + definitionQuery.toSuffix(); fieldDefinition = FieldDefinition.builder() .key(rangeKeyFrom) .queryField(queryField) .queryType(definitionQuery.type()) .fromSuffix(definitionQuery.fromSuffix()) .toSuffix(definitionQuery.toSuffix()) .build(); mappings.put(new Key(rangeKeyFrom), fieldDefinition); fieldDefinition = FieldDefinition.builder() .key(rangeKeyTo) .queryField(queryField) .queryType(definitionQuery.type()) .fromSuffix(definitionQuery.fromSuffix()) .toSuffix(definitionQuery.toSuffix()) .build(); mappings.put(new Key(rangeKeyTo), fieldDefinition); } /** * 對象映射 * @param clazz document * @return Map */ public static Map<Key, FieldDefinition> typeMapping(Class clazz) { DefinitionQueryRepeatable repeatable = (DefinitionQueryRepeatable) clazz.getAnnotation(DefinitionQueryRepeatable.class); Map<Key, FieldDefinition> mappings = new HashMap<>(); for (DefinitionQuery definitionQuery : repeatable.value()) { String key = definitionQuery.key(); switch (definitionQuery.type()) { case RANGE: buildRange("", mappings, definitionQuery, key, definitionQuery.mapped()); break; default: FieldDefinition fieldDefinition = FieldDefinition.builder() .key(key) .queryField(key) .queryType(definitionQuery.type()) .separator(definitionQuery.separator()) .nestedPath(definitionQuery.nestedPath()) .build(); mappings.put(new Key(key), fieldDefinition); break; } } return mappings; } }
定義Key
對象,解決重複字段在Map中會覆蓋的問題:
package com.lifengdi.model; /** * @author 李鋒鏑 * @date Create at 09:25 2019/8/28 */ public class Key { private String key; public Key(String key) { this.key = key; } @Override public String toString() { return key; } public String getKey() { return key; } }
接下來重頭戲來了,根據查詢類型的枚舉值,來封裝對應的ES查詢語句,若是須要新增查詢類型,則新增枚舉,而後新增對應的實現代碼;同時也增長了對排序的支持,不過排序字段須要傳完整的路徑,暫時還未實現經過mapping映射來進行對應的排序。
源碼以下:
package com.lifengdi.search; import com.lifengdi.model.FieldDefinition; import com.lifengdi.model.Key; import com.lifengdi.search.enums.QueryTypeEnum; import org.apache.commons.lang3.StringUtils; import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.index.query.*; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.SearchQuery; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import static com.lifengdi.global.Global.*; /** * @author 李鋒鏑 * @date Create at 16:49 2019/8/27 */ @Service public class SearchService { @Resource private ElasticsearchTemplate elasticsearchTemplate; /** * 通用查詢 * @param params 查詢入參 * @param indexName 索引名稱 * @param type 索引類型 * @param defaultSort 默認排序 * @param keyMappings 字段映射 * @param keyMappingsMap 索引對應字段映射 * @return Page */ protected Page<Map> commonSearch(Map<String, String> params, String indexName, String type, String defaultSort, Map<Key, FieldDefinition> keyMappings, Map<String, Map<Key, FieldDefinition>> keyMappingsMap) { SearchQuery searchQuery = buildSearchQuery(params, indexName, type, defaultSort, keyMappings, keyMappingsMap); return elasticsearchTemplate.queryForPage(searchQuery, Map.class); } /** * 數量通用查詢 * @param params 查詢入參 * @param indexName 索引名稱 * @param type 索引類型 * @param defaultSort 默認排序 * @param keyMappings 字段映射 * @param keyMappingsMap 索引對應字段映射 * @return Page */ protected long count(Map<String, String> params, String indexName, String type, String defaultSort, Map<Key, FieldDefinition> keyMappings, Map<String, Map<Key, FieldDefinition>> keyMappingsMap) { SearchQuery searchQuery = buildSearchQuery(params, indexName, type, defaultSort, keyMappings, keyMappingsMap); return elasticsearchTemplate.count(searchQuery); } /** * 根據ID獲取索引 * @param id ID * @param indexName 索引名 * @param type 索引類型 * @return 索引 */ protected Map get(String id, String indexName, String type) { return elasticsearchTemplate.getClient() .prepareGet(indexName, type, id) .execute() .actionGet() .getSourceAsMap(); } /** * 根據定義的查詢字段封裝查詢語句 * @param params 查詢入參 * @param indexName 索引名稱 * @param type 索引類型 * @param defaultSort 默認排序 * @param keyMappings 字段映射 * @param keyMappingsMap 索引對應字段映射 * @return SearchQuery */ private SearchQuery buildSearchQuery(Map<String, String> params, String indexName, String type, String defaultSort, Map<Key, FieldDefinition> keyMappings, Map<String, Map<Key, FieldDefinition>> keyMappingsMap) { NativeSearchQueryBuilder searchQueryBuilder = buildSearchField(params, indexName, type, keyMappings, keyMappingsMap); String sortFiled = params.getOrDefault(SORT, defaultSort); if (StringUtils.isNotBlank(sortFiled)) { String[] sorts = sortFiled.split(SPLIT_FLAG_COMMA); handleQuerySort(searchQueryBuilder, sorts); } return searchQueryBuilder.build(); } /** * 根據定義的查詢字段封裝查詢語句 * @param params 查詢入參 * @param indexName 索引名稱 * @param type 索引類型 * @param keyMappings 字段映射 * @param keyMappingsMap 索引對應字段映射 * @return NativeSearchQueryBuilder */ private NativeSearchQueryBuilder buildSearchField(Map<String, String> params, String indexName, String type, Map<Key, FieldDefinition> keyMappings, Map<String, Map<Key, FieldDefinition>> keyMappingsMap) { int page = Integer.parseInt(params.getOrDefault(PAGE, "0")); int size = Integer.parseInt(params.getOrDefault(SIZE, "10")); AtomicBoolean matchSearch = new AtomicBoolean(false); String q = params.get(Q); String missingFields = params.get(MISSING); String existsFields = params.get(EXISTS); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); BoolQueryBuilder boolFilterBuilder = QueryBuilders.boolQuery(); Map<String, BoolQueryBuilder> nestedMustMap = new HashMap<>(); Map<String, BoolQueryBuilder> nestedMustNotMap = new HashMap<>(); List<String> fullTextFieldList = new ArrayList<>(); // 查詢條件構建器 NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder() .withIndices(params.getOrDefault(INDEX_NAME, indexName)) .withTypes(params.getOrDefault(INDEX_TYPE, type)) .withPageable(PageRequest.of(page, size)); String fields = params.get(FIELDS); if (Objects.nonNull(fields)) { searchQueryBuilder.withFields(fields.split(SPLIT_FLAG_COMMA)); } keyMappingsMap.getOrDefault(params.getOrDefault(INDEX_NAME, indexName), keyMappings) .entrySet() .stream() .filter(m -> m.getValue().getQueryType() == QueryTypeEnum.FULLTEXT || m.getValue().getQueryType() != QueryTypeEnum.IGNORE && params.get(m.getKey().toString()) != null) .forEach(m -> { String k = m.getKey().toString(); FieldDefinition v = m.getValue(); String queryValue = params.get(k); QueryTypeEnum queryType = v.getQueryType(); String queryName = v.getQueryField(); String nestedPath = v.getNestedPath(); BoolQueryBuilder nestedMustBoolQuery = null; BoolQueryBuilder nestedMustNotBoolQuery = null; boolean nested = false; if (StringUtils.isNotBlank(nestedPath)) { nested = true; if (nestedMustMap.containsKey(nestedPath)) { nestedMustBoolQuery = nestedMustMap.get(nestedPath); } else { nestedMustBoolQuery = QueryBuilders.boolQuery(); } if (nestedMustNotMap.containsKey(nestedPath)) { nestedMustNotBoolQuery = nestedMustNotMap.get(nestedPath); } else { nestedMustNotBoolQuery = QueryBuilders.boolQuery(); } } switch (queryType) { case RANGE: RangeQueryBuilder rangeQueryBuilder = new RangeQueryBuilder(queryName); if (k.endsWith(v.getFromSuffix())) { rangeQueryBuilder.from(queryValue); } else { rangeQueryBuilder.to(queryValue); } boolFilterBuilder.must(rangeQueryBuilder); break; case FUZZY: if (nested) { if (k.startsWith(NON_FLAG)) { nestedMustBoolQuery.mustNot(QueryBuilders.wildcardQuery(queryName, queryValue)); } else { nestedMustBoolQuery.filter(QueryBuilders.wildcardQuery(queryName, StringUtils.wrapIfMissing(queryValue, WILDCARD))); } } else { if (k.startsWith(NON_FLAG)) { boolFilterBuilder.mustNot(QueryBuilders.wildcardQuery(queryName, queryValue)); } else { boolFilterBuilder.filter(QueryBuilders.wildcardQuery(queryName, StringUtils.wrapIfMissing(queryValue, WILDCARD))); } } break; case PREFIX: boolFilterBuilder.filter(QueryBuilders.prefixQuery(queryName, queryValue)); break; case AND: if (nested) { for (String and : queryValue.split(v.getSeparator())) { nestedMustBoolQuery.must(QueryBuilders.termQuery(queryName, and)); } } else { for (String and : queryValue.split(v.getSeparator())) { boolFilterBuilder.must(QueryBuilders.termQuery(queryName, and)); } } break; case IN: String inQuerySeparator = v.getSeparator(); if (nested) { buildIn(k, queryValue, queryName, nestedMustBoolQuery, inQuerySeparator, nestedMustNotBoolQuery); } else { buildIn(k, queryValue, queryName, boolFilterBuilder, inQuerySeparator); } break; case SHOULD: boolFilterBuilder.should(QueryBuilders.wildcardQuery(queryName, StringUtils.wrapIfMissing(queryValue, WILDCARD))); break; case FULLTEXT: if (!Q.equalsIgnoreCase(queryName)) { fullTextFieldList.add(queryName); } break; case MATCH: boolQueryBuilder.must(QueryBuilders.matchQuery(queryName, queryValue)); matchSearch.set(true); break; case EQUAL_IGNORE_CASE: boolFilterBuilder.must(QueryBuilders.termQuery(queryName, queryValue.toLowerCase())); break; default: boolFilterBuilder.must(QueryBuilders.termQuery(queryName, queryValue)); break; } if (nested) { if (nestedMustBoolQuery.hasClauses()) { nestedMustMap.put(nestedPath, nestedMustBoolQuery); } if (nestedMustNotBoolQuery.hasClauses()) { nestedMustNotMap.put(nestedPath, nestedMustNotBoolQuery); } } }); if (StringUtils.isNotBlank(q)) { MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(q); fullTextFieldList.forEach(multiMatchQueryBuilder::field); boolQueryBuilder.should(multiMatchQueryBuilder); } if (StringUtils.isNotBlank(q) || matchSearch.get()) { searchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); } if (StringUtils.isNotBlank(missingFields)) { for (String miss : missingFields.split(SPLIT_FLAG_COMMA)) { boolFilterBuilder.mustNot(QueryBuilders.existsQuery(miss)); } } if (StringUtils.isNotBlank(existsFields)) { for (String exists : existsFields.split(SPLIT_FLAG_COMMA)) { boolFilterBuilder.must(QueryBuilders.existsQuery(exists)); } } if (!CollectionUtils.isEmpty(nestedMustMap)) { for (String key : nestedMustMap.keySet()) { if (StringUtils.isBlank(key)) { continue; } boolFilterBuilder.must(QueryBuilders.nestedQuery(key, nestedMustMap.get(key), ScoreMode.None)); } } if (!CollectionUtils.isEmpty(nestedMustNotMap)) { for (String key : nestedMustNotMap.keySet()) { if (StringUtils.isBlank(key)) { continue; } boolFilterBuilder.mustNot(QueryBuilders.nestedQuery(key, nestedMustNotMap.get(key), ScoreMode.None)); } } searchQueryBuilder.withFilter(boolFilterBuilder); searchQueryBuilder.withQuery(boolQueryBuilder); return searchQueryBuilder; } private void buildIn(String k, String queryValue, String queryName, BoolQueryBuilder boolQuery, String separator) { buildIn(k, queryValue, queryName, boolQuery, separator, null); } private void buildIn(String k, String queryValue, String queryName, BoolQueryBuilder boolQuery, String separator, BoolQueryBuilder nestedMustNotBoolQuery) { if (queryValue.contains(separator)) { if (k.startsWith(NON_FLAG)) { if (Objects.nonNull(nestedMustNotBoolQuery)) { nestedMustNotBoolQuery.must(QueryBuilders.termsQuery(queryName, Arrays.asList(queryValue.split(separator)))); } else { boolQuery.mustNot(QueryBuilders.termsQuery(queryName, Arrays.asList(queryValue.split(separator)))); } } else { boolQuery.must(QueryBuilders.termsQuery(queryName, Arrays.asList(queryValue.split(separator)))); } } else { if (k.startsWith(NON_FLAG)) { if (Objects.nonNull(nestedMustNotBoolQuery)) { nestedMustNotBoolQuery.must(QueryBuilders.termsQuery(queryName, queryValue)); } else { boolQuery.mustNot(QueryBuilders.termsQuery(queryName, queryValue)); } } else { boolQuery.must(QueryBuilders.termsQuery(queryName, queryValue)); } } } /** * 處理排序 * * @param sorts 排序字段 */ private void handleQuerySort(NativeSearchQueryBuilder searchQueryBuilder, String[] sorts) { for (String sort : sorts) { sortBuilder(searchQueryBuilder, sort); } } private void sortBuilder(NativeSearchQueryBuilder searchQueryBuilder, String sort) { switch (sort.charAt(0)) { case '-': // 字段前有-: 倒序排序 searchQueryBuilder.withSort(SortBuilders.fieldSort(sort.substring(1)).order(SortOrder.DESC)); break; case '+': // 字段前有+: 正序排序 searchQueryBuilder.withSort(SortBuilders.fieldSort(sort.substring(1)).order(SortOrder.ASC)); break; default: searchQueryBuilder.withSort(SortBuilders.fieldSort(sort.trim()).order(SortOrder.ASC)); break; } } /** * 獲取一個符合查詢條件的數據 * @param filterBuilder 查詢條件 * @param indexName 索引名 * @param type 索引類型 * @return Map */ protected Map<String, Object> getOne(TermQueryBuilder filterBuilder, String indexName, String type) { final SearchResponse searchResponse = elasticsearchTemplate.getClient() .prepareSearch(indexName) .setTypes(type) .setPostFilter(filterBuilder) .setSize(1) .get(); final long total = searchResponse.getHits().getTotalHits(); if (total > 0) { return searchResponse.getHits().getAt(0).getSourceAsMap(); } return null; } }
好了關鍵的代碼就這麼些,具體源碼能夠在個人github上查看。
Git項目地址:search
若是以爲有幫助的話,請幫忙點贊、點星小小的支持一下~
謝謝~~