elasticsearch通用工具類

奕鋒部落格發表於2022-03-11

  這幾天寫了一個關於es的工具類,主要封裝了業務中常用es的常用方法。

  本文中使用到的elasticsearch版本6.7,但實際上也支援es7.x以上版本,因為主要是對springboot提供的:ElasticsearchRestTemplate 提供的API做的二次封裝。目的是:讓不懂es的開發人員新手也能輕鬆上手。

一、概述

整個工程分為es-api與es-server。

es-api為對外公共jar,包含了es對映實體;

es-server包含了具體的es工具類與Repository介面(下文會提到)。並通過necos配置中心統一管理es配置引數。

外部業務模組可引入es-api jar maven依賴,由Jar提供的入口,通過httpClient或feign呼叫(springcloud分散式專案)到es-server服務上的es工具類,得到需要的資料。

二、使用

這裡僅以springcloud分散式專案簡單為例

業務方使用者服務user 引入es-api maven

/**
 * @author: shf
 * description: es-server feign介面
 */
public interface EsServerClient {
    @PostMapping(value = "/queryList", produces = {"application/json"})
    public <T> List<T> queryList(@RequestBody T t);
}

在user服務中建立feignClient繼承自es-api中的EsServerClient

@FeignClient(contextId = "esFeignClient", name = "es-server")
public interface EsFeignClient extends EsServerClient {
}

在user服務的程式碼中即可呼叫

@AutoWired
public EsFeignClient esFeignClient;
 
 
public void test() {
   //.......如業務方Dto與es對映實體轉換 等省略
  //....
  EmployeeEs employee = new EmployeeEs();
  List queryList = Stream.of(employee.new QueryRelation<String>("張三", EntityEs.SHOULD, 5F), employee.new QueryRelation<String>("李四", EntityEs.SHOULD, 20F)).collect(Collectors.toList());
  employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserName, queryList).put(EmployeeEs::getUserAge, employee.new RangeRelation(20, EntityEs.GTE, null, null, EntityEs.MUST)));
  //排序查詢
  employee.setOrderMap(new EsMapUtil().put(EmployeeEs::getUserId, SortOrder.DESC));
  List<EmployeeEs> employeeEs = esFeignClient.queryList(employee);
  //.....employeeEs與業務方Dto轉換
}

三、具體實現

es-api 引入es依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>6.7.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.7.0</version>
</dependency>

排除後重新引入對應的Es版本6.7,避免因版本不一致導致的一些坑。

es-server 服務 application.yml配置es

spring:
 elasticsearch:
  rest:
    #ES的連線地址,多個地址用逗號分隔
    uris: localhost:9200
    username: kibana
    password: pass
    #連線超時時間
    connection-timeout: 1000
    #讀取超時時間
    read-timeout: 1000

一、對映實體

1、與ES mapping結構對應的對映實體:EmployeeEs

說明:

①設定的欄位與該es對應的該索引完全對應,不存在多餘欄位。

②專案中引入了spring-boot-starter-data-elasticsearch,所以可直接使用註解形式設定索引資訊與mapping結構資訊,詳見示例

③@JsonIgnoreProperties({"orderMap","pageNumber","pageSize","highlightFields","preTags","postTags","fieldQueryMap","scrollId","aggregationMap","multiLayerQueryList"})

    作用:在儲存文件到es時忽略父類EntityEs中的功能性欄位,下文會提到

④@EsRepository(EmployeeEsRepository.class)

    作用:通過註解過去該對映對應的Repository介面

elasticsearch通用工具類
/**
 * 員工物件
 * <p>
 * 註解:@Document用來宣告Java物件與ElasticSearch索引的關係 indexName 索引名稱 type 索引型別 shards 主分割槽數量,預設5
 * replicas 副本分割槽數量,預設1 createIndex 索引不存在時,是否自動建立索引,預設true
 */
@Setter
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EsRepository(EmployeeEsRepository.class)
@JsonIgnoreProperties({"orderMap","pageNumber","pageSize","highlightFields","preTags","postTags","fieldQueryMap","scrollId","aggregationMap","multiLayerQueryList"})
@Document(indexName = "employee_index", type = "employee_type", shards = 1, replicas = 0, createIndex = true)
public class EmployeeEs extends EntityEs {
 
 @Id
 @Field(type = FieldType.Keyword)
    private Long userId;
 
 //@Field(type = FieldType.Text, analyzer = "ik_max_word")
 @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "ik_max_word"), otherFields = @InnerField(suffix = "trueName", type = FieldType.Keyword))
    private String userName;
 
 @Field(type = FieldType.Keyword)
    private String userCode;
 
 @Field(type = FieldType.Integer)
    private Integer userAge;
 
 @Field(type = FieldType.Keyword)
    private String userMobile;
 
 @Field(type = FieldType.Date)
    private Date birthDay;
 
 
@Field(type = FieldType.Keyword)
 private String userSex;
 
 
 @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String remarks;
}
View Code

2、Repository介面:EmployeeEsRepository 

/**
 * @author: shf
 * description: 可根據對映實體設定自動生成mapping結構;支援bean的增刪改查操作
 * date: 2022/2/23 10:47
 */
@Component
public interface EmployeeEsRepository extends CrudRepository<EmployeeEs,Long> {
}

二、功能性實體類:EntityEs

說明:

①所有欄位非es索引中的mapping屬性欄位,均為功能性欄位,如排序、高亮、分頁設定和一些常量設定等,詳見貼碼

②所有es對映實體類均需繼承該實體

elasticsearch通用工具類
/**
 * @author: shf description: 功能性欄位(非mapping結構欄位)
 * date: 2022/3/1 15:07
 */
@Data
public class EntityEs {

    /**
     * 組合多查詢常量
     */
    /**
     * 文件 必須 匹配這些條件才能被查詢到。相當於sql中的and
     */
    public static String MUST = "must";

    /**
     * 文件 必須不 匹配這些條件才能被查詢到。相當於sql中的 not
     */
    public static String MUST_NOT = "must_not";

    /**
     * 如果滿足這些語句中的任意語句,將增加 _score ,否則,無任何影響。它們主要用於修正每個文件的相關性得分。相當於sql中的or
     */
    public static String SHOULD = "should";

    /**
     * 必須 匹配,但它以不評分、過濾模式來進行。這些語句對評分沒有貢獻,只是根據過濾標準來排除或包含文件
     */
    public static String FILTER = "filter";

    /**
     * 至少匹配一項should子句
     */
    public static String MINIMUM_SHOULD_MATCH = "minimum_should_match";

    /**
     * 多欄位排序查詢
     */
    public EsMapUtil orderMap;

    /**
     * 分頁查詢
     */
    public Integer pageNumber;

    public Integer pageSize;

    /**
     * 遊標分頁ID
     */
    public String scrollId;

    /**
     * 遊標分頁ID有效期 單位:毫秒
     */
    public static Long scrollIdExpireTime = 1000 * 60 * 2L;

