在咱们公司,大多数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首要包含两个要害特点:
- “query”特点:其值为SQL句子。这里运用的SQL句子遵从规范的SQL语法规则,与MySQL的语法极为相似,使得了解传统数据库开发的工程师更容易上手。
-
“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的呼应内容,咱们能够明确地识别出其由三个首要部分组成:
- columns:这部分包含了一切回来字段的名称和类型。它为数据解析和后续操作供给了必要的结构信息。
-
rows:此部分包含了查询成果的具体值,其排列次序与
columns
部分中界说的字段次序严厉对应。这种一致性确保了数据的完整性和易用性。 -
cursor:这是完结分页功能的要害元素。
cursor
的存在表明,当时回来的数据集仅仅满足查询条件的一部分,因为fetch_size
的设置,初度呼应只包含了限定数量的数据。要拜访后续的数据页,咱们需求将cursor
值回传至API。这种机制允许高效地遍历很多数据,而不用一次性加载悉数成果。
1.3 回传cursor,获取其他的分页
持续运用前述数据,若咱们需求拜访查询成果的第二页或第三页,能够简单地将cursor
值用作RequestBody,并再次调用相同的接口。这个进程遵从与初次查询相同的“配方”。
POST /_sql?format=json
{
"cursor": "sDXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAAEWWWdrRlVfSS1TbDYtcW9lc1FJNmlYdw"
}
在后续的呼应中,API一般只回来rows
和cursor
特点。这是因为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条数据,流程大致如下:
-
初次查询:获取一切字段(
columns
)和首批10000条数据(rows
),一起取得一个cursor
值。 -
第2次查询:运用已取得的
cursor
值作为RequestBody,检索下一批10000条数据,并再次取得cursor
值。 - 第三次和第四次查询:重复第2次查询的过程。
-
第五次查询:最终获取剩余的9000条数据,此刻呼应不再包含
cursor
,表示查询已完结。
在编写对应的代码逻辑时,能够考虑运用递归或者while循环来判断cursor
值是否为null,然后决议是否持续查询。还或许有其他编程办法可用于完结这一逻辑。
2 迭代器形式实战
2.1 UML类成果分化、办法界说以及实战
迭代器形式是一种常用的规划形式,其首要目的是对数据结构中的一切元素进行逐一遍历,直到一切元素均被拜访一次。大多数Java开发人员在学习Java SE时,经过List数据结构就现已触摸到了迭代器的概念。运用List的迭代器遍历列表元素一般是一项基本且简单的任务。
咱们将首先学习迭代器形式的UML类图,然后针对每个人物进行具体类的创立和办法的界说。迭代器形式的UML类图首要包含四个人物,但咱们只需求创立其中的三个:
- Iterator(笼统迭代器) :界说了拜访和遍历元素的接口。
- ConcreteIterator(具体迭代器) :完结迭代器接口,负责完结对容器元素的实践遍历。
- Aggregate(笼统容器) :供给创立具体迭代器目标的接口。
- ConcreteAggregate(具体容器) :完结了创立具体迭代器目标的办法。
在UML类图中,除Iterator(JDK的java.util.Iterator)外,咱们需求完结其他三个人物。中心的逻辑首要会集在ConcreteIterator中。此外,咱们还需求界说一个实体类来接收Elasticsearch SQL Rest API的呼应数据,该实体类应包含columns
、rows
和cursor
特点,并供给相应的getter和setter办法。接下来,咱们将深化探讨UML类结构的分化和办法界说。
- 创立与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;
}
- 创立Aggregate笼统容器一-EsSqlQueryInterface
笼统容器人物负资供给创立具体迭代器人物的笼统办法,咱们运用泛型T保证了该类的扩展性。咱们界说的笼统 iterator 办法,是为了new 一个具体的选代器目标,当然了,这部分逻辑会在子类中进行完结。代码如下:
public interace EsSqlQueryInterface {
public T iterator();
}
- 创立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);
}
}
- 创立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,可是代码不杂乱,非常容易了解。
- 创立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());
}
}
- 经过PostMan 恳求对应的数据
总结
本文深化探讨了Elasticsearch SQL Rest API及迭代器形式在高效数据查询中的运用。文章介绍了运用Elasticsearch的/_sql?format=json
接口进行数据查询的机制,具体评论了迭代器形式的完结,包含其在Elasticsearch查询中的具体运用。经过介绍UML类图和相关的类结构,解说了怎么创立和运用不同的迭代器人物,如笼统迭代器、具体迭代器和笼统容器等。供给了实践的代码示例,以展示怎么在实践中运用迭代器形式高效遍历和管理Elasticsearch的查询成果。
其次,文章具体评论了迭代器形式的完结,包含其在Elasticsearch查询中的具体运用。经过介绍UML类图和相关的类结构,作者清晰地解说了怎么创立和运用不同的迭代器人物,如笼统迭代器、具体迭代器和笼统容器等。特别地,文章供给了实践的代码示例,以展示怎么在实践中运用迭代器形式高效遍历和管理Elasticsearch的查询成果。
参考文章: