在迭代交接项目的时候,发现原项目用的是org.elasticsearch.common.xcontent,其本质还是在拼接DSL,代码的可读性和可维护性都比较差,如果不借助SQL转DSL工具,对于不太熟悉语法的人来说,调试耗时,故改成ES-SQL。
本项目的es版本是6.8.x,所以支持es的sql语法,代码sample如下:

ES-SQL

  1. package net.xinhuamm.nmedia.cms.sample;
  2. import okhttp3.Call;
  3. import okhttp3.Credentials;
  4. import okhttp3.MediaType;
  5. import okhttp3.OkHttpClient;
  6. import okhttp3.Request;
  7. import okhttp3.RequestBody;
  8. import okhttp3.Response;
  9. import java.io.IOException;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. import java.util.concurrent.TimeUnit;
  13. /**
  14. * @author zhanqian
  15. * @Date 2021/6/21 13:45
  16. * @Description TODO
  17. */
  18. public class ElasticSqlSample {
  19. static final int timeOut = 10 * 1000;
  20. public static void main(String[] args) throws IOException {
  21. Map<String, Object> param = new HashMap<>();
  22. // param.put("query", "select name, searchAll FROM cms_index where match(type, '4') and match(lesseeId, '44cfaf082c804f168584b632fa16610e') and match(searchAll, '1') and (match(name, '1') or match(creator, '1') or match(tags, '1') or match(content, '1') or match(description, '1') or match(labelAiText, '1') or match(personAiText, '1')) ");
  23. param.put("query", "select name FROM cms_index where match(type, '4') and match(lesseeId, '44cfaf082c804f168584b632fa16610e') and match(searchAll, '融媒') and match('name^6,creator^5,tags^4,content^3,description^2,labelAiText^2,personAiText^2', '融媒') limit 1");
  24. param.put("fetch_size", 2);
  25. String url = "http://localhost:9200/_xpack/sql?format=json";
  26. System.out.println(okhttpPost(url, param));
  27. }
  28. /**
  29. * Post请求 带Authorization认证
  30. * */
  31. public static String okhttpPost(String get_url, Map<String, Object> map){
  32. OkHttpClient client = new OkHttpClient.Builder()
  33. .connectTimeout(10000L, TimeUnit.MILLISECONDS)
  34. .readTimeout(10000L, TimeUnit.MILLISECONDS)
  35. .build();
  36. final String credential = Credentials.basic("account", "password");
  37. String result = "";
  38. // RequestBody fromBody = generateParametersForPost(get_data).build();
  39. MediaType JSON = MediaType.parse("application/json; charset=utf-8");
  40. RequestBody fromBody = RequestBody.create(JSON, com.alibaba.fastjson.JSON.toJSONString(map));
  41. Request request = new Request.Builder()
  42. .url(get_url)
  43. .header("Authorization", credential)
  44. .header("Accept", "application/json")
  45. .post(fromBody)
  46. .build();
  47. Call call = client.newCall(request);
  48. try {
  49. Response response = call.execute();
  50. //判断是否成功
  51. if (response.isSuccessful()){
  52. result = response.body().string();
  53. } else {
  54. return "请求失败";
  55. }
  56. System.out.println(result);
  57. } catch (Exception e) {
  58. e.printStackTrace();
  59. System.out.println("网络GET请求失败!提示信息:"+e.getMessage());
  60. }
  61. return result;
  62. }
  63. }

单独抽取出SQL:

  1. select name
  2. FROM cms_index
  3. where match(type, '4')
  4. and match(lesseeId, '44cfaf082c804f168584b632fa16610e')
  5. and match(searchAll, '融媒')
  6. and match('name^6,creator^5,tags^4,content^3,description^2,labelAiText^2,personAiText^2', '融媒')
  7. limit 1
  1. sql中支持es DSL语义match等操作
  2. 如果想用boost,搜索权重。参考:'name^6,creator^5,tags^4,content^3,description^2,labelAiText^2,personAiText^2', '融媒' 表示该6个字段都匹配’融媒’字段,并且以’^’后的数字即为权重

