写点什么

MetaModel——跨多种数据存储提供统一的数据访问

2013 年 8 月 30 日

最近, Human Inference Apache 软件基金会(ASF)分别宣布了捐赠与接受 MetaModel 项目的消息,今后 MetaModel 将成为一个 Apache 孵化器项目。此前,MetaModel 遵循 LGPL 许可证,由 Human Inference 的产品开发团队管理,但现在已经转移到了 ASF 上,将遵循新的许可证、拥有新的社区、接受新的管理。那么这个项目到底是什么呢?它又有哪些用途?

MetaModel 是一个 Java 类库,设计它的目的是提供一个可以与任何数据存储(不论是关系型数据库、NoSQL 数据库,还是电子表格或者其他格式的文件)进行交互的单一接口。我们的意思是,通过交互来搜索元数据,查询、写入或更改数据存储中保存的数据。很显然,高层次抽象会丢失一些细节,会带来过度概括和丢失重要特性的风险。我们不想把关系型 SQL 数据库的功能减化到只有像(SELECT * FROM [table])这样的全表扫描。但另一方面,我们也不想暴露一些只能在特定品牌特定版本的 SQL 服务器上才能使用的功能,因为这些功能无法在任何其他数据存储上使用。最终,我们想基于现有的常规技能(比如标准 SQL)建立数据交互方式。

元数据处理

那么,MetaModel 项目为数据存储的方法做了怎样的抽象?该项目通过 Java 接口发布了一个非常类似于 SQL 的查询模型,有时该模型也可从字符中解析。由于把查询定义为常规的 Java 对象,就能容易地解析查询(取决于底层技术),并能根据底层技术细节选择最佳的执行策略。这意味着MetaModel 不仅提供了一个接口,还提供了一个完整的查询引擎,这个引擎在查询时可以处理部分或者全部任务。如果你使用的是关系型JDBC 数据库,会在数据库的本地引擎上执行99% 的查询。但是如果使用了MetaModel,就可以利用它的查询引擎把数据切片、切块,就能在CSV 文件或Excel 电子表格上执行同样的查询了。同时,你根本不必修改查询语句。

当然,这得假设你数据存储的元数据与数据结构是兼容的。不同的数据存储以不同的方式暴露或推理元数据。JDBC 数据库通常会通过JDBC 元数据API 暴露元数据。文件格式(比如CSV 和Excel 表格)并没有很明确地定义,通常可以读取文件的首行内容获取它们的元数据。还有种比较极端的情况,有一些NoSQL 数据库甚至根本就没有元数据。MetaModel 可以让你选择暴露元数据的方式,你可以指定是以编程方式暴露,或通过检查数据存储的前N 条记录的方式来推理出元数据。

MetaModel 中最核心的设计是 DataContext 接口,它表示数据存储,可以用它来浏览和查询该数据存储。此外,它的子接口 UpdateableDataContext 表示可写的数据存储,可以用它更新数据。一旦你拥有了 DataContext 实例,就可以或多或少地学习使用类库代码补全的功能了。这里有几个 DataContext 实现的典型示例,让我们来看看它们是如何实例化的:

复制代码
// a DataContext for a CSV file
UpdateableDataContextcsv = new CsvDataContext(new File(“data.csv”));
// a DataContext for an Excel spreadsheet
UpdateableDataContext excel = new ExcelDataContext(new File(“spreadsheet.xlsx”));
// a DataContext for a JDBC database (can use either DataSource or Connection)
java.sql.DataSourcedataSource = …
UpdateableDataContextjdbc = new JdbcDataContext(dataSource);
// a DataContext for an XML file (where metadata is automatically inferred)
DataContext xml = new XmlDomDataContext(new File(“data.xml”));
// a DataContext for connecting to Salesforce.com’s data web services
UpdateableDataContextsalesforce =
newSalesforceDataContext(username, pw, securityToken);
// a in-memory DataContext for POJOs (useful for testing and mocking)
Person record1 = ...
Person record2 = ...
TableDataProvidertableDataProvider = new ObjectTableDataProvider(
“persons”, Person.class, Arrays.asList(record1, record2));
UpdateableDataContextpojos = new PojoDataContext(“schema”, tableDataProvider);

对于 MetaModel 来说元数据极为重要,它不仅用来管理数据结构,而且还用来定义查询。如果你的查询要确保使用了适当的元数据,就需要投入大量的精力保证查询的安全性。因此,作为一名开发人员,你在查询之前就要持有元数据对象。举例来说,如果你知道有一张表,表名是 ORDER_LINE,它有一列是 price,另一列是 order_id,那么就可以用寻常的硬编码方式去查询所需的元数据(显然,只有你熟悉数据存储时才能使用这种方式):

复制代码
DataContextdataContext = ... // the DataContext object represents the ‘connection’
Table orderLines = dataContext.getTableByQualifiedLabel(“ORDER_LINES”);
Column price = orderLines.getColumnByName(“price”);
Column orderId = orderLines.getColumnByName(“order_id”);

