import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.sitech.logengine.commons.page.PageInfo; import com.sitech.logengine.commons.page.PageResult; import com.sitech.logengine.commons.util.DateUtils; import com.sitech.logengine.httpclient.util.HttpClientUtil; import com.sitech.logengine.httpclient.util.HttpResult; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.lang.reflect.Field; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.TimeUnit; /** * es查詢工具類 * 支持sql語句查詢、分頁查詢 * TODO 目前如果用到這個類必須要依賴於 httpclient-utils這個模塊 * 並且spring必須配置掃描這個類HttpClientUtil所在的包 * */ @Service @Slf4j public class ElasticsearchQueryUtil { private static final Logger log = LoggerFactory.getLogger(ElasticsearchUtil.class); String ES_SQL_QUERY_KEY = "query"; String ES_SQL_COLUMNS_KEY = "columns"; String ES_SQL_ROWS_KEY = "rows"; private static final String FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; private static final ThreadLocal<SimpleDateFormat> threadLocalDateFormat = new ThreadLocal<SimpleDateFormat>(); @Autowired private HttpClientUtil httpClientService; @Autowired private ElasticsearchConfig elasticsearchConfig; @Autowired private String esSqlQueryUrl; @Autowired private RestHighLevelClient client; public <T> List<T> queryByTemplateSql(String sql, Class<T> className) throws IOException { if (StringUtils.isBlank(sql)) { return new ArrayList<>(); } log.debug("ElasticSearch final sql {}", sql); Map<String, Object> query = new HashMap<>(); query.put(ES_SQL_QUERY_KEY, sql); HttpResult httpResult = doPostJson(esSqlQueryUrl, JSONObject.toJSONString(query)); JSONObject response = JSONObject.parseObject(httpResult.getContent()); List<Columns> columns = JSONObject.parseArray(response.getString(ES_SQL_COLUMNS_KEY), Columns.class); List<List> rows = JSONObject.parseArray(response.getString(ES_SQL_ROWS_KEY), List.class); if(columns == null || rows == null){ //如果查詢報錯或者沒有數據則返回 null return null; } ElasticSearchModel esModel = new ElasticSearchModel(columns, rows); return esModelToJavaObject(esModel, className); } /** * 目前的sql語句還不支持分頁查詢,只支持限定返回多少條記錄數,如果需要分頁查詢請使用pageQuery或者pageQuery開頭的方法 * esSql查詢 sql語句中需要用到參數的地方, 請用#{param} 的形式修飾,#{}符號中間的單詞就是第二個參數中的某個鍵 * @param sql sql語句 * @param params 參數 一個key對應一個參數 * @param className 目標類的class * @return 如果查詢報錯或者沒有數據則返回 null */ public <T> List<T> queryBySql(String sql, Map<String, Object> params, Class<T> className) throws IOException { if (StringUtils.isBlank(sql)) { return new ArrayList<>(); } log.debug("ElasticSearch ori sql {} \n sql params {}", sql,JSONObject.toJSONString(params)); // 轉換sql參數 if (params != null && params.size() != 0) { for (Map.Entry<String,Object> entry : params.entrySet()){ sql = sql.replace("#{" + entry.getKey() + "}", entry.getValue().toString()); } } log.debug("ElasticSearch final sql {}", sql); Map<String, Object> query = new HashMap<>(); query.put(ES_SQL_QUERY_KEY, sql); HttpResult httpResult = doPostJson(esSqlQueryUrl, JSONObject.toJSONString(query)); JSONObject response = JSONObject.parseObject(httpResult.getContent()); List<Columns> columns = JSONObject.parseArray(response.getString(ES_SQL_COLUMNS_KEY), Columns.class); List<List> rows = JSONObject.parseArray(response.getString(ES_SQL_ROWS_KEY), List.class); if(columns == null || rows == null){ //如果查詢報錯或者沒有數據則返回 null return null; } ElasticSearchModel esModel = new ElasticSearchModel(columns, rows); return esModelToJavaObject(esModel, className); } /** * 使用sql語句查詢總數 * esSql查詢 sql語句中需要用到參數的地方, 請用#{param} 的形式修飾,#{}符號中間的單詞就是第二個參數中的某個鍵 * @param sql * @param params * @return * @throws IOException */ public Long queryCountBySql(String sql, Map<String, Object> params) throws IOException { List<Long> countLong = this.queryBySql(sql,params,Long.class); if(countLong == null || countLong.isEmpty()){ return 0L; } return countLong.get(0); } /** * 目前es的sql語句還不支持分頁查詢,如果需要分頁查詢,請用此方法 * @param pageInfo * @param queryBuilder 查詢條件builder需要構造好 * @param searchRequest 查詢索引需要構造好 * @param clazz * @param <T> * @return * @throws IOException */ public <T> PageResult<T> pageQuery(PageInfo pageInfo, QueryBuilder queryBuilder, SearchRequest searchRequest, Class<T> clazz) throws IOException { JSONObject resJSON = new JSONObject(); JSONArray jsonArr = new JSONArray(); //最大不能超過 10000條,可以通過設置索引的"max_result_window": "10000000"來解決,不過效率不知如何 int currentPage = pageInfo.getCurrentPage()-1; int pageSize = pageInfo.getPageSize(); int fromSize = currentPage*pageSize; // 創建查詢函數構造對象 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.query(queryBuilder); // 把父查詢對象放入函數構造對象中 sourceBuilder.from(fromSize); // 參數範圍起 sourceBuilder.size(pageSize); // 參數範圍始 sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));// 設置超時時間 sourceBuilder.trackTotalHits(true); // 取消默認最大查詢數量上限(默認10000) searchRequest.source(sourceBuilder);// 把查詢函數構造對象注入查詢請求中 // 創建響應對象 SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); SearchHits searchHits = searchResponse.getHits();//獲取響應中的列表數據 long total = searchHits.getTotalHits().value;//獲取響應中的列表數據總數 pageInfo.calculate((int)total); List<T> contents = new ArrayList<>(); for(SearchHit hit:searchHits.getHits()){// 遍歷構造返回JSON,以下不再多說 JSONObject dataJSON = new JSONObject(); String tempRes = hit.getSourceAsString(); dataJSON = JSONObject.parseObject(tempRes); jsonArr.add(dataJSON); contents.add(JSON.parseObject(tempRes,clazz)); } // resJSON.put("resArr", jsonArr); PageResult pageResult = new PageResult(); pageResult.setPageInfo(pageInfo); pageResult.setContents(contents); return pageResult; } /** * 目前es的sql語句還不支持分頁查詢,如果需要分頁查詢,請用此方法 * @param pageInfo * @param queryBuilder 查詢條件builder需要構造好 * @param clazz * @param indices * @param <T> * @return * @throws IOException */ public <T> PageResult<T> pageQuery(PageInfo pageInfo, QueryBuilder queryBuilder, Class<T> clazz, String... indices) throws IOException{ SearchRequest searchRequest = new SearchRequest(indices); return this.pageQuery(pageInfo,queryBuilder,searchRequest,clazz); } /** * 向指定 URL 發送POST方法的請求 * * @param url * 發送請求的 URL * @param param * 請求參數,請求參數應該是 name1=value1&name2=value2 的形式。 * @return 所代表遠程資源的響應結果 * @throws Exception */ public HttpResult doPostJson(String url, String param) throws IOException { return httpClientService.doPostJson(url,param); } /** * model轉換實體類 * @param esModel * @param className * @param <T> * @return */ private <T> List<T> esModelToJavaObject(ElasticSearchModel esModel, Class<T> className) { Field[] fields = className.getDeclaredFields(); List<T> result = new ArrayList<>(esModel.getRows().size()); esModel.getRows().forEach(row -> { try { // 判斷是不是基本數據類型 if (!isBasicType(className)) { // 新增實體 T t = className.newInstance(); for (int i = 0; i < fields.length; i++) { // 設置該屬性可以修改 fields[i].setAccessible(true); for (int j = 0; j < esModel.getColumns().size(); j++) { // 判斷屬性名和es返回的列名一致 if (fields[i].getName().equals(esModel.getColumns().get(j).getName())) { String type = esModel.getColumns().get(j).getType(); Object val = row.get(j); // if(val == null || "null".equals(val)){ // //值爲null字符串的則不設置 // continue; // } // 時間轉換Time // 這裏的弊端。時間轉換沒有辦法識別。我只能通過這個列名有沒有Time這個單詞來判斷是不是時間 // if (esModel.getColumns().get(j).getName().toLowerCase().contains("time")) // type = "time"; Object o = castValue(type, val); // String esColumnType = esModel.getColumns().get(j).getName().toLowerCase(); if (type.contains("datetime") || type.contains("date")){ String filedTypeName = fields[i].getType().getTypeName(); if(filedTypeName.contains("String")){ Date date = (Date)o; fields[i].set(t,DateUtils.dateToStringDefault(date)); }else{ fields[i].set(t, o); } }else{ fields[i].set(t, o); } } } } result.add(t); }else { String type = esModel.getColumns().get(0).type; Object val = row.get(0); // 基本數據類型 Object o = castValue(type, val); result.add((T) o); } } catch (InstantiationException | IllegalAccessException e) { log.warn("esModel to java object fail ",e); } }); return result; } /** * Es返回的類型轉換成數據類型包裝類 * @param type es結果集中返回的類型 * @param val 需要轉換的值 */ private Object castValue(String type, Object val) { if (StringUtils.isBlank(type) || val == null || StringUtils.isBlank(val.toString())) { return null; } if ("text".equals(type) || "keyword".equals(type)) { try { return val.toString(); // 防止時間轉換失敗 }catch (IllegalArgumentException e) { log.warn("type {} cast fail value {} ",type,val,e); } }else if ("long".equals(type)) { return Long.parseLong(val + ""); } else if ("double".equals(type)) { return Double.parseDouble(val + ""); } else if ("float".equals(type)) { return Float.parseFloat(val + ""); }else if ("datetime".equals(type)) { // 防止時間轉換錯誤 try { if(val == null || val.toString().isEmpty()){ return null; } return covertDateStrToDate(val.toString()); // 時間戳轉換 }catch (ParseException e) { log.warn("type {} cast fail value {} ",type,val,e); } } return null; } public static Date covertDateStrToDate(String dateStr) throws ParseException { SimpleDateFormat sdf = threadLocalDateFormat.get(); if (sdf == null){ sdf = new SimpleDateFormat(FORMAT); } return sdf.parse(dateStr); } /** * 判斷是不是基本數據類型包裝類 * @params */ private static boolean isBasicType(Class className) { if (className == null) { return false; } else if(className.equals(String.class)) { return true; } else if(className.equals(Integer.class)) { return true; } else if(className.equals(Long.class)) { return true; } else if(className.equals(Short.class)) { return true; } else if(className.equals(Double.class)) { return true; } else if(className.equals(Float.class)) { return true; } else if(className.equals(Character.class)) { return true; } else if(className.equals(Byte.class)) { return true; } return false; } /** * ES返回數據Model類 * */ @Data static class ElasticSearchModel { private List<Columns> columns; private List<List> rows; public ElasticSearchModel(List<Columns> columns, List<List> rows) { this.columns = columns; this.rows = rows; } public List<Columns> getColumns() { return columns; } public void setColumns(List<Columns> columns) { this.columns = columns; } public List<List> getRows() { return rows; } public void setRows(List<List> rows) { this.rows = rows; } } /** * EsQuery中使用sql查詢。他會返回的列 * @author [email protected] * @time 23:13 */ @Data static class Columns{ /** * 列名 */ private String name; /** * 列的類型 */ private String type; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } } }
ElasticsearchQueryUtil 查詢工具
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.