谈及多数据源整合,多数开发者的第一反应往往是Mybatis-Plus的分库分表方案。然而,今天要分享的这项技术可能会刷新你对数据查询的认知边界。
想象一下,能否用一条SQL语句同时查询内存中的Java对象、本地CSV文件,甚至实现跨异构数据源的关联分析?在传统架构中,这似乎是天方夜谭。但Apache Calcite的出现,让这种设想变成了现实。
Apache Calcite定位为数据库领域的底层基础设施框架,其核心价值在于通过标准化SQL接口,实现对多元化数据源的统一访问与智能优化。
该框架采用Adapter(适配器)设计模式对接各类外部数据源。无论是CSV文本、MySQL实例、Java堆内存对象,还是Redis缓存,都能通过相应适配器在Calcite中映射为可查询的虚拟表。
需要明确的是,Calcite本质是"查询优化引擎"而非传统数据库。它不持久化存储数据,而是专注于查询计划的生成与优化,并将计算任务下推至原始数据源执行。因此,它不支持INSERT、UPDATE、DELETE等数据变更操作。
官方文档:calcite.apache.org/
支持的适配器类型一览:
本节将依次演示Java内存对象、CSV文件、MySQL数据库三种数据源的接入方式,并最终呈现混合查询效果。
3.1 Maven依赖引入
<!-- Calcite核心模块 --> <dependency> <groupId>org.apache.calcite</groupId> <artifactId>calcite-core</artifactId> <version>1.38.0</version> </dependency> <!-- CSV适配器扩展 --> <dependency> <groupId>org.apache.calcite</groupId> <artifactId>calcite-csv</artifactId> <version>1.38.0</version> </dependency> 3.2 配置文件结构
数据源管理采用JSON格式进行声明式配置,基本结构如下:
{ "version": "1.0", "defaultSchema": "your schema name", "schemas": [ { "name": "your schema name", "type": "custom", "factory": "", "operand": { } } ] } | 顶层字段说明 | 字段 | 含义 |
|---|---|---|
version | 配置规范版本 | |
defaultSchema | 默认激活的Schema | |
schemas | Schema配置列表 |
| Schema详细参数 | 字段 | 说明 |
|---|---|---|
name | Schema唯一标识 | |
type | 类型选项:custom(自定义)、map(内存映射)、jdbc(JDBC连接) | |
factory | 适配器工厂类全限定名 | |
operand | 数据源特定配置项 |
内存数据查询能力由calcite-core模块提供,关键Schema工厂类为:
org.apache.calcite.adapter.java.ReflectiveSchema$Factory (注意$符号表示这是内部类)
配置示例
{ "version": "1.0", "defaultSchema": "MEM", "schemas": [ { "name": "MEM", "type": "custom", "factory": "org.apache.calcite.adapter.java.ReflectiveSchema$Factory", "operand": { "class": "com.simonking.boot.calcite.schema.UserSchema" } } ] } 该方案基于反射机制工作,operand中需指定实际的数据源类。
4.1 数据类定义
public class UserSchema { public final User[] users; public UserSchema() { // 构造测试数据 this.users = new User[]{ new User(1, "zhangsan", 18), new User(2, "admin", 20), new User(3, "lisi", 22) }; } @AllArgsConstructor public class User { public final Integer id; // 用户编号 public final String name; // 用户姓名 public final Integer age; // 用户年龄 } } 关键约束:
- 数据字段必须声明为
public - 必须以数组形式组织
- 表结构需定义为内部类
- 只读场景建议使用
final修饰4.2 查询客户端
使用方式与标准JDBC几乎一致:
@Test void test01() throws Exception { CalciteConnection calciteConn = getCalciteConnection(); String sql = "SELECT * FROM MEM.users"; Statement stmt = calciteConn.createStatement(); ResultSet rs = stmt.executeQuery(sql); doResult(rs); }注意: SQL中必须显式指定Schema前缀,否则无法定位表结构。
连接获取方法private static CalciteConnection getCalciteConnection() throws Exception { Class.forName("org.apache.calcite.jdbc.Driver"); Properties prop = new Properties(); prop.put("lex", "MYSQL"); prop.put("model", "src/main/resources/calcite-model.json"); Connection connection = DriverManager.getConnection("jdbc:calcite:", prop); CalciteConnection calciteConn = connection.unwrap(CalciteConnection.class); return calciteConn; }Properties关键参数:
- lex:SQL词法解析模式,设为MYSQL后表名不区分大小写(默认为ORACLE模式)
- model:JSON配置文件路径
***结果格式化输出
五、CSV文件查询private void doResult(ResultSet rs) throws SQLException { ResultSetMetaData meta = rs.getMetaData(); StringBuffer sb = new StringBuffer("["); while (rs.next()) { sb.append("{"); for (int i = 1; i <= meta.getColumnCount(); i++) { sb.append(meta.getColumnName(i)) .append(":") .append(rs.getObject(i)) .append(","); } sb.deleteCharAt(sb.length() - 1); sb.append("},"); } sb.deleteCharAt(sb.length() - 1); sb.append("]"); System.out.println(sb); }CSV数据源需引入calcite-csv扩展,核心Schema工厂类:
org.apache.calcite.adapter.csv.CsvSchemaFactory配置追加
{ "version": "1.0", "defaultSchema": "MEM", "schemas": [ { "name": "MEM", "type": "custom", "factory": "org.apache.calcite.adapter.java.ReflectiveSchema$Factory", "operand": { "class": "com.simonking.boot.calcite.schema.UserSchema" } }, { "name": "CSV", "type": "custom", "factory": "org.apache.calcite.adapter.csv.CsvSchemaFactory", "operand": { "directory": "csv", "flavor": "scannable" } } ] }参数说明:
directory:CSV文件目录(默认从classpath查找)flavor:查询模式(可选,默认scannable)5.1 数据准备
直接创建CSV文件,文件名即表名。
5.2 查询代码
六、MySQL数据库查询@Test void test02() throws Exception { CalciteConnection calciteConn = getCalciteConnection(); String sql = "SELECT * FROM CSV.orders"; Statement stmt = calciteConn.createStatement(); ResultSet rs = stmt.executeQuery(sql); doResult(rs); }MySQL接入同样由calcite-core支持,Schema工厂类:
org.apache.calcite.adapter.jdbc.JdbcSchema$Factory配置扩展
{ "version": "1.0", "defaultSchema": "MEM", "schemas": [ { "name": "MEM", "type": "custom", "factory": "org.apache.calcite.adapter.java.ReflectiveSchema$Factory", "operand": { "class": "com.simonking.boot.calcite.schema.UserSchema" } }, { "name": "CSV", "type": "custom", "factory": "org.apache.calcite.adapter.csv.CsvSchemaFactory", "operand": { "directory": "csv", "flavor": "scannable" } }, { "name": "MYSQL_DB", "type": "custom", "factory": "org.apache.calcite.adapter.jdbc.JdbcSchema$Factory", "operand": { "jdbcUrl": "jdbc:mysql://localhost:3306/test", "jdbcUser": "root", "jdbcPassword": "root" } } ] }6.1 查询演示
@Test void test03() throws Exception { CalciteConnection calciteConn = getCalciteConnection(); String sql = "SELECT * FROM MYSQL_DB.user_roles"; Statement stmt = calciteConn.createStatement(); ResultSet rs = stmt.executeQuery(sql); doResult(rs); }6.2 查询输出
七、跨源关联查询完成上述配置后,跨数据源关联查询只需编写标准SQL即可:
八、总结与思考核心价值
企业级应用普遍采用分库分表架构,传统跨库查询方案需要将数据同步至单一存储,造成冗余和维护成本。Apache Calcite提供了更优雅的解决思路——无需数据搬迁,直接实现异构数据源的联邦查询。
已知问题
测试中发现,当版本升级至1.39.0及以上时,若关联查询涉及CSV数据源且结果为空集,可能出现异常行为。但单独查询该CSV源时工作正常。此现象是系统缺陷还是优化策略,目前官方文档未明确说明,欢迎有经验的开发者交流讨论。
适用场景- 数据湖查询引擎
- 多源数据联邦分析
- 临时数据探查工具
- 异构系统数据整合
Apache Calcite为复杂数据环境下的统一查询提供了新的技术选型思路,值得在合适场景中深入探索和应用。
共同学习,写下你的评论
评论加载中...
作者其他优质文章