此外,你还可以使用 API 基于探索的方式动态获取元数据。当你想为用户提供定制查询时,这种方式就能发挥巨大的作用了,你可以用这种方式为用户提供可用的表和列,让用户自己选择所需的查询。

复制代码
Schema[] schema = dataContext.getSchemas();
Table[] tables = schemas[0].getTables();
Column[] columns = tables[0].getColumns();

MetaModel 还一个非常重要的思想,它把元数据、查询和其他数据交互都当作是对象。查询在 MetaModel 中就是一个普通的 Java 对象,所以你可以在执行之前篡改它,也可以把它分发出去。这使应用程序可以创建由不同代码块协作完成的复合工作流程,在优化查询计划时就不必再面对冗长的 SQL 字符串操作了。它对类型安全也很有帮助,举例来说,查询模型是基于像列、表等类型安全的结构来描述模型的,而不是采用模糊不清的字符串常量。

数据存储的查询

那么,让我们来看看 MetaModel 是如何查询数据存储的。

你可以用三种不同的方式触发同一个查询:

1. 手写所有代码:

手写所有代码是 POJO 的传统方式。用这种方式要写非常冗长的代码,但你能得到最高的灵活性。

复制代码
Query q = new Query();
q.select(SUM, price);
q.select(orderId);
q.from(orderLines);
q.groupBy(orderId);
q.setMaxRows(100);
DataSetdataSet = dataContext.executeQuery(q);

2. 使用优雅的 Builder API:

使用 Builder API 是另外一种类型安全的查询方式,用这种方式只需要编写较短的代码。另外,这个 API 运用了建造者模式 builder-pattern),使开发人员可以迭代调用 API,一步一步补充子查询逻辑。如果你只想定义一个组件的查询,首先就应考虑这种方式。

复制代码
Query q = dataContext.query().from(orderLines)
.select(SUM, price).and(orderId)
.groupBy(orderId).maxRows(100).toQuery();
DataSetdataSet = dataContext.executeQuery(q);

3. 通过字符串解析

有时可能你只想快捷简单地完成工作,那么就可以使用编写 SQL 语句这种更加传统的方式了。MetaModel 也能把普通字符串解析成查询,但这种方式只能在运行期验证查询语句,所以存在类型安全的风险。

复制代码
Query q = dataContext.parseQuery(
“SELECT SUM(price), order_id FROM order_lines GROUP BY order_id LIMIT 100”);
DataSetdataSet = dataContext.executeQuery(q);

很明显可以看出,这三种方式最终的查询结果都是一个 DataSet 类型的对象,这个对象把查询结果描述成一个表格。DataSet 并没有太多复杂的特性,遍历它的方法非常简单,如下所示:

复制代码
Try {
while (dataSet.next()) {
Row row = dataSet.getRow();
System.out.println(row.toString());
}
} finally {
dataset.close();
}

数据存储的更新

MetaModel 使用类型安全、元数据驱动的方法执行更新。如前文所述,并非所有的数据存储都是可写的,所以若要写入数据,你不仅仅需要一个 DataContext 对象,还需要实现 UpdateableDataContext 接口。假设,我们想更新 _order_ 表的数据,代码示例如下:

复制代码
dataContext.executeUpdate(new UpdateScript() {
@Override
public void run(UpdateCallbackcb) {
// insert a new order line
cb.insertInto(orderLines).value(orderId, 123).value(price, 395).execute();
// update the price of orderlines where order_id = 122
cb.update(orderLines).where(orderId).eq(122).value(price, 295).execute();
}
});

这里要注意 UpdateScript 的代码结构,更新操作是放在逻辑事务里的。MetaModel 会根据不同的底层数据技术选用适当的事务策略,比如 JDBC 数据库就会采用 ACID 事务,对于大多数文件格式的存储就会采用同步写入的策略,不一而足。最终使你只需要用单一的语法就能把数据写入到所有数据存储中。

因为示例代码使用了匿名内部类,所以看起来并不是特别优雅。但 Java 8 肯定会借助闭包的概念改进这一点。此外,如果你只希望执行一个单独的操作,也可以使用几个预制的 UpdateScript 类,它们简单易用,极易上手:

复制代码
dataContext.executeUpdate(
new InsertInto(orderLines).value(orderId, 123).value(price, 395));

而且,你还可以使用 executeUpdate 方法创建和删除表,也能用它删除表记录。

对新的数据存储支持