    /**
     * 遊標分頁ID最小有效期 單位:毫秒
     */
    public static Long scrollIdMinExpireTime = 1000L;

    /**
     * 高亮查詢
     */
    public List<String> highlightFields;

    public String preTags;

    public String postTags;

    /**
     * 欄位查詢
     */
    public EsMapUtil fieldQueryMap;

    /**
     * 聚合查詢,當前只支援單個欄位分組聚合count與sum,只針對keyword型別欄位有效
     */
    public EsMapUtil aggregationMap;

    public static String COUNT = "count";
    public static String SUM = "sum";

    /**
     * 多層(bool)查詢
     */
    public List multiLayerQueryList;

    /**
     * 範圍查詢常量
     */
    public static String GT = "gt";
    public static String GTE = "gte";
    public static String LT = "lt";
    public static String LTE = "lte";

    @Data
    public class RangeRelation<T> {

        //String fieldKey;

        T fieldMinValue;

        String fieldMinMode;

        T fieldMaxValue;

        String fieldMaxMode;

        String queryMode;

        public RangeRelation(T fieldMinValue, String fieldMinMode, T fieldMaxValue, String fieldMaxMode, String queryMode) {
            this.fieldMinValue = fieldMinValue;
            this.fieldMinMode = fieldMinMode;
            this.fieldMaxValue = fieldMaxValue;
            this.fieldMaxMode = fieldMaxMode;
            this.queryMode = queryMode;
        }
    }

    @Data
    public class QueryRelation<T> {

        T fieldValue;

        String queryMode;

        Float boostValue;

        public QueryRelation(T fieldValue, String queryMode) {
            this.fieldValue = fieldValue;
            this.queryMode = queryMode;
        }

        public QueryRelation(T fieldValue, String queryMode, Float boostValue) {
            this.fieldValue = fieldValue;
            this.queryMode = queryMode;
            this.boostValue = boostValue;
        }
    }

    @Data
    public class MultiLayerRelation {

        String queryMode;

        EsMapUtil map;

        List<EntityEs.MultiLayerRelation> multiLayerList;

        public MultiLayerRelation(String queryMode, EsMapUtil map) {
            this.queryMode = queryMode;
            this.map = map;
        }

        public MultiLayerRelation(String queryMode, EsMapUtil map, List<MultiLayerRelation> multiLayerList) {
            this.queryMode = queryMode;
            this.map = map;
            this.multiLayerList = multiLayerList;
        }
    }
}
View Code

三、小工具:EsMapUtil

說明:封裝了一個map工具,編碼簡潔鏈式呼叫,應用了方法引用特性避免了字串硬編碼造成單詞拼錯的情況。

/**
 * @author: shf description: 函式式介面 便於方法引用獲取實體欄位名稱
 * date: 2022/3/4 13:41
 */
@FunctionalInterface
public interface IGetterFunction<T> extends Serializable{
    Object get(T source);
}
elasticsearch通用工具類
/**
 * @author: shf
 * description
 * date: 2019/11/13 18:30
 */
public class EsMapUtil extends LinkedHashMap<String, Object> {

    public <T> EsMapUtil put(IGetterFunction<T> fn, Object value) {
        String key = getFieldName(fn);
        super.put(key, value);
        return this;
    }

    public <T> EsMapUtil putStr(String key, Object value) {
        super.put(key, value);
        return this;
    }

    private static Map<Class, SerializedLambda> CLASS_LAMDBA_CACHE = new ConcurrentHashMap<>();

    /***
     * 轉換方法引用為屬性名
     * @param fn
     * @return
     */
    public <T> String getFieldName(IGetterFunction<T> fn) {
        SerializedLambda lambda = getSerializedLambda(fn);
        String methodName = lambda.getImplMethodName();
        String prefix = null;
        if (methodName.startsWith("get")) {
            prefix = "get";
        }
        // 擷取get之後的字串並轉換首字母為小寫
        return toLowerCaseFirstOne(methodName.replace(prefix, ""));
    }

    /**
     * 首字母轉小寫
     *
     * @param s
     */
    public String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

