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

相关推荐
yaaakaaang3 分钟前
十三、责任链模式
java·责任链模式
爱吃烤鸡翅的酸菜鱼6 分钟前
【Java】封装位运算通用工具类——用一个整数字段替代几十个布尔列,极致节省存储空间
java·开发语言·设计模式·工具类·位运算·合成复用原则
菜菜小狗的学习笔记13 分钟前
八股(三)Java并发
java·开发语言
云烟成雨TD17 分钟前
Spring AI Alibaba 1.x 系列【10】ReactAgent 工具加载和执行流程
java·人工智能·spring
lee_curry18 分钟前
JUC第一章 java中基础概念和CompletableFuture
java·多线程·并发·juc
迷藏49431 分钟前
**超融合架构下的Go语言实践:从零搭建高性能容器化微服务集群**在现代云原生时代,*
java·python·云原生·架构·golang
それども40 分钟前
Spring Bean @Autowired自注入空指针问题
java·开发语言·spring
如来神掌十八式42 分钟前
Java所有的锁:从基础到进阶
java·
硅基诗人1 小时前
Java后端高并发核心瓶颈突破(JVM+并发+分布式底层实战)
java·jvm·分布式
聆听。。花开雨落1 小时前
intelij idea闪退后再启动tomcat报错端口冲突
java·tomcat·intellij-idea