前言
前文我们介绍了如何扩展实现自定义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关键对象就创建完毕。核心过程总结如下
- 从类中获取成员变量, 每一个成员变量对应一个Table
- 检查是否是数组类型
- 如果是数组类型获取数组元素类型
- 通过读取数组元素类型构造RelDataType
- 完成Table的构造
总结
基于ReflectiveSchema适配器可以简化我们的工作,提升效率。但该工具有一定局限性 ,要么调整使用方式,要么改写核心方法