在咱们公司,大多数Java开发工程师在项目中都有运用Elasticsearch的经历。一般,他们会经过引进第三方工具包或运用Elasticsearch Client等方式来进行数据查询。可是,当涉及到根据Elasticsearch Rest API的/_sql?format=json接口时,即使是有Elasticsearch运用经历的开发人员也或许感到困惑。这是因为在开发进程中,咱们一般习惯于运用根据JSON界说的DSL言语,运用Elasticsearch的规范工具包、Query、Filter、termsQuery等办法,或运用scrollId来查询很多数据集。

在开发某个客户定制项目进程中,,客户提出了期望能够依据SQL查询设定的条件来执行数据查询的需求。鉴于此种情况,咱们必须放弃原先常用的DSL言语,而转向运用/_sql?format=json接口实行Elasticsearch数据检索。/_sql?format=json接口进行Elasticsearch数据查询的进程往往在整个项目规划上依赖于Elasticsearch Rest API。这种方式的挑战在于,开发者需求自行处理scrollId的迭代查询,因为没有第三方工具来主动封装这一进程。这意味着咱们需求手动操控scrollId,每次查询最多10000条数据,并重复运用该接口直到获取悉数所需数据。

本文将结合项目开发进程中的实践经历,具体介绍/_sql?format=json接口的调用机制和回来值格式,深化探讨迭代器形式在实践Elasticsearch查询中的运用。文章内容包含:

  • Elasticsearch SQL Rest API的/_sql?format=json调用机制及其回来值格式。
  • 迭代器形式的实践运用:包含类结构剖析、办法界说及Elasticsearch查询实例。

本篇文章的编列旨在将常见的规划形式中的“迭代器形式”与“Elasticsearch RestAPI 查询实战”结合起来。这样的组织虽然提高了代码了解的难度,但对于经历稍显不足的开发人员来说,将是一个极好的挑战和学习时机。

1 ElasticSearch SQL Rest API 机制介绍

1.1 SQL Rest API接口信息和入参

POST /_sql?format=json
{
  "query": "SELECT * FROM library ORDER BY page_count DESC",
  "fetch_size": 5
}

经过剖析图示,咱们能够具体地了解该API的工作方式。此API采用POST办法拜访,其统一资源标识符(URI)设置为/_sql?format=json。在发送恳求时,RequestBody首要包含两个要害特点:

  1. “query”特点:其值为SQL句子。这里运用的SQL句子遵从规范的SQL语法规则,与MySQL的语法极为相似,使得了解传统数据库开发的工程师更容易上手。
  2. “fetch_size”特点:这个值为数字类型,用于指定回来成果的限制数量,类似于SQL中的LIMIT子句。

此外,该API允许经过format=json参数来指定回来数据的格式。默认情况下,这一参数设置回来格式为JSON,但API相同支撑其他格式,如CSV、TSV、TEXT、YAML、CBOR和SMILE。在咱们的项目实践中,JSON格式因其易于解析和通用性而被频繁运用。

1.2 SQL Rest API回来值

{
  "columns": [
    {"name": "author",       "type": "text"},
    {"name": "name",         "type": "text"},
    {"name": "page_count",   "type": "short"},
    {"name": "release_date", "type": "datetime"}
  ],
  "rows": [
    ["Peter F. Hamilton",  "Pandora's Star",       768, "2004-03-02T00:00:00.000Z"],
    ["Vernor Vinge",       "A Fire Upon the Deep", 613, "1992-06-01T00:00:00.000Z"],
    ["Frank Herbert",      "Dune",                 604, "1965-06-01T00:00:00.000Z"],
    ["Alastair Reynolds",  "Revelation Space",     585, "2000-03-15T00:00:00.000Z"],
    ["James S.A. Corey",   "Leviathan Wakes",      561, "2011-06-02T00:00:00.000Z"]
  ],
  "cursor": "sDXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAAEWWWdrRlVfSS1TbDYtcW9lc1FJNmlYdw==:BAFmBmF1dGhvcgFmBG5hbWUBZgpwYWdlX2NvdW50AWYMcmVsZWFzZV9kYXRl+v///w8="
}

剖析API的呼应内容,咱们能够明确地识别出其由三个首要部分组成:

  1. columns:这部分包含了一切回来字段的名称和类型。它为数据解析和后续操作供给了必要的结构信息。
  2. rows:此部分包含了查询成果的具体值,其排列次序与columns部分中界说的字段次序严厉对应。这种一致性确保了数据的完整性和易用性。
  3. cursor:这是完结分页功能的要害元素。cursor的存在表明,当时回来的数据集仅仅满足查询条件的一部分,因为fetch_size的设置,初度呼应只包含了限定数量的数据。要拜访后续的数据页,咱们需求将cursor值回传至API。这种机制允许高效地遍历很多数据,而不用一次性加载悉数成果。

1.3 回传cursor,获取其他的分页

