1. 简介
什么是Calcite?
Apache Calcite 是一个动态数据管理框架。
它包含了典型数据库管理系统(DBMS)的许多核心组件,但省略了一些关键功能:不提供数据存储、不实现数据处理算法,也不维护存储元数据的仓库。
Calcite 刻意避开了数据存储与处理的业务范畴。正如我们将看到的,这使其成为连接应用程序与一个或多个数据存储位置及数据处理引擎的绝佳中介。同时,它也是构建数据库的完美基础------只需补充数据即可。
Calcite应用场景
- 联邦查询
用一条 SQL 同时查多个异构数据源,如下示例:
sql
SELECT u.name, o.amount FROM mysql.users u JOIN csv.orders o ON u.id = o.user_idWHERE o.amount > 200
该示例,将从mysql和csv文件中连接查询数据。
支持的数据源:MySQL、PostgreSQL、Oracle、CSV、JSON、Elasticsearch、Kafka、HDFS 等。
- 为自研数据库/计算引擎提供 SQL 引擎
避免重复造轮子,快速获得工业级 SQL 能力。
被这些顶级项目采用:Apache Flink:Flink SQL 的解析与优化器; Apache Druid:SQL 查询接口; Splunk:SQL 查询功能底层等。
当你有如下需求时,你可以考虑使用Calcite:
-
统一查询多个数据源
-
为产品加 SQL 能力
-
快速搭建虚拟数据层
本篇文章,我们将基于 MySQL 与 CSV 的跨数据源联合查询实现。
2.实战案例
2.1 引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>1.41.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-file</artifactId>
<version>1.41.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
MySQL数据准备

CSV文件数据

2.2 模型配置
java
{
"version": "1.0",
"defaultSchema": "users",
"schemas": [
{
"name": "users",
"type": "custom",
"factory": "org.apache.calcite.adapter.jdbc.JdbcSchema$Factory",
"operand": {
"jdbcDriver": "com.mysql.cj.jdbc.Driver",
"jdbcUrl": "jdbc:mysql://localhost:3306/ddd?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=UTF-8",
"jdbcUser": "root",
"jdbcPassword": "root"
}
},
{
"name": "orders",
"type": "custom",
"factory": "org.apache.calcite.adapter.file.FileSchemaFactory",
"operand": {
"directory": "F:/datas" // 你csv文件目录,每个文件名将会成为表名
}
}
]
}
这里我们配置了2个Schema,MySQL和CSV文件。
在准备好上述数据后,我们先使用 Calcite 分别对 MySQL 和 CSV 文件进行查询,以验证数据访问是否正常,最后再将其与 Spring Boot 结合使用。
2.3 基本使用测试
MySQL测试
java
@Test
public void testJdbc() throws Exception {
Properties info = new Properties();
info.put("lex", "MYSQL");
ClassPathResource resource = new ClassPathResource("model.json") ;
info.put("model", resource.getFile().getAbsolutePath());
try (Connection connection = DriverManager.getConnection("jdbc:calcite:", info)) {
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery("select * from users.o_user");
while (rs.next()) {
System.err.println("%s, %s, %s, %s, %s".formatted(rs.getObject("id"),rs.getObject("name"),rs.getObject("age"),rs.getObject("sex"),rs.getObject("phone"))) ;
}
}
}
运行结果

CSV测试
java
@Test
public void testCsv() throws Exception {
Properties info = new Properties();
info.put("lex", "MYSQL");
ClassPathResource resource = new ClassPathResource("model.json") ;
info.put("model", resource.getFile().getAbsolutePath());
try (Connection connection = DriverManager.getConnection("jdbc:calcite:", info)) {
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery("select * from orders.orders");
while (rs.next()) {
System.err.println("%s, %s, %s, %s, %s".formatted(rs.getObject("order_id"),rs.getObject("user_id"),rs.getObject("product"),rs.getObject("amount"),rs.getObject("order_time"))) ;
}
}
}
运行结果

2.4 MySQL+CSV联合查询
首先,自定义数据源
java
public class CalciteDataSource implements DataSource {
private final Properties info ;
public CalciteDataSource(String lex, String model) {
info = new Properties();
info.put("lex", lex);
ClassPathResource resource = new ClassPathResource(model) ;
try {
info.put("model", resource.getFile().getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:calcite:", info) ;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return this.getConnection();
}
}
接下来,配置数据源
java
@Bean(autowireCandidate = false)
DataSource calciteDataSource() throws Exception {
return new CalciteDataSource("MYSQL", "model.json") ;
}
最后,通过JdbcClient执行联合查询
java
@Service
public class CalciteService {
private final JdbcClient jdbcClient ;
public CalciteService(ApplicationContext context) {
this.jdbcClient = JdbcClient.create(context.getBean("calciteDataSource", DataSource.clas)) ;
}
public List<Map<String, Object>> queryUser() {
List<Map<String, Object>> result = this.jdbcClient.sql("select * from users.o_user x left join orders.orders y on(x.id = y.user_id) where x.id = 8 and y.amount > 200") .query().listOfRows() ;
return result ;
}
}
运行结果