最后,MetaModel 的专家级用户可能会问,“如果我想连接 [XYZ] 呢?”(XYZ 是我们暂不支持的其他数据存储)。很显然,我们希望 MetaModel 易于扩展,出于这个原因和其他方面的考虑,我们设计了可插拔的查询引擎。你只需要自己实现 DataContext 接口,但是如果你从头开始实现的话,也不是很简单。所以,我们为你提供了一个抽象类实现,它包括了几个扩展点。以下是全部过程:

  • 先让你的类扩展抽象类 _QueryPostprocessDataContext。_ 然后你会发现几个需要实现的抽象方法:
    • getMainSchema()
      通过实现这个方法,暴露你 DataContext 的模式模型。
    • materializeMainSchemaTable(Table, Column[], int)
      通过实现这个方法,为一张特定的表提供一个相当于全表扫描的实现。
  • 现在你的 DataContext 就可以使用了,你可以在外来数据存储中开始使用 MetaModel 了!
  • 但是,我们还要继续优化!虽然我们的 DataContext 现在完全可以使用了,但是可能它还无法执行某些查询,因为 MetaModel 查询引擎依赖于 materializeMainSchemaTable(…)方法,会把该方法的执行结果作为数据源来处理几乎所有的查询。所以,你可能还需要重写以下几个方法:
    • executeCountQuery(…)
      很多数据存储都提供了这么一个统计指定表总记录数的简易方法。由于经常会用到这种查询,所以很有必要重写这个方法。
    • materializeMainSchemaTable(Table, Column[], int, int)
      大家很多时候都会采用 _ 分页 _ 查询的方式获取表数据(从第 X 条记录到第 Y 条记录)。为了满足这种查询需求,就需要在这个方法中追加 int 类型的参数,让使用者可以通过这个参数按页查询,而不必总是查出整张表的所有记录。
    • executeQuery(Query)
      如果你想要进一步优化,支持 WHERE 或 GROUP BY 子句,就需要重写这个方法。为了满足各种复杂的情况,MetaModel 把这个方法的参数(Query)设计为复杂对象类型。在 SalesforceDataContext 和 MongoDbDataContext 两个类的源代码中可以找到一些很好的参考示例。

结束语

在这篇文章中,我们介绍了 MetaModel(它是一个类库,让我们可以用它访问各种不同的数据存储),解释了如何用它处理元数据,如何用它询问存储,以及如何用它更新数据。

即使 MetaModel 搬了新家(Apache),我们将来还会继续完善它。我们将会针对 HBase、Cassandra 和其他流行的数据库增加更多内置的 DataContext 实现。并且进一步扩展特性,让你可以利用元数据完成更多的工作。除了这些,我们还有一些其他的想法,比如我们正在努力丰富元数据嵌套结构(比如许多 NoSQL 数据库都会用到的地图和列表)、创建虚拟表(类似于视图,但要建在客户端上,而不是服务端上)的能力,支持把 POJO 映射为 DataSet 行,为查询引擎添加更多的功能。

如果您对这个项目感兴趣,可以在 Apache Incubator MetaModel 页面中查阅到邮件列表、bug 跟踪等相关内容。

作者简介

Kasper Sørensen Human Inference 的首席软件工程师和产品经理。他的专业特长和兴趣爱好是开发数据密集型应用产品。在哥本哈根商学院毕业时,他创办了 DataCleaner MetaModel 这两个开源项目,他硕士论文的部分内容就是关于这两个项目的。你可以在他的博客 Kasper’s Source 中了解更多内容。

查看英文原文: MetaModel – Providing Uniform Data Access Across Various Data Stores


感谢马国耀对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2013 年 8 月 30 日 06:015667

评论

发布
暂无评论
发现更多内容

架构师训练营第一周作业-命题作业

阿德儿

性能优化-1-压测

raox

AOP的姿势之 简化 MemoryCache 使用方式

八苦-瞿昙

C# aop cache

架构师训练营第十一周作业2

韩儿

第六周命题作业

cc

SAML和OAuth2这两种SSO协议的区别

程序那些事

权限系统 OAuth2 程序那些事 SAML SSO

第一周架构方法-周总结

潘涛

Week11总结

lggl

架构师训练营 1 期:大作业(一)

piercebn

架构师训练营第 1 期

架构师第一周总结

永不言弃

[架构师训练营] 食堂就餐卡系统设计

Frank

架构师训练营 4 期

食堂就餐卡系统设计

跳蚤

最全总结 | 聊聊 Python 数据处理全家桶(Mysql 篇)

星安果

Python MySQL 数据库 最全总结

食堂就餐卡系统设计

永不言弃

架构

架构师训练营第一周作业-学习总结

阿德儿

第十一周课后练习

晴空万里

编程常用的加密方式

皮蛋

加密 加解密 加密技术

架构师训练营第十一周作业1

韩儿

[架构师训练营] 第一周学习总结

Frank

架构师训练营 4 期

第六周学习心得

cc

架构师训练营第 11 周学习总结

菜青虫

架构师训练营第 11 周课后练习

菜青虫

物联网基础知识整理及实战

garlic

物联网

LeetCode题解:264. 丑数 II,二叉堆,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

架構師訓練營 大作業二

ilake

北极科考:我们为什么要在北极呆上一年?

脑极体

架构师训练营第六周课后作业

万有引力

架构师培训第一周学习总结

跳蚤

甲方日常 78

句子

工作 随笔杂谈 日常

指令重排序、内存屏障很难?看完这篇你就懂了!

Java鱼仔

Java 程序员 面试 JMM 指令重排序

知识改变命运,你相信这句话吗?

熊斌

成长 演讲 教育

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

MetaModel——跨多种数据存储提供统一的数据访问-InfoQ