    public static SerializedLambda getSerializedLambda(Serializable fn) {
        SerializedLambda lambda = CLASS_LAMDBA_CACHE.get(fn.getClass());
        if (lambda == null) {
            try {
                Method method = fn.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                lambda = (SerializedLambda) method.invoke(fn);
                CLASS_LAMDBA_CACHE.put(fn.getClass(), lambda);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return lambda;
    }
}
View Code

四、Es通用工具類EsService

elasticsearch通用工具類
/**
 * @author: shf description: es工具類,支援:分頁(支援遊標分頁)、高亮(支援自定義標籤)、範圍查詢、bool組合查詢、多層bool套bool、多欄位排序、加權重、聚合、二級欄位查詢
 * date: 2022/2/23 10:54
 */
@Component
@Slf4j
public class EsService {
    @Autowired
    private ElasticsearchRestTemplate restTemplate;
 
    @Autowired
    private ApplicationContext context;
 
 
    /**
     * 判斷索引是否存在
     *
     * @return boolean
     */
    public <T> boolean indexExists(Class<T> clazz) {
        return restTemplate.indexExists(clazz);
    }
 
    /**
     * 判斷索引是否存在
     *
     * @param indexName 索引名稱
     * @return boolean
     */
    public boolean indexExists(String indexName) {
        return restTemplate.indexExists(indexName);
    }
 
    /**
     * 建立索引(推薦使用:因為Java物件已經通過註解描述了Setting和Mapping)
     *
     * @return boolean
     */
    public <T> boolean indexCreate(Class<T> clazz) {
        boolean createFlag = restTemplate.createIndex(clazz);
        boolean mappingFlag = restTemplate.putMapping(clazz);
        return createFlag && mappingFlag;
 
    }
 
    /**
     * 索引刪除
     *
     * @param indexName 索引名稱
     * @return boolean
     */
    public boolean indexDelete(String indexName) {
        return restTemplate.deleteIndex(indexName);
    }
 
    /**
     * 新增資料
     *
     * @param bean 資料物件
     */
    public <T> void saveBean(T bean) {
        EsRepository esRepositoryAnno = bean.getClass().getAnnotation(EsRepository.class);
        CrudRepository crudRepository = (CrudRepository) context.getBean(esRepositoryAnno.value());
        crudRepository.save(bean);
    }
 
    /**
     * 批量新增資料
     *
     * @param list 資料集合
     */
    public <T> void saveList(List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        EsRepository esRepositoryAnno = list.get(0).getClass().getAnnotation(EsRepository.class);
        CrudRepository crudRepository = (CrudRepository) context.getBean(esRepositoryAnno.value());
        crudRepository.saveAll(list);
    }
 
    /**
     * 根據物件刪除資料,主鍵ID不能為空
     *
     * @param bean 物件
     */
    public <T> void deleteByBean(T bean) {
        EsRepository esRepositoryAnno = bean.getClass().getAnnotation(EsRepository.class);
        CrudRepository crudRepository = (CrudRepository) context.getBean(esRepositoryAnno.value());
        crudRepository.delete(bean);
    }
 
    /**
     * 根據物件集合,批量刪除
     *
     * @param beanList 物件集合
     */
    public <T> void deleteAllByBeanList(List<T> beanList) {
        if (CollectionUtils.isEmpty(beanList)) {
            return;
        }
        EsRepository esRepositoryAnno = beanList.get(0).getClass().getAnnotation(EsRepository.class);
        CrudRepository crudRepository = (CrudRepository) context.getBean(esRepositoryAnno.value());
        crudRepository.deleteAll(beanList);
    }
 
    /**
     * 刪除所有
     */
    public <T> void deleteAll(T bean) {
        EsRepository esRepositoryAnno = bean.getClass().getAnnotation(EsRepository.class);
        CrudRepository crudRepository = (CrudRepository) context.getBean(esRepositoryAnno.value());
        crudRepository.deleteAll();
    }
 
    /**
     * 修改資料
     *
     * @param t 修改資料物件,ID不能為空
     */
    public <T> boolean updateByBean(T t) throws IllegalAccessException {
        Class clazz = t.getClass();
        Field[] Fields = clazz.getDeclaredFields();
        String beanId = null;
        String beanIdName = null;
        for (Field f : Fields) {
            f.setAccessible(true);
            if (f.isAnnotationPresent(org.springframework.data.annotation.Id.class)) {
                beanId = String.valueOf(f.get(t));
                beanIdName = f.getName();
            }
        }
        if (StringUtils.isBlank(beanId)) {
            log.warn("id不能為空");
            return false;
        }
        if (Objects.isNull(restTemplate.queryForObject(GetQuery.getById(beanId), clazz))) {
            log.warn("該文件不存在");
            return false;
        }
        Document annotation = (Document) clazz.getAnnotation(Document.class);
        UpdateRequest updateRequest = new UpdateRequest();
        //衝突重試
        updateRequest.retryOnConflict(1);
        updateRequest.doc(JSON.toJSONString(t), XContentType.JSON);
        //預設是_id來路由的,用來路由到不同的shard,會對這個值做hash,然後對映到shard。所以分片
        updateRequest.routing(beanId);
        UpdateQuery query = new UpdateQueryBuilder().withIndexName(annotation.indexName()).withType(annotation.type()).withId(beanId)
            .withDoUpsert(false)//不加預設false。true表示更新時不存在就插入
            .withClass(clazz).withUpdateRequest(updateRequest).build();
        UpdateResponse updateResponse = restTemplate.update(query);
        if (!Objects.equals(updateResponse.getShardInfo().getSuccessful(), 1)) {
            return false;
        }
        return true;
    }
 
    /**
     * 根據bean ID 查詢
     *
     * @param beanId
     * @param clazz
     * @param <T>
     */
    public <T> T queryBeanById(String beanId, Class<T> clazz) {
        return restTemplate.queryForObject(GetQuery.getById(beanId), clazz);
    }
 
    /**
     * 資料查詢,返回List
     *
     * @return List<T>
     */
    public <T> List<T> queryList(T t) throws IllegalAccessException, NoSuchFieldException {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        Class clazz = (Class) t.getClass();
        Map<String, Object> queryMap = (LinkedHashMap) clazz.getField("fieldQueryMap").get(t);
        Map<String, String> orderMap = (LinkedHashMap) clazz.getField("orderMap").get(t);
        List<String> highlightFields = (List<String>) clazz.getField("highlightFields").get(t);
        String preTags = StringUtils.isNotBlank((String) clazz.getField("preTags").get(t)) ? (String) clazz.getField("preTags").get(t) : "<em>";
        String postTags = StringUtils.isNotBlank((String) clazz.getField("postTags").get(t)) ? (String) clazz.getField("postTags").get(t) : "</em>";
        List<EntityEs.MultiLayerRelation> multiLayerQueryList = (List<EntityEs.MultiLayerRelation>) clazz.getField("multiLayerQueryList").get(t);
 
        String beanIdName = null;
        Field[] Fields = clazz.getDeclaredFields();
        for (Field f : Fields) {
            f.setAccessible(true);
            if (f.isAnnotationPresent(org.springframework.data.annotation.Id.class)) {
                beanIdName = f.getName();
                break;
            }
        }
        //構建組合查詢(支援權重)
        getFieldQueryBuilder(boolQueryBuilder, clazz, queryMap);
        //處理多層bool查詢
        getNestQueryBuilder(boolQueryBuilder, clazz, multiLayerQueryList);
 
        log.info("列印語句:{}", boolQueryBuilder.toString());
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder);
        //支援多欄位排序查詢
        getOrderBuilder(beanIdName, orderMap, nativeSearchQueryBuilder);
        //支援多欄位高亮查詢並可自定義高亮標籤規則
        getHighLightBuilder(highlightFields, preTags, postTags, nativeSearchQueryBuilder);
 
        if (CollectionUtils.isEmpty(highlightFields)) {
            return restTemplate.queryForList(nativeSearchQueryBuilder.build(), clazz);
        } else {
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 10000));
            ScrolledPage page = restTemplate.startScroll(EntityEs.scrollIdMinExpireTime, nativeSearchQueryBuilder.build(), clazz, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                    List<T> chunk = new ArrayList<>();
                    for (SearchHit searchHit : response.getHits()) {
                        if (response.getHits().getHits().length <= 0) {
                            return null;
                        }
                        try {
                            T t = (T) JSON.parseObject(searchHit.getSourceAsString(), clazz);
                            for (String fieldName : highlightFields) {
                                Field f = clazz.getDeclaredField(fieldName);
                                HighlightField highlightField = searchHit.getHighlightFields().get(fieldName);
                                if (highlightField != null) {
                                    f.setAccessible(true);
                                    f.set(t, highlightField.fragments()[0].toString());
                                }
                            }
                            chunk.add(t);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (NoSuchFieldException e) {
                            e.printStackTrace();
                        }
                    }
                    if (chunk.size() > 0) {
                        return new AggregatedPageImpl<>((List<T>) chunk);
                    }
                    return null;
                }
 
                @Override
                public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
                    return null;
                }
            });
            return page.toList();
        }
    }
 
 
    /**
     * 分頁查詢
     *
     * @param t
     * @param <T>
     */
    public <T> List<T> queryPage(T t) throws IllegalAccessException, NoSuchFieldException {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        Class clazz = (Class) t.getClass();
 
        Map<String, Object> queryMap = (LinkedHashMap) clazz.getField("fieldQueryMap").get(t);
        Map<String, String> orderMap = (LinkedHashMap) clazz.getField("orderMap").get(t);
        List<String> highlightFields = (List<String>) clazz.getField("highlightFields").get(t);
        String preTags = StringUtils.isNotBlank((String) clazz.getField("preTags").get(t)) ? (String) clazz.getField("preTags").get(t) : "<em>";
        String postTags = StringUtils.isNotBlank((String) clazz.getField("postTags").get(t)) ? (String) clazz.getField("postTags").get(t) : "</em>";
        Integer pageNumber = !Objects.isNull((Integer) clazz.getField("pageNumber").get(t)) ? (Integer) clazz.getField("pageNumber").get(t) : 0;
        Integer pageSize = !Objects.isNull((Integer) clazz.getField("pageSize").get(t)) ? (Integer) clazz.getField("pageSize").get(t) : 10;
        List<EntityEs.MultiLayerRelation> multiLayerQueryList = (List<EntityEs.MultiLayerRelation>) clazz.getField("multiLayerQueryList").get(t);
 
        String beanIdName = null;
        Field[] Fields = clazz.getDeclaredFields();
        for (Field f : Fields) {
            f.setAccessible(true);
            if (f.isAnnotationPresent(org.springframework.data.annotation.Id.class)) {
                beanIdName = f.getName();
                break;
            }
        }
        //構建組合查詢(支援權重)
        getFieldQueryBuilder(boolQueryBuilder, clazz, queryMap);
        //處理多層bool查詢
        getNestQueryBuilder(boolQueryBuilder, clazz, multiLayerQueryList);
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder);
        //支援多欄位排序查詢
        getOrderBuilder(beanIdName, orderMap, nativeSearchQueryBuilder);
        //支援多欄位高亮查詢並可自定義高亮標籤規則
        getHighLightBuilder(highlightFields, preTags, postTags, nativeSearchQueryBuilder);
        //分頁查詢
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNumber, pageSize));
 
        AggregatedPage page = null;
        if (CollectionUtils.isEmpty(highlightFields)) {
            page = restTemplate.queryForPage(nativeSearchQueryBuilder.build(), clazz);
        } else {
            page = restTemplate.queryForPage(nativeSearchQueryBuilder.build(), clazz, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                    List<T> chunk = new ArrayList<>();
                    for (SearchHit searchHit : response.getHits()) {
                        if (response.getHits().getHits().length <= 0) {
                            return null;
                        }
                        try {
                            T t = (T) JSON.parseObject(searchHit.getSourceAsString(), clazz);
                            for (String fieldName : highlightFields) {
                                Field f = clazz.getDeclaredField(fieldName);
                                HighlightField highlightField = searchHit.getHighlightFields().get(fieldName);
                                if (highlightField != null) {
                                    f.setAccessible(true);
                                    f.set(t, highlightField.fragments()[0].toString());
                                }
                            }
                            chunk.add(t);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (NoSuchFieldException e) {
                            e.printStackTrace();
                        }
                    }
                    if (chunk.size() > 0) {
                        return new AggregatedPageImpl<>((List<T>) chunk);
                    }
 
 
                    return null;
                }
 
                @Override
                public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
                    return null;
                }
            });
        }
        // 總記錄數
        long totalElements = page.getTotalElements();
        // 總頁數
        int totalPages = page.getTotalPages();
        // 當前頁號
        //int currentPage = page.getPageable().getPageNumber();
        // 當前頁資料集
        List<T> beanList = page.toList();
        List<T> content = page.getContent();
        System.out.println(beanList);
        //TODO 根據專案中的分頁封裝類將以上資料設定後返回即可
        return Lists.newArrayList();
    }
 
 
    /**
     * 遊標分頁
     *
     * @param t
     * @param <T>
     */
    public <T> List<T> queryScrollPage(T t) throws IllegalAccessException, NoSuchFieldException {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        Class clazz = (Class) t.getClass();
        String scrollId = (String) clazz.getField("scrollId").get(t);
        if (StringUtils.isNotBlank(scrollId)) {
            ScrolledPage page = restTemplate.continueScroll(scrollId, EntityEs.scrollIdExpireTime, clazz);
            // 總記錄數
            long totalElements = page.getTotalElements();
            // 總頁數
            //int totalPages = page.getTotalPages();
            // 當前頁號
            //int currentPage = page.getPageable().getPageNumber();
            // 當前頁資料集
            List<T> beanSet = page.toList();
            List<T> content = page.getContent();
            System.out.println("page.getScrollId:" + page.getScrollId());
            System.out.println(beanSet);
            //TODO 根據專案中的分頁封裝類將以上資料設定後返回即可
            return Lists.newArrayList();
        }
        Map<String, Object> queryMap = (LinkedHashMap) clazz.getField("fieldQueryMap").get(t);
        Map<String, String> orderMap = (LinkedHashMap) clazz.getField("orderMap").get(t);
        List<String> highlightFields = (List<String>) clazz.getField("highlightFields").get(t);
        String preTags = StringUtils.isNotBlank((String) clazz.getField("preTags").get(t)) ? (String) clazz.getField("preTags").get(t) : "<em>";
        String postTags = StringUtils.isNotBlank((String) clazz.getField("postTags").get(t)) ? (String) clazz.getField("postTags").get(t) : "</em>";
        Integer pageNumber = !Objects.isNull((Integer) clazz.getField("pageNumber").get(t)) ? (Integer) clazz.getField("pageNumber").get(t) : 0;
        Integer pageSize = !Objects.isNull((Integer) clazz.getField("pageSize").get(t)) ? (Integer) clazz.getField("pageSize").get(t) : 10;
        List<EntityEs.MultiLayerRelation> multiLayerQueryList = (List<EntityEs.MultiLayerRelation>) clazz.getField("multiLayerQueryList").get(t);
 
        String beanIdName = null;
        Field[] Fields = clazz.getDeclaredFields();
        for (Field f : Fields) {
            f.setAccessible(true);
            if (f.isAnnotationPresent(org.springframework.data.annotation.Id.class)) {
                beanIdName = f.getName();
                break;
            }
        }
        //構建組合查詢(支援權重)
        getFieldQueryBuilder(boolQueryBuilder, clazz, queryMap);
        //處理多層bool查詢
        getNestQueryBuilder(boolQueryBuilder, clazz, multiLayerQueryList);
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder);
        //支援多欄位排序查詢
        getOrderBuilder(beanIdName, orderMap, nativeSearchQueryBuilder);
        //支援多欄位高亮查詢並可自定義高亮標籤規則
        getHighLightBuilder(highlightFields, preTags, postTags, nativeSearchQueryBuilder);
        //分頁查詢
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNumber, pageSize));
 
        ScrolledPage page = restTemplate.startScroll(EntityEs.scrollIdExpireTime, nativeSearchQueryBuilder.build(), clazz, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<T> chunk = new ArrayList<>();
                for (SearchHit searchHit : response.getHits()) {
                    if (response.getHits().getHits().length <= 0) {
                        return null;
                    }
                    try {
                        T t = (T) JSON.parseObject(searchHit.getSourceAsString(), clazz);
                        for (String fieldName : highlightFields) {
                            Field f = clazz.getDeclaredField(fieldName);
                            HighlightField highlightField = searchHit.getHighlightFields().get(fieldName);
                            if (highlightField != null) {
                                f.setAccessible(true);
                                f.set(t, highlightField.fragments()[0].toString());
                            }
                        }
                        chunk.add(t);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    }
                }
                if (chunk.size() > 0) {
                    return new AggregatedPageImpl<>((List<T>) chunk);
                }
                return null;
            }
 
            @Override
            public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
                return null;
            }
        });
 
        // 總記錄數
        long totalElements = page.getTotalElements();
        // 總頁數
        //int totalPages = page.getTotalPages();
        // 當前頁號
        //int currentPage = page.getPageable().getPageNumber();
        // 當前頁資料集
        List<T> beanSet = page.toList();
        List<T> content = page.getContent();
        System.out.println("page.getScrollId:" + page.getScrollId());
        System.out.println(beanSet);
        //TODO 根據專案中的分頁封裝類將以上資料設定後返回即可
        return Lists.newArrayList();
    }
 
 
    /**
     * 聚合查詢
     *
     * @param t
     * @param <T>
     */
    public <T> Map queryForAggregation(T t) throws IllegalAccessException, NoSuchFieldException {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        Class clazz = (Class) t.getClass();
 
        Map<String, Object> queryMap = (LinkedHashMap) clazz.getField("fieldQueryMap").get(t);
        Integer pageNumber = !Objects.isNull(clazz.getField("pageNumber").get(t)) ? (Integer) clazz.getField("pageNumber").get(t) : 0;
        Integer pageSize = !Objects.isNull(clazz.getField("pageSize").get(t)) ? (Integer) clazz.getField("pageSize").get(t) : 1;
        Map<String, String> aggregationMap = (LinkedHashMap) clazz.getField("aggregationMap").get(t);
        List<EntityEs.MultiLayerRelation> multiLayerQueryList = (List<EntityEs.MultiLayerRelation>) clazz.getField("multiLayerQueryList").get(t);
 
        //構建組合查詢(支援權重)
        getFieldQueryBuilder(boolQueryBuilder, clazz, queryMap);
        //處理多層bool查詢
        getNestQueryBuilder(boolQueryBuilder, clazz, multiLayerQueryList);
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder);
        //分頁查詢
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNumber, pageSize));
 
        // 建立聚合查詢條件
        String aggKey = null;
        String aggValue = null;
        for (Map.Entry<String, String> entry : aggregationMap.entrySet()) {
            aggKey = entry.getKey();
            aggValue = entry.getValue();
            if (Objects.equals(aggValue, EntityEs.COUNT)) {
                TermsAggregationBuilder agg = AggregationBuilders.terms(aggKey).field(aggKey);
                nativeSearchQueryBuilder.addAggregation(agg);
                break;
            } else if (Objects.equals(aggValue, EntityEs.SUM)) {
                SumAggregationBuilder agg = AggregationBuilders.sum(aggKey).field(aggKey);
                nativeSearchQueryBuilder.addAggregation(agg);
                break;
            }
        }
        AggregatedPage page = restTemplate.queryForPage(nativeSearchQueryBuilder.build(), clazz);
        // 取出聚合結果
        Aggregations entitiesAggregations = page.getAggregations();
        Map<String, Object> retMap = new HashMap<>();
        if (Objects.equals(aggValue, EntityEs.COUNT)) {
            Terms terms = (Terms) entitiesAggregations.asMap().get(aggKey);
            // 遍歷取出聚合欄位列的值,與對應的數量
            for (Terms.Bucket bucket : terms.getBuckets()) {
                // 聚合欄位列的值
                String keyAsString = bucket.getKeyAsString();
                // 聚合欄位對應的數量
                long docCount = bucket.getDocCount();
                log.info("keyAsString={},value={}", keyAsString, docCount);
                retMap.put(keyAsString, docCount);
            }
        } else if (Objects.equals(aggValue, EntityEs.SUM)) {
            Aggregation aggregation = entitiesAggregations.get(aggKey);
            retMap.put(aggregation.getName(), ((ParsedSum) aggregation).getValue());
        }
        // 當前頁資料集
        List<T> beanList = page.toList();
        System.out.println("當前頁資料集:" + beanList);
        return retMap;
    }
 
 
    /**
     * 根據自定義查詢條件批量查詢
     *
     * @param searchQuery
     * @param clazz
     * @param <T>
     */
    public <T> List<T> queryListBySearchQuery(SearchQuery searchQuery, Class<T> clazz) {
        return restTemplate.queryForList(searchQuery, clazz);
    }
 
 
    /*------------------------------------------- private 私有方法 ----------------------------------------------*/
 
    private void getNestQueryBuilder(BoolQueryBuilder boolQueryBuilder, Class clazz, List<EntityEs.MultiLayerRelation> multiLayerQueryList) throws NoSuchFieldException {
        if (!CollectionUtils.isEmpty(multiLayerQueryList)) {
            for (EntityEs.MultiLayerRelation r : multiLayerQueryList) {
                BoolQueryBuilder nestBoolQuery = QueryBuilders.boolQuery();
                EsMapUtil nestMap = r.getMap();
                getFieldQueryBuilder(nestBoolQuery, clazz, nestMap);
                if (Objects.equals(r.getQueryMode(), EntityEs.MUST)) {
                    boolQueryBuilder.must(nestBoolQuery);
                } else if (Objects.equals(r.getQueryMode(), EntityEs.SHOULD)) {
                    boolQueryBuilder.should(nestBoolQuery);
                } else if (Objects.equals(r.getQueryMode(), EntityEs.FILTER)) {
                    boolQueryBuilder.filter(nestBoolQuery);
                } else if (Objects.equals(r.getQueryMode(), EntityEs.MUST_NOT)) {
                    boolQueryBuilder.mustNot(nestBoolQuery);
                }
                //遞迴巢狀
                if (!CollectionUtils.isEmpty(r.getMultiLayerList())) {
                    //處理多層bool查詢
                    getNestQueryBuilder(nestBoolQuery, clazz, r.getMultiLayerList());
                }
            }
        }
    }
 
    /**
     * 構建組合查詢(支援權重)
     *
     * @param boolQueryBuilder
     * @param clazz
     * @param queryMap
     */
    private void getFieldQueryBuilder(BoolQueryBuilder boolQueryBuilder, Class clazz, Map<String, Object> queryMap) throws NoSuchFieldException {
        if (queryMap != null && queryMap.size() > 0) {
            for (Map.Entry<String, Object> entry : queryMap.entrySet()) {
                String k = entry.getKey();
                List vList = new ArrayList();
                if (entry.getValue() instanceof List) {
                    vList = (ArrayList) entry.getValue();
                } else {
                    vList.add(entry.getValue());
                }
                FieldType type = null;
                if (k.indexOf(".") == -1) {
                    Field f = clazz.getDeclaredField(k);
                    if (f.isAnnotationPresent(org.springframework.data.elasticsearch.annotations.Field.class)) {
                        type = f.getAnnotation(org.springframework.data.elasticsearch.annotations.Field.class).type();
                    } else if (f.isAnnotationPresent(org.springframework.data.elasticsearch.annotations.MultiField.class)) {
                        type = f.getAnnotation(org.springframework.data.elasticsearch.annotations.MultiField.class).mainField().type();
                    }
                } else {
                    //如果欄位Field type定義的是Keyword,走matchQuery效果也是term精確查詢
                    type = FieldType.Text;
                }
                if (Objects.equals(type, FieldType.Text)) {
                    for (Object o : vList) {
                        if (o instanceof EntityEs.RangeRelation) {
                            EntityEs.RangeRelation v = (EntityEs.RangeRelation) o;
                            //範圍查詢
                            getRangeBuilder(boolQueryBuilder, k, v);
                        } else if (o instanceof EntityEs.QueryRelation) {
                            EntityEs.QueryRelation v = (EntityEs.QueryRelation) o;
                            if (Objects.equals(v.getQueryMode(), EntityEs.MUST)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.must(QueryBuilders.matchQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.must(QueryBuilders.matchQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.MUST_NOT)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.mustNot(QueryBuilders.matchQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.mustNot(QueryBuilders.matchQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.SHOULD)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.should(QueryBuilders.matchQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.should(QueryBuilders.matchQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.FILTER)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.filter(QueryBuilders.matchQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.filter(QueryBuilders.matchQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            }
                        }
                    }
                } else if (Objects.equals(type, FieldType.Keyword)) {
                    for (Object o : vList) {
                        if (o instanceof EntityEs.RangeRelation) {
                            EntityEs.RangeRelation v = (EntityEs.RangeRelation) o;
                            //範圍查詢
                            getRangeBuilder(boolQueryBuilder, k, v);
                        } else if (o instanceof EntityEs.QueryRelation) {
                            EntityEs.QueryRelation v = (EntityEs.QueryRelation) o;
                            if (Objects.equals(v.getQueryMode(), EntityEs.MUST)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.must(QueryBuilders.termQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.must(QueryBuilders.termQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.MUST_NOT)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.mustNot(QueryBuilders.termQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.mustNot(QueryBuilders.termQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.SHOULD)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.should(QueryBuilders.termQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.should(QueryBuilders.termQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.FILTER)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.filter(QueryBuilders.termQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.filter(QueryBuilders.termQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            }
 
                        }
                    }
                } else {
                    for (Object o : vList) {
                        if (o instanceof EntityEs.RangeRelation) {
                            EntityEs.RangeRelation v = (EntityEs.RangeRelation) o;
                            //範圍查詢
                            getRangeBuilder(boolQueryBuilder, k, v);
                        } else if (o instanceof EntityEs.QueryRelation) {
                            EntityEs.QueryRelation v = (EntityEs.QueryRelation) o;
                            if (Objects.equals(v.getQueryMode(), EntityEs.MUST)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.must(QueryBuilders.termQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.must(QueryBuilders.termQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.MUST_NOT)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.mustNot(QueryBuilders.termQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.mustNot(QueryBuilders.termQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.SHOULD)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.should(QueryBuilders.termQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.should(QueryBuilders.termQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            } else if (Objects.equals(v.getQueryMode(), EntityEs.FILTER)) {
                                if (Objects.isNull(v.getBoostValue())) {
                                    boolQueryBuilder.filter(QueryBuilders.termQuery(k, v.getFieldValue()));
                                } else {
                                    boolQueryBuilder.filter(QueryBuilders.termQuery(k, v.getFieldValue()).boost(v.getBoostValue()));
                                }
                            }
                        }
                    }
                }
            }
        }
    }
 
    /**
     * 構建範圍查詢
     *
     * @param boolQueryBuilder
     * @param k
     * @param v
     */
    private void getRangeBuilder(BoolQueryBuilder boolQueryBuilder, String k, EntityEs.RangeRelation v) {
        if (Objects.equals(v.getQueryMode(), EntityEs.MUST)) {
            if (Objects.equals(v.getFieldMinMode(), EntityEs.GT)) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery(k).gt(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.GTE)) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery(k).gte(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.LT)) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery(k).lt(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.LTE)) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery(k).lte(v.getFieldMinValue()));
            }
            if (Objects.equals(v.getFieldMaxMode(), EntityEs.GT)) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery(k).gt(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.GTE)) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery(k).gte(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.LT)) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery(k).lt(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.LTE)) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery(k).lte(v.getFieldMaxValue()));
            }
        } else if (Objects.equals(v.getQueryMode(), EntityEs.MUST_NOT)) {
            if (Objects.equals(v.getFieldMinMode(), EntityEs.GT)) {
                boolQueryBuilder.mustNot(QueryBuilders.rangeQuery(k).gt(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.GTE)) {
                boolQueryBuilder.mustNot(QueryBuilders.rangeQuery(k).gte(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.LT)) {
                boolQueryBuilder.mustNot(QueryBuilders.rangeQuery(k).lt(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.LTE)) {
                boolQueryBuilder.mustNot(QueryBuilders.rangeQuery(k).lte(v.getFieldMinValue()));
            }
            if (Objects.equals(v.getFieldMaxMode(), EntityEs.GT)) {
                boolQueryBuilder.mustNot(QueryBuilders.rangeQuery(k).gt(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.GTE)) {
                boolQueryBuilder.mustNot(QueryBuilders.rangeQuery(k).gte(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.LT)) {
                boolQueryBuilder.mustNot(QueryBuilders.rangeQuery(k).lt(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.LTE)) {
                boolQueryBuilder.mustNot(QueryBuilders.rangeQuery(k).lte(v.getFieldMaxValue()));
            }
        } else if (Objects.equals(v.getQueryMode(), EntityEs.SHOULD)) {
            if (Objects.equals(v.getFieldMinMode(), EntityEs.GT)) {
                boolQueryBuilder.should(QueryBuilders.rangeQuery(k).gt(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.GTE)) {
                boolQueryBuilder.should(QueryBuilders.rangeQuery(k).gte(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.LT)) {
                boolQueryBuilder.should(QueryBuilders.rangeQuery(k).lt(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.LTE)) {
                boolQueryBuilder.should(QueryBuilders.rangeQuery(k).lte(v.getFieldMinValue()));
            }
            if (Objects.equals(v.getFieldMaxMode(), EntityEs.GT)) {
                boolQueryBuilder.should(QueryBuilders.rangeQuery(k).gt(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.GTE)) {
                boolQueryBuilder.should(QueryBuilders.rangeQuery(k).gte(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.LT)) {
                boolQueryBuilder.should(QueryBuilders.rangeQuery(k).lt(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.LTE)) {
                boolQueryBuilder.should(QueryBuilders.rangeQuery(k).lte(v.getFieldMaxValue()));
            }
        } else if (Objects.equals(v.getQueryMode(), EntityEs.FILTER)) {
            if (Objects.equals(v.getFieldMinMode(), EntityEs.GT)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(k).gt(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.GTE)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(k).gte(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.LT)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(k).lt(v.getFieldMinValue()));
            } else if (Objects.equals(v.getFieldMinMode(), EntityEs.LTE)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(k).lte(v.getFieldMinValue()));
            }
            if (Objects.equals(v.getFieldMaxMode(), EntityEs.GT)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(k).gt(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.GTE)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(k).gte(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.LT)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(k).lt(v.getFieldMaxValue()));
            } else if (Objects.equals(v.getFieldMaxMode(), EntityEs.LTE)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(k).lte(v.getFieldMaxValue()));
            }
        }
 
    }
 
    /**
     * 構建排序查詢
     *
     * @param beanIdName
     * @param orderMap
     * @param nativeSearchQueryBuilder
     */
    private void getOrderBuilder(String beanIdName, Map orderMap, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        if (orderMap != null && orderMap.size() > 0) {
            orderMap.forEach((k, v) -> {
                nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((String) k).order((SortOrder) v));
            });
        } else {
            //無指定排序欄位預設按ID倒序
            //nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(beanIdName).order(SortOrder.DESC));
        }
    }
 
    /**
     * 構建高亮查詢
     *
     * @param highlightFields
     * @param preTags
     * @param postTags
     * @param nativeSearchQueryBuilder
     */
    private void getHighLightBuilder(List<String> highlightFields, String preTags, String postTags, NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        if (highlightFields != null && highlightFields.size() > 0) {
            List<HighlightBuilder.Field> highlightTitles = new ArrayList<>();
            for (String title : highlightFields) {
                highlightTitles.add(new HighlightBuilder.Field(title).preTags(preTags).postTags(postTags));
            }
            nativeSearchQueryBuilder.withHighlightFields(highlightTitles.stream().toArray(HighlightBuilder.Field[]::new));
        }
    }
}
View Code

五、單元測試

1、根據對映實體設定生成索引與mapping

      說明:啟動專案會自動載入自動建立,如需要手動建立可呼叫方法

@Test
public void indexCreateTest() {
    System.out.println(esService.indexCreate(EmployeeEs.class));
}

2、刪除指定索引

@Test
public void indexDelete() {
    System.out.println(esService.indexDelete("employee_index"));
}

3、判斷指定索引是否存在

@Test
public void indexExists() {
    System.out.println(esService.indexExists(EmployeeEs.class));
}

4、儲存單個bean

@Autowired
private EsService esService;
 
@Test
public void saveBean() {
    System.out.println("-----es測試start...");
 EmployeeEs employee = EmployeeEs.builder().userId(9L).userName("張三1").userCode("abc").userAge(22).userMobile("12345678987")
        .remarks("今天天氣好晴朗~").birthDay(new Date()).build();
 esService.saveBean(employee);
 System.out.println("-----es測試end...");
}

5、批量儲存

@Test
public void saveList() throws ParseException {
    System.out.println("-----es測試start...");
 EmployeeEs employee  =  EmployeeEs.builder().userId(1L).userName("張三").userCode("abc").userAge(18).userSex("男").userMobile("12345678987").birthDay(new SimpleDateFormat("yyyy-MM-dd").parse("1995-9-20")).build();
 EmployeeEs employee1 = EmployeeEs.builder().userId(2L).userName("李四").userCode("abc").userAge(10).userSex("女").userMobile("12345678987").birthDay(new SimpleDateFormat("yyyy-MM-dd").parse("1995-6-20")).build();
 EmployeeEs employee2 = EmployeeEs.builder().userId(3L).userName("王五").userCode("abc").userAge(10).userSex("男").userMobile("12345678987").birthDay(new SimpleDateFormat("yyyy-MM-dd").parse("1995-5-20")).build();
 EmployeeEs employee3 = EmployeeEs.builder().userId(4L).userName("趙六").userCode("abc").userAge(20).userSex("男").userMobile("12345678987").birthDay(new SimpleDateFormat("yyyy-MM-dd").parse("1995-8-20")).build();
 EmployeeEs employee4 = EmployeeEs.builder().userId(5L).userName("董七").userCode("abc").userAge(20).userSex("女").userMobile("12345678987").birthDay(new SimpleDateFormat("yyyy-MM-dd").parse("1995-8-20")).build();
 
 esService.saveList(Lists.newArrayList(employee,employee1,employee2,employee3,employee4));
 System.out.println("-----es測試end...");
}

 

 

6、根據ID刪除指定Bean

@Test
public void deleteByBean() {
    EmployeeEs employee = EmployeeEs.builder().userId(1L).build();
 esService.deleteByBean(employee);
}

7、批量刪除

@Test
public void deleteList() {
    EmployeeEs employee = EmployeeEs.builder().userId(1L).build();
 EmployeeEs employee1 = EmployeeEs.builder().userId(2L).build();
 esService.deleteAllByBeanList(Lists.newArrayList(employee,employee1));
}

8、刪除該索引下所有資料

@Test
public void deleteAll() {
    esService.deleteAll(EmployeeEs.class);
}

9、修改資料(ID不能為空)

@Test
public void updateTest() throws IllegalAccessException {
    System.out.println("-----es測試start...");
 EmployeeEs employee = EmployeeEs.builder().userId(5L).userName("張一").userCode("abcD").userAge(19).build();
 esService.updateByBean(employee);
 System.out.println("-----es測試end...");
}

10、根據ID查詢指定Bean

@Test
public void queryById() {
    EmployeeEs employeeEs = esService.queryBeanById("2", EmployeeEs.class);
 System.out.println(employeeEs);
}

11、批量查詢

/**
 * 涉及到了組合查詢bool、權重、範圍查詢、排序
 */
@Test
public void queryList() throws IllegalAccessException, NoSuchFieldException {
    System.out.println("-----es測試start...");
 EmployeeEs employee = new EmployeeEs();
 List queryList = Stream.of(employee.new QueryRelation<String>("張三", EntityEs.SHOULD, 5F), employee.new QueryRelation<String>("李四", EntityEs.SHOULD, 20F)).collect(Collectors.toList());
 employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserName, queryList).put(EmployeeEs::getUserAge, employee.new RangeRelation(20, EntityEs.GTE, null, null, EntityEs.MUST)));
 //排序查詢
 employee.setOrderMap(new EsMapUtil().put(EmployeeEs::getUserId, SortOrder.DESC));
 List<EmployeeEs> employeeEs = esService.queryList(employee);
 System.out.println(employeeEs);
 System.out.println("-----es測試end...");
}

列印出來的語句:

查詢結果:

12、二級屬性查詢

@Test
public void querySecondList() throws IllegalAccessException, NoSuchFieldException {
    System.out.println("-----es測試start...");
 EmployeeEs employee = new EmployeeEs();
 employee.setFieldQueryMap(new EsMapUtil().putStr("userName.trueName", employee.new QueryRelation<String>("張三", EntityEs.MUST)));
 List<EmployeeEs> employeeEsList = esService.queryList(employee);
 System.out.println(employeeEsList);
 System.out.println("-----es測試end...");
}

先準備一條測試資料插入

看下上面查詢語句的結果:

由於userName的二級屬性trueName的型別是keyword,所以是term精確查詢

對比:

@Test
public void querySecondList() throws IllegalAccessException, NoSuchFieldException {
    System.out.println("-----es測試start...");
 EmployeeEs employee = new EmployeeEs();
 employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserName, employee.new QueryRelation<String>("張三", EntityEs.MUST)));
 //employee.setFieldQueryMap(new EsMapUtil().putStr("userName.trueName", employee.new QueryRelation<String>("張三", EntityEs.MUST)));
 List<EmployeeEs> employeeEsList = esService.queryList(employee);
 System.out.println(employeeEsList);
 System.out.println("-----es測試end...");
}

13、分頁查詢

@Test
public void queryPage() throws IllegalAccessException, NoSuchFieldException {
    System.out.println("-----es測試start...");
    EmployeeEs employee = new EmployeeEs();
    List<EntityEs.QueryRelation> queryList = Stream.of(employee.new QueryRelation<String>("張三", EntityEs.SHOULD), employee.new QueryRelation<String>("李四", EntityEs.SHOULD)).collect(Collectors.toList());
    employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserName, queryList));
    //分頁
    employee.setPageNumber(0);
    employee.setPageSize(2);
    List<EmployeeEs> employeeEs = esService.queryPage(employee);
    System.out.println(employeeEs);
    System.out.println("-----es測試end...");
}

14、遊標分頁查詢

@Test
public void queryScrollPage() throws IllegalAccessException, NoSuchFieldException {
    System.out.println("-----es測試start...");
    String scrollId = "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAADJEWMVFWMVBnb1ZSZDZsV1k2Y2JjLVlldw==";
    EmployeeEs employee = new EmployeeEs();
    if (StringUtils.isNotBlank(scrollId)) {
        //如果前端有傳scrollId
        employee.setScrollId(scrollId);
        List<EmployeeEs> employeeEs = esService.queryScrollPage(employee);
        System.out.println(employeeEs);
    } else {
        List<EntityEs.QueryRelation> queryList = Stream.of(employee.new QueryRelation<String>("張三", EntityEs.SHOULD, 20F), employee.new QueryRelation<String>("李四", EntityEs.SHOULD)).collect(Collectors.toList());
        employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserName, queryList));
        //每頁頁數
        employee.setPageSize(4);
        List<EmployeeEs> employeeEs = esService.queryScrollPage(employee);
        System.out.println(employeeEs);
    }
    System.out.println("-----es測試end...");
}

15、多層bool套bool查詢

@Test
public void queryMuiltiLayer() throws IllegalAccessException, NoSuchFieldException {
    System.out.println("-----es測試start...");
    EmployeeEs employee = new EmployeeEs();
    
    //多層bool查詢
    employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserSex, employee.new QueryRelation("男", EntityEs.MUST)));
    List<EntityEs.QueryRelation> multiLayerUserAgeList = Stream.of(employee.new QueryRelation(10, EntityEs.SHOULD), employee.new QueryRelation(18, EntityEs.SHOULD)).collect(Collectors.toList());
    EntityEs.MultiLayerRelation multiLayerRelation = employee.new MultiLayerRelation(EntityEs.MUST, new EsMapUtil().put(EmployeeEs::getUserAge, multiLayerUserAgeList));
    employee.setMultiLayerQueryList(Lists.newArrayList(multiLayerRelation));
 
    List<EmployeeEs> userEs = esService.queryList(employee);
    System.out.println(userEs);
    System.out.println("-----es測試end...");
}

