多数据源:CSV、内存对象可以通过SQL查询,甚至联查,你敢信!

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

提到多数据源,你可以首先想到Mybatis-Plus的多数据源。今天介绍的多数据源绝对颠覆你的认知。(至少把我震惊了)

有没有想过通过SQL的方式查询内存对象,CSV,甚至关联查询。以前想都不敢想,本能反应根本实现不了。现在可以了,通过Apache Calcite就可以实现。

02 简介

Apache Calcite是一个高性能数据库的基础框架,特别擅长通过标准SQL接口统一访问和优化来自多种数据源的数据。

Calcite 通过 Adapters(适配器)来连接第三方数据源。每个适配器都让一种外部数据(如 CSV 文件、MySQL 数据库、Java内存对象、Redis 等)在 Calcite 看来像是一张可以通过 SQL查询的表。

Calcite 是一个"查询优化框架",它位于各种数据源之上,提供统一的SQL查询接口。它不持有数据,只负责将查询计划优化并下推到底层数据源执行。它不是数据库,本身不存储数据,因此 不支持标准的INSERTUPDATEDELETE 等数据操作语言(DML)。

官网地址:calcite.apache.org/

支持的适配器:

03 主要参数说明

我们分别从Java内存对象、CSV文件、Mysql数据库来了解,最后看看混合查询的效果。

3.1 主要依赖

xml 复制代码
<!-- Apache Calcite -->
<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-core</artifactId>
    <version>1.38.0</version>
</dependency>
<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-csv</artifactId>
    <version>1.38.0</version>
</dependency>

3.2 参数说明

多数据管理的核心文件是一个JSON文件,包含的主要参数:

json 复制代码
{
  "version": "1.0",
  "defaultSchema": "your schema name",
  "schemas": [
    {
      "name": "your schema name",
      "type": "custom",
      "factory": "",
      "operand": {
      }
    }
  ]
}

最外层参数

  • version:定义的版本
  • defaultSchema:默认使用的Schema
  • schemas:具体的Schema,是一个数组

具体Schema参数

  • nameSchema的名称
  • typeSchema类型。包括:custommapjdbc
    • custom:自定义/扩展模式,需要配置factory
    • map:内置的、基于内存映射的简单模式,无需配置factory
    • jdbc:传统的 JDBC 连接模式
  • factory:适配工厂
  • operand:数据源的相关信息,不同的数据源参数不同

04 内存对象SQL查询

内存对象的SQL查询来自calcite-core的支持,关键的Schema类:

java 复制代码
org.apache.calcite.adapter.java.ReflectiveSchema$Factory

注意这里的$,表示内部类。

模型数据

json 复制代码
{
  "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"
      }
    }
  ]
}

内存对象使用的是反射,这里需要配置class。对应的class就是数据源。

4.1 数据源定义

java 复制代码
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)
        };
    }

    /**
     * @Description: 字段类型
     **/
    @AllArgsConstructor
    public class User {

        /** 用户id */
        public final Integer id;
        /** 用户名 */
        public final String name;
        /** 年龄 */
        public final Integer age;
    }
}

因为所数据源无法修改数据,数据库可以设计为final类型。

库表为public类型的字段,字段的类型必须是数组类型,否则无法获取数据。表的字段必须为内部类,内部类的字段必须是public类型。

4.2 客户端查询

客户端的和标准的jdbc非常类似。

java 复制代码
@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,否则无法识别。

获取连接的方法

java 复制代码
 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的方言,默认是ORACLE。这里改成MYSQL后,表名不区分大小写。

model:指定配置数据源的JSON文件路径

输入结果

java 复制代码
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);
}

这里没啥好说的,就是将查询的结果拼接成json,方便查看。

4.3 查询结果

05 CSV查询

CSV的查询需要引入扩展包calcite-csv,关键的Schema类:

java 复制代码
org.apache.calcite.adapter.csv.CsvSchemaFactory

模型数据

只需要追加Schema即可。

json 复制代码
{
  "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

4.1 数据源定义

直接定义csv文件即可,文件名为表名

4.2 客户端查询

java 复制代码
@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);
}

4.3 查询结果

06 Mysql查询

Mysql查询来自calcite-core的支持,关键的Schema类:

java 复制代码
org.apache.calcite.adapter.jdbc.JdbcSchema$Factory

模型数据

只需要追加Schema即可。

json 复制代码
{
  "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 客户端查询

按照正常的SQL编写即可。

java 复制代码
@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 查询结果

07 关联查询

沿用之前的模型配置,直接编写关联SQL即可。

08 小结

很多企业会分库,但是为了解决跨库查询的问题,经常会同步一个全量的库来解决跨库查询的问题。而Apache Calcite可以直接解决此问题。

在测试过程中,发现版本升级到1.39.0及以上,如果出现关联CSV查询的时候出现查询结果为空的问题,主要原因是关联的CSV内容为空。但是单独查询又是可以的。不知道这个是BUG还是官方的一个优化。有知道的老铁么?

相关推荐
毕设源码-钟学长2 小时前
【开题答辩全过程】以 高校疫情管理系统为例,包含答辩的问题和答案
java
cqbzcsq2 小时前
MC Forge1.20.1 mod开发学习笔记(数据生成、食物)
java·笔记·学习·mc
Hx_Ma162 小时前
mybatis练习2
java·数据库·mybatis
山北雨夜漫步2 小时前
MQ消息队列
java·开发语言
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于Web的小型宾馆客房管理系统为例,包含答辩的问题和答案
java
Zhu_S W2 小时前
EasyExcel:让Excel操作变得简单优雅
java·前端
爱学习的小可爱卢2 小时前
JavaSE基础-Java字符串转整数与拼接实战指南
java·开发语言
星辰_mya2 小时前
Kafka Producer 发送慢 → TPS 骤降 90%
java·数据库·kafka