Spring Boot3.3 + Apache Calcite 實戰(zhàn):用 SQL 動態(tài)查詢 JSON 數(shù)據(jù)源
在現(xiàn)代 Web 應用開發(fā)中,數(shù)據(jù)來源日益多樣化,除了傳統(tǒng)的關系型數(shù)據(jù)庫,JSON 文件、NoSQL 以及各種 API 接口提供的數(shù)據(jù)格式也被廣泛使用。為了以統(tǒng)一的方式對異構(gòu)數(shù)據(jù)源進行查詢,Apache Calcite 提供了強大且靈活的 SQL 查詢引擎,可以通過虛擬視圖將 JSON 數(shù)據(jù)建模為表結(jié)構(gòu),再用標準 SQL 語句進行高效查詢。
本篇文章將結(jié)合 Spring Boot3.3 與 Apache Calcite,詳細講解如何將本地 JSON 文件作為數(shù)據(jù)源,通過 Calcite 提供的 schema 映射機制進行 SQL 查詢操作,幫助你輕松實現(xiàn)對 JSON 數(shù)據(jù)的結(jié)構(gòu)化訪問與動態(tài)查詢。
Apache Calcite 簡介
Apache Calcite 是一個開源的動態(tài)數(shù)據(jù)管理框架,它本身并不是數(shù)據(jù)庫,而是一個提供 SQL 查詢解析、驗證、優(yōu)化和執(zhí)行的中間層框架,支持對多種數(shù)據(jù)源進行統(tǒng)一訪問。其核心特性包括:
- 多數(shù)據(jù)源支持支持關系型數(shù)據(jù)庫、CSV、JSON、MongoDB、Elasticsearch 等多種數(shù)據(jù)源。
- SQL 引擎具備完整的 SQL 解析、校驗、優(yōu)化和執(zhí)行能力。
- 虛擬化查詢可將非結(jié)構(gòu)化或半結(jié)構(gòu)化數(shù)據(jù)通過 schema 建模為結(jié)構(gòu)化視圖。
- 可插拔架構(gòu)支持自定義函數(shù)、自定義規(guī)則、插件式架構(gòu)。
在本文中,我們將使用 Apache Calcite 對本地 JSON 文件進行虛擬化建模,并通過標準 SQL 查詢訪問數(shù)據(jù)。
項目依賴配置(pom.xml)
<dependencies>
<!-- Apache Calcite 核心依賴 -->
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>1.35.0</version>
</dependency>
<!-- Jackson 用于 JSON 解析 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot Web 模塊 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
項目結(jié)構(gòu)預覽
├── resources/
│ ├── model.json # 本地 JSON 數(shù)據(jù)
│ └── json-model.schema.json # Calcite 模型 schema
├── controller/
│ └── CalciteQueryController.java
├── service/
│ └── CalciteQueryService.java
├── config/
│ └── CalciteUtils.java # Calcite 配置和執(zhí)行類
JSON 數(shù)據(jù)樣例 model.json
[
{"id":1,"name":"Alice","age":30},
{"id":2,"name":"Bob","age":25},
{"id":3,"name":"Charlie","age":28}
]
創(chuàng)建 Calcite 模型文件 json-model.schema.json
{
"version":"1.0",
"defaultSchema":"json_schema",
"schemas":[
{
"name":"json_schema",
"type":"custom",
"factory":"org.apache.calcite.adapter.json.JsonSchemaFactory",
"operand":{
"directory":"src/main/resources",
"flavor":"file"
},
"tables":[
{
"name":"model",
"type":"custom",
"factory":"org.apache.calcite.adapter.json.JsonTableFactory",
"operand":{
"path":"model.json"
}
}
]
}
]
}
Calcite 工具類 CalciteUtils.java
@Slf4j
public class CalciteUtils {
public static Connection getConnection() throws Exception {
Properties info = new Properties();
try {
String modelPath = Paths.get("src/main/resources/json-model.schema.json").toAbsolutePath().toString();
info.put("model", modelPath);
return DriverManager.getConnection("jdbc:calcite:", info);
} catch (Exception e) {
log.error("初始化 Calcite 連接失敗", e);
throw e;
}
}
public static List<Map<String, Object>> executeQuery(String sql) throws Exception {
List<Map<String, Object>> results = new ArrayList<>();
try (Connection conn = getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>();
for (int i = 1; i <= columnCount; i++) {
row.put(meta.getColumnLabel(i), rs.getObject(i));
}
results.add(row);
}
}
return results;
}
}
查詢接口實現(xiàn)
CalciteQueryService.java
@Service
public class CalciteQueryService {
public List<Map<String, Object>> query(String sql) throws Exception {
return CalciteUtils.executeQuery(sql);
}
}
CalciteQueryController.java
@RestController
@RequestMapping("/calcite")
public class CalciteQueryController {
@Autowired
private CalciteQueryService service;
@GetMapping("/query")
public ResponseEntity<?> query(@RequestParam String sql) {
try {
return ResponseEntity.ok(service.query(sql));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
}
測試
啟動項目后,訪問:
http://localhost:8080/calcite/query?sql=SELECT * FROM json_schema.model WHERE age > 25
返回結(jié)果:
[
{"id":1,"name":"Alice","age":30},
{"id":3,"name":"Charlie","age":28}
]
結(jié)語
Apache Calcite 作為一個高度可擴展的查詢引擎,在大數(shù)據(jù)和異構(gòu)數(shù)據(jù)訪問場景中具有廣泛的應用價值。它不僅支持靈活的 SQL 查詢語法,還能與各類數(shù)據(jù)源輕松集成,讓開發(fā)者能夠以統(tǒng)一方式訪問結(jié)構(gòu)化和非結(jié)構(gòu)化數(shù)據(jù)。
通過本篇文章我們學習了如何結(jié)合 Spring Boot3.3 利用 Calcite 查詢本地 JSON 文件,實現(xiàn)了無需轉(zhuǎn)換即可用 SQL 查詢 JSON 的能力,為復雜的數(shù)據(jù)分析與快速原型開發(fā)提供了極大的便利。
未來你還可以嘗試對接 Elasticsearch、MongoDB、CSV 文件等更多數(shù)據(jù)源,構(gòu)建統(tǒng)一查詢平臺,充分發(fā)揮 Calcite 的潛能。