Apache Calcite - 使用框架Reflective schema访问Java内存数据

前言

前文我们介绍了如何扩展实现自定义schema来访问Java内存数据,在Calcite框架中已提供了若干适配器,可用于访问不用来源的数据,简化我们的工作。 本节介绍 Reflective schema,通过这个适配器直接可以访问内存数据而不用额外扩展。

Reflective schema 简介

Apache Calcite中的ReflectiveSchema是一种机制,允许Calcite通过反射来访问Java对象作为数据库的模式。即可以将普通的Java对象集合作为数据库表来查询。ReflectiveSchema可以将Java对象的字段映射为表的列,对象的集合映射为表的行。

使用举例

在使用ReflectiveSchema时,需要定义一个包含Java对象集合的类。然后,将这个模式类添加到Calcite连接的根模式中,这样Calcite就能够通过SQL查询这些Java对象了。

例如,有一个Employee类和一个包含Employee对象列表的HrSchema类,可以使用ReflectiveSchema将HrSchema实例添加到Calcite的根模式中。可以执行SQL查询来访问HrSchema中的Employee对象了。

使用ReflectiveSchema的步骤大致如下:

  • 定义Java对象类(例如Employee)。
  • 定义包含Java对象集合的模式类(例如HrSchema)。
  • 创建一个Calcite连接,并将模式类的实例添加到连接的根模式中。
  • 通过SQL查询Java对象。
  • 这种方式使得在不需要将数据存储在传统数据库中的情况下,直接在内存中的Java对象上执行SQL查询成为可能。
java 复制代码
public class HrSchema {
        public Employee[] employees;

        public HrSchema(Employee[] employees) {
            this.employees = employees;
        }

        public Employee[] getEmployees() {
            return employees;
        }
    }
java 复制代码
    @Test
    public void test() throws Exception{
        Employee[] employees = new Employee[]{
            new Employee( "Alice","1", "50000"),
            new Employee( "Bob","2", "60000"),
            new Employee( "Charlie","3", "70000")};

        Properties info = new Properties();
        info.setProperty("lex","JAVA");
        Connection connection = DriverManager.getConnection("jdbc:calcite:", info);
        CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
        SchemaPlus rootSchema = calciteConnection.getRootSchema();
        rootSchema.add("hr", new ReflectiveSchema(new HrSchema(employees)))

        ResultSet resultSet = calciteConnection.createStatement().executeQuery(
            "SELECT * FROM hr.employees Where id > 2"
        );

        while (resultSet.next()) {
            System.out.println(resultSet.getString("id") + ", " +
                resultSet.getString("name") + ", " +
                resultSet.getString("deptId"));
        }
        resultSet.close();
        connection.close();
    }

上述代码我们先定义了类 HrSchema,其内部包含对象数组employees

接着创建ReflectiveSchema对象,构造函数接收一个Object对象参数,target限制如下

  • 是一个对象
  • 内部包含的成员必须是数组对象,可以有一个或多个成员,每个成员将映射为一张表
java 复制代码
  public ReflectiveSchema(Object target) {
    super();
    this.clazz = target.getClass();
    this.target = target;
  }

将该schema加入到root schema中,我们接下来就可以直接使用sql查询数组中的数据了

ReflectiveSchema 源码解读

ReflectiveSchema 帮助我们实现了功能,其核心做的事情还是创建table对象、table字段类型描述类型对象。创建table的逻辑如下。通过反射获取类成员变量,接着通过fieldRelation将每个合适的成员创建为table。