目前REST API支持的参数不是很多,其他参数可以参考上面的文档,如分页超时时间等参数:

  1. param.put("fetch_size", 2);

ES-SQL返回数据封装

ES-SQL format=json的情况下,返回的json格式不太好封装成对象,故借助CGLIB,根据返回的动态字段,创建动态对象。

CGLIB

  1. package net.xinhuamm.nmedia.cms.model;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.junit.Test;
  4. import org.springframework.cglib.beans.BeanGenerator;
  5. import org.springframework.cglib.beans.BeanMap;
  6. import java.util.HashMap;
  7. import java.util.Map;
  8. /**
  9. * @author zhanqian
  10. * @Date 2021/6/21 19:52
  11. * @Description cglib动态生成bean
  12. */
  13. @Slf4j
  14. public final class CglibBean {
  15. /**
  16. * 实体object
  17. */
  18. private Object object = null;
  19. private BeanMap beanMap = null;
  20. public CglibBean() {
  21. super();
  22. }
  23. public CglibBean(Map<String, Class<?>> propertyMap) {
  24. this.object = generateBean(propertyMap);
  25. this.beanMap = BeanMap.create(this.object);
  26. }
  27. /**
  28. * 给bean属性赋值
  29. *
  30. * @param property 属性名
  31. * @param value 值
  32. */
  33. public void setValue(String property, Object value) {
  34. beanMap.put(property, value);
  35. }
  36. /**
  37. * 通过属性名得到属性值
  38. *
  39. * @param property 属性名
  40. * @return 值
  41. */
  42. public Object getValue(String property) {
  43. return beanMap.get(property);
  44. }
  45. /**
  46. * 将成员属性map生成对象类
  47. *
  48. * @param propertyMap
  49. * @return
  50. */
  51. public Object generateBean(Map<String, Class<?>> propertyMap) {
  52. if (propertyMap == null || propertyMap.isEmpty()) {
  53. return null;
  54. }
  55. BeanGenerator generator = new BeanGenerator();
  56. for (Map.Entry<String, Class<?>> entry : propertyMap.entrySet()) {
  57. generator.addProperty(entry.getKey(), entry.getValue());
  58. }
  59. return generator.create();
  60. }
  61. @Test
  62. public void test() throws ClassNotFoundException {
  63. Map<String, Class<?>> propertyMap = new HashMap<>();
  64. propertyMap.put("id", Class.forName("java.lang.Long"));
  65. propertyMap.put("name", Class.forName("java.lang.String"));
  66. CglibBean bean = new CglibBean(propertyMap);
  67. bean.setValue("id", 1);
  68. bean.setValue("name", "zhanqian");
  69. log.info("id:{}, name:{}", bean.getValue("id"), bean.getValue("name"));
  70. }
  71. }

