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

相关推荐
suweijie7682 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿3 小时前
List深拷贝后,数据还是被串改
java
xlsw_6 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹7 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭7 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫8 小时前
泛型(2)
java
超爱吃士力架8 小时前
邀请逻辑
java·linux·后端
南宫生8 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石8 小时前
12/21java基础
java
李小白668 小时前
Spring MVC(上)
java·spring·mvc