java 复制代码
  private Map<String, Table> createTableMap() {
    final ImmutableMap.Builder<String, Table> builder = ImmutableMap.builder();
    for (Field field : clazz.getFields()) {
      final String fieldName = field.getName();
      final Table table = fieldRelation(field);
      if (table == null) {
        continue;
      }
      builder.put(fieldName, table);
    }
    Map<String, Table> tableMap = builder.build();
    // Unique-Key - Foreign-Key
    for (Field field : clazz.getFields()) {
      if (RelReferentialConstraint.class.isAssignableFrom(field.getType())) {
        RelReferentialConstraint rc;
        try {
          rc = (RelReferentialConstraint) field.get(target);
        } catch (IllegalAccessException e) {
          throw new RuntimeException(
              "Error while accessing field " + field, e);
        }
        requireNonNull(rc, () -> "field must not be null: " + field);
        FieldTable table =
            (FieldTable) tableMap.get(Util.last(rc.getSourceQualifiedName()));
        assert table != null;
        List<RelReferentialConstraint> referentialConstraints =
            table.getStatistic().getReferentialConstraints();
        if (referentialConstraints == null) {
          // This enables to keep the same Statistics.of below
          referentialConstraints = ImmutableList.of();
        }
        table.statistic =
            Statistics.of(
                ImmutableList.copyOf(
                    Iterables.concat(referentialConstraints,
                        Collections.singleton(rc))));
      }
    }
    return tableMap;
  }

创建table的核心逻辑中getElementType获取元素类型,只有是数组时才能拿到数组元素类型。

java 复制代码
  private <T> @Nullable Table fieldRelation(final Field field) {
    final Type elementType = getElementType(field.getType());
    if (elementType == null) {
      return null;
    }
    Object o;
    try {
      o = field.get(target);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(
          "Error while accessing field " + field, e);
    }
    requireNonNull(o, () -> "field " + field + " is null for " + target);
    @SuppressWarnings("unchecked")
    final Enumerable<T> enumerable = toEnumerable(o);
    return new FieldTable<>(field, elementType, enumerable);
  }
  
  private static @Nullable Type getElementType(Class clazz) {
    if (clazz.isArray()) {
      return clazz.getComponentType();
    }
    if (Iterable.class.isAssignableFrom(clazz)) {
      return Object.class;
    }
    return null; // not a collection/array/iterable
  }

结果上面两个核心流程,基于反射,Table、RelDataType关键对象就创建完毕。核心过程总结如下

  1. 从类中获取成员变量, 每一个成员变量对应一个Table
  2. 检查是否是数组类型
  3. 如果是数组类型获取数组元素类型
  4. 通过读取数组元素类型构造RelDataType
  5. 完成Table的构造

总结

基于ReflectiveSchema适配器可以简化我们的工作,提升效率。但该工具有一定局限性 ,要么调整使用方式,要么改写核心方法

相关推荐
BD_Marathon2 分钟前
Spring是什么
java·后端·spring
我命由我123454 分钟前
Android 消息机制 - Looper(Looper 静态方法、Looper 静态方法注意事项、Looper 实例方法、Looper 实例方法注意事项)
android·java·android studio·安卓·android jetpack·android-studio·android runtime
月明长歌7 分钟前
【码道初阶】Leetcode138:随机链表的复制:用 HashMap 做深拷贝的标准解法
java·数据结构·算法·leetcode·链表·哈希算法
.简.简.单.单.10 分钟前
Design Patterns In Modern C++ 中文版翻译 第八章 组合
java·c++·设计模式
getapi21 分钟前
/usr/local/apache-tomcat-9.0.71/logs/catalina.out占用了118G
tomcat·apache·firefox
七夜zippoe21 分钟前
Spring MVC请求处理流程源码分析与DispatcherServlet核心逻辑
java·spring·mvc·过滤器·拦截器
weixin_4407305022 分钟前
Nginx、Apache和tomcat的简单了解。
nginx·tomcat·apache
笙枫23 分钟前
Agent 进阶设计:状态管理、中间件与多Agent协作
java·服务器·python·ai·中间件
有趣灵魂27 分钟前
Java-根据HTTP链接读取文件转换为base64
java·开发语言·http
YIN_尹30 分钟前
CANN开源仓Catlass模板库核心能力与编程实战
java·开源·dubbo