EsHelper

  1. package net.xinhuamm.nmedia.cms.helper;
  2. import com.alibaba.fastjson.JSONArray;
  3. import com.alibaba.fastjson.JSONObject;
  4. import net.xinhuamm.nmedia.cms.model.CglibBean;
  5. import net.xinhuamm.nmedia.common.util.StringUtils;
  6. import org.junit.Test;
  7. import java.io.BufferedReader;
  8. import java.io.File;
  9. import java.io.FileReader;
  10. import java.io.IOException;
  11. import java.util.ArrayList;
  12. import java.util.HashMap;
  13. import java.util.List;
  14. import java.util.Map;
  15. /**
  16. * @author zhanqian
  17. * @Date 2021/6/21 20:06
  18. * @Description TODO
  19. */
  20. public class EsHelper {
  21. /**
  22. * es返回数据类型和java数据类型映射
  23. */
  24. Map<String, Class<?>> classPropertyMap = new HashMap<String, Class<?>>() {
  25. {
  26. try {
  27. put("boolean", Class.forName("java.lang.Boolean"));
  28. put("byte", Class.forName("java.lang.Byte"));
  29. put("short", Class.forName("java.lang.Short"));
  30. put("long", Class.forName("java.lang.Long"));
  31. put("integer", Class.forName("java.lang.Integer"));
  32. put("string", Class.forName("java.lang.String"));
  33. put("double", Class.forName("java.lang.Double"));
  34. put("float", Class.forName("java.lang.Float"));
  35. put("half_float", Class.forName("java.lang.Float"));
  36. put("text", Class.forName("java.lang.String"));
  37. put("keyword", Class.forName("java.lang.String"));
  38. put("ip", Class.forName("java.lang.String"));
  39. } catch (ClassNotFoundException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. };
  44. /**
  45. * 封装es sql返回值
  46. * @param json 聚合查询的返回值参考 @see "聚合查询返回值.txt"
  47. * @return 按每一行row为单位对象,返回list
  48. */
  49. public List<CglibBean> getEsSqlResult(String json) {
  50. JSONObject jsonObject = JSONObject.parseObject(json);
  51. JSONArray columns = jsonObject.getJSONArray("columns");
  52. Map<String, Class<?>> propertyMap = new HashMap<>();
  53. int size = columns.size();
  54. String[] nameProps = new String[size];
  55. Class[] typeProps = new Class[size];
  56. for (int i = 0; i < size; i++) {
  57. JSONObject column = (JSONObject)columns.get(i);
  58. String name = column.getString("name");
  59. Class<?> type = classPropertyMap.get(StringUtils.lowerCase(column.getString("type")));
  60. propertyMap.put(name, type);
  61. nameProps[i] = name;
  62. typeProps[i] = type;
  63. }
  64. List<CglibBean> list = new ArrayList<>();
  65. //通过rows数据,生成对象
  66. JSONArray rows = jsonObject.getJSONArray("rows");
  67. for (Object object : rows) {
  68. //通过columns的元数据属性,动态生成类
  69. CglibBean cglibBean = new CglibBean(propertyMap);
  70. JSONArray row = (JSONArray)object;
  71. for (int i = 0; i < size; i++) {
  72. Object value = row.get(i);
  73. if (value == null) {
  74. continue;
  75. }
  76. Class typeProp = typeProps[i];
  77. // 分开判断是因为部分类型,强转会失败,如es的colums中返回是Long类型,对应的rows值在java中为Integer,强行set到Long类型中会报错
  78. if (typeProp == Long.class && value != null) {
  79. cglibBean.setValue(nameProps[i], Long.parseLong(value.toString()));
  80. } else {
  81. cglibBean.setValue(nameProps[i], value);
  82. }
  83. }
  84. list.add(cglibBean);
  85. }
  86. return list;
  87. }
  88. @Test
  89. public void test() throws IOException {
  90. StringBuffer path = new StringBuffer(System.getProperty("user.dir"));
  91. path.append(File.separator + "doc" + File.separator + "agg-test.json");
  92. File file = new File(path.toString());
  93. BufferedReader reader = null;
  94. StringBuffer sbf = new StringBuffer("");
  95. try {
  96. reader = new BufferedReader(new FileReader(file));
  97. String tmp;
  98. while ((tmp = reader.readLine()) != null) {
  99. sbf.append(tmp);
  100. }
  101. reader.close();
  102. } finally {
  103. if (reader != null) {
  104. reader.close();
  105. }
  106. }
  107. String json = sbf.toString();
  108. List<CglibBean> esSqlResult = new EsHelper().getEsSqlResult(json);
  109. System.out.println(esSqlResult);
  110. }
  111. public static void main(String[] args) throws IOException {
  112. System.out.println();
  113. }
  114. }

文献:
[1] elasitic官方网站6.8版本[OL].https://www.elastic.co/guide/en/elasticsearch/reference/6.8/xpack-sql.html