持续运用前述数据,若咱们需求拜访查询成果的第二页或第三页,能够简单地将cursor值用作RequestBody,并再次调用相同的接口。这个进程遵从与初次查询相同的“配方”。

POST /_sql?format=json
{
  "cursor": "sDXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAAEWWWdrRlVfSS1TbDYtcW9lc1FJNmlYdw"
 }

在后续的呼应中,API一般只回来rowscursor特点。这是因为columns特点,即字段的名称和类型,现已在第一次呼应中供给,无需重复回来。当咱们抵达查询成果的最终一页时,呼应中将只包含rows特点,不再包含cursor,表明一切数据已被彻底检索。

{
  "rows": [
    ["Peter F. Hamilton",  "Pandora's Star",       768, "2004-03-02T00:00:00.000Z"],
    ["Vernor Vinge",       "A Fire Upon the Deep", 613, "1992-06-01T00:00:00.000Z"],
    ["Frank Herbert",      "Dune",                 604, "1965-06-01T00:00:00.000Z"],
    ["Alastair Reynolds",  "Revelation Space",     585, "2000-03-15T00:00:00.000Z"],
    ["James S.A. Corey",   "Leviathan Wakes",      561, "2011-06-02T00:00:00.000Z"]
  ]
}

例如,若要查询总共49000条数据,流程大致如下:

  1. 初次查询:获取一切字段(columns)和首批10000条数据(rows),一起取得一个cursor值。
  2. 第2次查询:运用已取得的cursor值作为RequestBody,检索下一批10000条数据,并再次取得cursor值。
  3. 第三次和第四次查询:重复第2次查询的过程。
  4. 第五次查询:最终获取剩余的9000条数据,此刻呼应不再包含cursor,表示查询已完结。

在编写对应的代码逻辑时,能够考虑运用递归或者while循环来判断cursor值是否为null,然后决议是否持续查询。还或许有其他编程办法可用于完结这一逻辑。

2 迭代器形式实战

2.1 UML类成果分化、办法界说以及实战

迭代器形式是一种常用的规划形式,其首要目的是对数据结构中的一切元素进行逐一遍历,直到一切元素均被拜访一次。大多数Java开发人员在学习Java SE时,经过List数据结构就现已触摸到了迭代器的概念。运用List的迭代器遍历列表元素一般是一项基本且简单的任务。

深化实战:ElasticSearch的Rest API与迭代器形式在高效查询中的运用

咱们将首先学习迭代器形式的UML类图,然后针对每个人物进行具体类的创立和办法的界说。迭代器形式的UML类图首要包含四个人物,但咱们只需求创立其中的三个:

  1. Iterator(笼统迭代器) :界说了拜访和遍历元素的接口。
  2. ConcreteIterator(具体迭代器) :完结迭代器接口,负责完结对容器元素的实践遍历。
  3. Aggregate(笼统容器) :供给创立具体迭代器目标的接口。
  4. ConcreteAggregate(具体容器) :完结了创立具体迭代器目标的办法。

在UML类图中,除Iterator(JDK的java.util.Iterator)外,咱们需求完结其他三个人物。中心的逻辑首要会集在ConcreteIterator中。此外,咱们还需求界说一个实体类来接收Elasticsearch SQL Rest API的呼应数据,该实体类应包含columnsrowscursor特点,并供给相应的getter和setter办法。接下来,咱们将深化探讨UML类结构的分化和办法界说。

  1. 创立与ESSOL RestAPI回来值对应的实体目标逐个EsResponseData
    该目标并不是UML类图中的人物,可是要处理ES SQL Rest API 的回来值,此类必不可少。代码和注释如下:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EsResponseData {
   //一切的字段
   private List> columns;
   //回来的数据值
   private List> rows;
   //用于分页的 cursor 值
   private String cursor;
}
  1. 创立Aggregate笼统容器一-EsSqlQueryInterface
    笼统容器人物负资供给创立具体迭代器人物的笼统办法,咱们运用泛型T保证了该类的扩展性。咱们界说的笼统 iterator 办法,是为了new 一个具体的选代器目标,当然了,这部分逻辑会在子类中进行完结。代码如下:
public interace EsSqlQueryInterface {
   public T iterator();
}
  1. 创立ConcreteAggregate具体容器-EsSqlQuery
    具体容器完结容器接口界说的笼统办法,创立迭代器目标。代码及注释如下:
@Data
@JsonIgnoreProperties
public class EsSqlQuery implements EsSqlQueryInterface{
   private String query;
   private Long fetchSize;
   private String cursor;
   public EsSqlQuery(String cursor) {
       this.cursor = cursor;
   }
   public EsSqlQuery(String query, Long fetchSize) {
       this.query = query;
       this.fetchSize = fetchSize;
   }
   public EsQueryIterator iterator(){
       return new EsQueryIterator(this.query, this.fetchSize);
   }
}
  1. 创立ConcreteIterator具体迭代器—EsQueryIterator
    此处代码是中心的代码,需求完结 java.util.Iterator 接口,并覆写 hasNext 以及 next办法,一起需求增加自己的 scrolINext 办法用于判断 cursor 是否为 null.假如 cursor 为 null,则阐明现已选代完结