示例:查詢年齡為10或者18歲的男性員工

//錯誤案例
EmployeeEs employee = new EmployeeEs();
List<EntityEs.QueryRelation> ageList = Lists.newArrayList(employee.new QueryRelation(10, EntityEs.SHOULD), employee.new QueryRelation(18, EntityEs.SHOULD));
employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserSex, employee.new QueryRelation("男", EntityEs.MUST))
    .put(EmployeeEs::getUserAge,ageList)
);
List<EmployeeEs> userEs = esService.queryList(employee);
System.out.println(userEs);

結果年齡為20的趙六也被查詢了出來。原因是:當should遇到must和filter時就不是或者了,而是應該的意思。可通過must巢狀一層解決。

修改為多層bool查詢

EmployeeEs employee = new EmployeeEs();
//修改為多層bool查詢
employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserSex, employee.new QueryRelation("男", EntityEs.MUST)));
List<EntityEs.QueryRelation> multiLayerUserAgeList = Stream.of(employee.new QueryRelation(10, EntityEs.SHOULD), employee.new QueryRelation(18, EntityEs.SHOULD)).collect(Collectors.toList());
EntityEs.MultiLayerRelation multiLayerRelation = employee.new MultiLayerRelation(EntityEs.MUST, new EsMapUtil().put(EmployeeEs::getUserAge, multiLayerUserAgeList));
employee.setMultiLayerQueryList(Lists.newArrayList(multiLayerRelation));
 
