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适配器可以简化我们的工作,提升效率。但该工具有一定局限性 ,要么调整使用方式,要么改写核心方法

相关推荐
Nyarlathotep011313 分钟前
线程创建和Thread类
java
阿波罗尼亚18 分钟前
JDK17 新特性
java
独自破碎E18 分钟前
【面试真题拆解】Spring事务机制
java·spring·面试
我是咸鱼不闲呀20 分钟前
力扣Hot100系列21(Java)——[多维动态规划]总结(不同路径,最小路径和,最长回文子串,最长公共子序列, 编辑距离)
java·leetcode·动态规划
lihao lihao23 分钟前
二分查找
java·数据结构·算法
Albert Edison23 分钟前
【C++11】可变参数模板
java·开发语言·c++
代码栈上的思考26 分钟前
消息队列持久化:文件存储设计与实现全解析
java·前端·算法
sg_knight32 分钟前
设计模式实战:策略模式(Strategy)
java·开发语言·python·设计模式·重构·架构·策略模式
麦麦鸡腿堡33 分钟前
JavaWeb_SpringBootWeb,HTTP协议,Tomcat快速入门
java·开发语言