public class EsQueryIterator implements Iterator> {
   //记载当时cursor分页
   private String cursor;
   //记载查询的columns,因为只有第一次查询才会回来columns数据
   private List columns;
   //将ES SQL Rest API的回来值封装到List中,以便处理回来值
   Iterator> iterator;
   //此处咱们从简而行,不再进行@Autowire注入,把更多的精力放到迭代器形式中
   RestTemplate restTemplate = new RestTemplate();
   //构造函数进行第一次查询,而且初始化咱们后续需求运用的 columns 和 iterator 和 cursor
   public EsQueryIterator(String query, Long fetchSize) {
       EsResponseData esResponseData = restTemplate.postForObject("http://localhost:9200/_sql?format=json",
               new EsSqlQuery(query, fetchSize), EsResponseData.class);//第一次拜访的成果出来了
       this.cursor = esResponseData.getCursor();
       this.columns = esResponseData.getColumns()
               .stream().map(x -> x.get("name"))
               .collect(Collectors.toList());
       this.iterator = convert(columns, esResponseData).iterator();
   }
   // hasNext 根据 是否 cursor 为null进行后续的 第2次,第三次,,,的拜访,直到 cursor 为null
   @Override
   public boolean hasNext() {
       return iterator.hasNext() || scrollNext();
   }
   //获取第2次及以后的查询成果
   private boolean scrollNext() {
       if (iterator == null || this.cursor == null) {
           return false;
       }
       EsResponseData esResponseData = restTemplate.postForObject("http://localhost:9200/_sql?format=json",
               new EsSqlQuery(this.cursor), EsResponseData.class);
       this.cursor = esResponseData.getCursor();
       this.iterator = convert(columns, esResponseData).iterator();
       return iterator.hasNext();
   }
   @Override
   public Map next() {
       return iterator.next();
   }
   //将 ES SQL Rest API的回来值转化为 List
   private List> convert(List columns, EsResponseData esResponseData) {
       List> results = new ArrayList<>();
       for (List row : esResponseData.getRows()) {
           Map map = new HashMap<>();
           for (int i = 0; i < columns.size(); i++) {
               map.put(columns.get(i), row.get(i));
           }
           results.add(map);
       }
       return results;
   }
}

2.2 实战测验

接下来,咱们进行迭代器形式的实战测验。测验进程并不杂乱,咱们会创EsQueryController 和 EsQueryService 类,大家能够更重视 EsOueryService 类的办法,此处咱们会运用 Stream 和 Spliterators,或许部分开发未运用过 Spliterators,可是代码不杂乱,非常容易了解。

  1. 创立EsQueryController和EsQueryService代码如下:
@RestController
public class EsQueryController {
    @Autowired
    private EsQueryService esQueryService;
    @PostMapping("/queryEsBySql")
    public Object queryEsBySql(@RequestBody EsSqlQuery esSqlQuery) {
        return esQueryService.queryEsBySql(esSqlQuery);
    }
}
@Service
public class EsQueryService {
    public Object queryEsBySql(EsSqlQuery esSqlQuery) {
        EsQueryIterator iterator = esSqlQuery.iterator();
        Stream> resultStream = StreamSupport.stream(Spliterators
                .spliteratorUnknownSize(iterator, 0), false);
        return resultStream.collect(Collectors.toList());
    }
}
  1. 经过PostMan 恳求对应的数据

深化实战:ElasticSearch的Rest API与迭代器形式在高效查询中的运用

总结

本文深化探讨了Elasticsearch SQL Rest API及迭代器形式在高效数据查询中的运用。文章介绍了运用Elasticsearch的/_sql?format=json接口进行数据查询的机制,具体评论了迭代器形式的完结,包含其在Elasticsearch查询中的具体运用。经过介绍UML类图和相关的类结构,解说了怎么创立和运用不同的迭代器人物,如笼统迭代器、具体迭代器和笼统容器等。供给了实践的代码示例,以展示怎么在实践中运用迭代器形式高效遍历和管理Elasticsearch的查询成果。

其次,文章具体评论了迭代器形式的完结,包含其在Elasticsearch查询中的具体运用。经过介绍UML类图和相关的类结构,作者清晰地解说了怎么创立和运用不同的迭代器人物,如笼统迭代器、具体迭代器和笼统容器等。特别地,文章供给了实践的代码示例,以展示怎么在实践中运用迭代器形式高效遍历和管理Elasticsearch的查询成果。

参考文章:

  1. [迭代器形式 | 菜鸟教程]
  2. Response Data Formats | Elasticsearch Guide [7.17] | Elastic