List<EmployeeEs> userEs = esService.queryList(employee);

 

 

16、高亮查詢

@Test
public void highlightTest() throws NoSuchFieldException, IllegalAccessException {
    System.out.println("-----es測試start...");
    EmployeeEs employee = new EmployeeEs();
    employee.setFieldQueryMap(new EsMapUtil().put(EmployeeEs::getUserName, employee.new QueryRelation<String>("張三", EntityEs.MUST))
        .put(EmployeeEs::getRemarks, employee.new QueryRelation<String>("天氣", EntityEs.MUST)));
    employee.setHighlightFields(Lists.newArrayList("remarks"));
    //預設<em></em>,可自定義高亮標籤
    employee.setPreTags("<h1>");
    employee.setPostTags("</h1>");
    List<EmployeeEs> employeeEs = esService.queryList(employee);
    System.out.println(employeeEs);
    System.out.println("-----es測試end...");
}

17、聚合查詢

@Test
public void queryForAggregation() throws NoSuchFieldException, IllegalAccessException {
    System.out.println("-----queryForAggregation-es測試start...");
    EmployeeEs employee = new EmployeeEs();
    employee.setAggregationMap(new EsMapUtil().put(EmployeeEs::getUserAge, EntityEs.COUNT));
    Map employeeEsAggMap = esService.queryForAggregation(employee);
    System.out.println("返回結果:" + employeeEsAggMap);
    System.out.println("-----queryForAggregation-es測試end...");
}

將上面的EntityEs.COUNT改為EntityEs.SUM 求和執行結果:

 

相關文章