java orm 开源框架_Reladomo简介-企业开源Java ORM,包括电池! (第2部分)

java orm 开源框架

重要要点

  • Reladomo是由高盛开发的企业级Java ORM,并于2016年作为开源项目发布。
  • 性能是Reladomo的关键成果。 通过提供最小化,组合和传播IO的工具,开发人员可以使用工具来调整其应用程序性能。
  • Reladomo的自定义缓存有效利用内存来减少IO并提高应用程序性能。
  • Reladomo的企业功能使其与传统ORM脱颖而出。 分片和临时对象支持是这些功能的重点。
  • 可测试性并不是Reladomo的事后想法。 提供的测试资源非常适合编写高质量的测试,以提高应用程序代码库的长期生存能力。

在我们对Reladomo ORM的调查的第一部分中 ,我们探索了可用性和可编程性功能,以及指导其发展的一些核心价值。 在第二部分(也是最后一部分)中,我们将介绍Reladomo的性能,可测试性和针对企业的功能。

性能

高性能解决方案是大型可扩展企业应用程序的基石。 从框架的角度来看,性能有两个方面:

  1. 框架代码本身应进行优化。
  2. 框架公开的模式应允许应用程序代码实现高性能代码。

关于ACID数据库交互,最关键的问题归结为正确执行IO。 在Reladomo中,我们还认识到与带宽相比,延迟是更大的问题,因此我们对此进行了优化。 简而言之,“正确执行IO”是指编写代码以最小化,组合(分批)和扩展(多线程)IO。

最小化IO

在第一篇文章中,我们已经提到了deepFetch中的deepFetch和高级关系功能,这些功能大大减少了对象图上的IO。 在读取路径中,Reladomo的完全自定义缓存可以对I​​O减少产生巨大影响,我们将在下面介绍更多内容。 在写入路径中,对同一对象的多次写入将自动合并到同一工作单元中,从而最大程度地减少了对数据库的调用。

合并IO

允许应用程序在执行查询时正确组合其IO的两个功能是:

  • Reladomo对映射到临时表的临时对象的支持,以及
  • 它支持查询中的元组集。

在分类账示例中考虑Balance对象。 如果要从一对“ Account/Product列表中检索“ Balances ,则可以说:

MithraArrayTupleTupleSet tupleSet = new MithraArrayTupleTupleSet();
tupleSet.add(1234, 777); // Add account and product to the query
tupleSet.add(5678, 200);
tupleSet.add(1111, 250);

TupleAttribute acctAndProd = BalanceFinder.acctId().tupleWith(BalanceFinder.productId());

Operation op = BalanceFinder.businessDate().eq(today);
op = op.and(BalanceFinder.desk().eq("A"));
op = op.and(acctAndProd.in(tupleSet));

BalanceList balances = BalanceFinder.findMany(op);

对于小集合,Reladomo将此转换为or-and子句。 对于较大的集合,将使用一个临时表(在幕后)来加入Balance表。

在写路径中,批量操作(包括自动批处理)的功能有助于合并写操作。 Reladomo将对事务中的写入进行重新排序,以在不影响正确性的情况下实现最大批量。 Reladomo还将选择适合工作量和数据库大小的批处理策略。 例如,Reladomo可以选择四种不同的插入策略(批量,并集,多值,jdbc批处理)。

分配IO

Reladomo使用两种主要模型来帮助扩展IO。

  1. 分片,如下所述。 分片允许更多的同时写入,因为可以使用更多的硬件,并且分片不必担心锁定太多。
  2. Reladomo的对象身份合同(整个JVM中给定主键只有一个持久对象)使推理多线程代码变得简单。 编写多线程代码可能会对IO产生巨大影响。 Reladomo中的许多基本API都公开了内置的多线程,例如MithraList.setNumberOfParallelThreads和实用程序,例如MatcherThreadMultiThreadedBatchProcessor

Reladomo的多线程加载器是将所有这些概念组合在一起的通用实用程序的一个很好的例子。 它涵盖了一个简单但重复性很高且可重复使用的用例:给定一个庞大的传入数据集(例如文件),在数据库中插入/更新/删除相应行的最有效方法是什么? 当以批处理设置将数据从一个系统复制到另一个系统时,通常使用这种用例。 使用多线程加载器,可以异步读取,比较和有效地回写源和目标。 涉及的组件是MatcherThread (进行比较), InputThreadDbLoadThread用于读取源和接收器)以及SingleQueueExecutor用于对写入进行智能批处理)。 使用大量线程进行读取,比较和写入是我们扩展IO的方式。 DbLoadThread通过Reladomo的forEachWithCursor方法流式传输数据库数据,从而最大程度地减少了读取。 SingleQueueExecutor正在智能地组合IO以减少死锁。 只需几行代码即可连接多线程加载程序的实例。 如果您有此用例,则应该尝试一下!

应用程序设计对于利用以上提供的功能至关重要。 在我们的分类帐示例中,以下是可以显着提高吞吐量的一些内容:

  • 分片用于传播IO。
  • 在每个分片中,在同一工作单元中处理多个交易,以最小化提交,最小化查询并合并写入。
    • 例如,在为即将到来的交易查找产品时,我们进行一堆交易,并对数据库进行单个查询以查找所有产品。
  • 余额计算将批量写入数据库。
  • 对多个分片的UI查询发生在多个线程中。

Reladomo的快取

Reladomo带有一个自定义缓存,它比任何通用缓存都更适合ORM的需求。 Reladomo缓存不是地图。 它是无密钥索引的集合,其中每个索引都是可搜索的集合或多集合。 构成特定索引的标识符的属性是任意的。 缓存始终具有主键索引,并且其他索引是根据应用程序声明或对象之间的关系添加的。

Reladomo保证具有给定主键的对象在JVM中仅存在一次,并允许高速缓存覆盖整个JVM。 这使得该缓存比基于会话的缓存更加有用。 与不需要辅助序列化缓存相比,它还使效率大大提高,因为不需要双重存储同一对象,也不需要序列化/反序列化。 与其他ORM缓存不同,Reladomo缓存中的对象是应用程序可见和使用的对象。

缓存还完全了解Reladomo的事务上下文。 事务中的更改(插入/更新/删除)在该事务中可见,但仅在提交后在外部可见。

可以将缓存配置为按需填充(基于被激发的查询)或在启动时完全填充。 完整的缓存对于小型静态对象(例如国家或货币)非常有用。 在适当的情况下,完整的缓存对于较大的数据集也很有用。

缓存结构也完全针对临时对象存储进行了自定义(请参见下文)。 临时对象需要非常不同的内存存储,因为业务主键不能唯一地标识行。 Reladomo的SemiUniqueDatedIndex是一个单一的数据结构,它以两种不同的方式对相同的数据进行哈希处理。

可以在此处找到有关缓存的更多技术讨论。

缓存通知

在企业环境中,不可能只有一个JVM访问或更改特定的数据集。 这对ORM高速缓存提出了一个严重的问题。 Reladomo的缓存支持缓存间通知来解决此问题。 该通知被构造为一组通过广播网络传递的轻量级缓存过期消息。 Reladomo开箱即用,带有广播网络的TCP实现,适用于数百个JVM。 TCP实现是一个简单的中心辐射模型,但包括用于故障转移的双重中心。 通过实现几个小型接口,插入其他广播实现也很容易。 该通知在此处进行了更详细的描述。

复制的堆外缓存

根据应用程序访问模式,使用内存体系结构可以最好地解决某些问题。 如果数据保存在数据库中(可能是分片的和按时间的),则由于以下几个原因,很难扩大和保持内存中的高速缓存为最新状态:

  • 保持数据与数据库同步需要一种策略,该策略不会使缓存长时间处于不确定状态。
  • 当堆大小达到100GB并且应用程序在计算期间创建垃圾时,Java GC可能会遇到困难。
  • 如果将对象序列化到堆外区域中或从堆外区域进行序列化,堆外缓存结构可能会导致更多的垃圾问题。
  • 堆外缓存结构很难维护对象之间的关系。
  • 当计算需求超出单个节点时,理想情况下,缓存应在多个节点上可用。

Reladomo的堆外缓存解决了所有这些问题。

  • Reladomo利用审核时间维度来使缓存与数据库和副本同步。 始终保证应用程序代码可以看到一致的数据,直到达到一个已知的里程碑,随着新数据的到来,这个里程碑将不断发展。
  • 缓存结构允许对所有数据进行零反序列化访问,这对于处理整体中很大一部分的应用程序(例如,聚合)非常重要。 这也有助于将GC保持在最低水平。
  • 堆外缓存的对象的对象API与堆外对象完全相同,因此应用程序代码无需根据缓存模式进行更改。
  • 堆外高速缓存可用的索引结构与堆外高速缓存可用的索引结构相同,索引数据也保持堆外。
  • 数百GB的缓存性能很好。
  • 使用高效算法复制缓存。 由一个主服务器提供十个副本很简单。
  • 就像堆上的对象一样,对象之间的关系也可以动态解决。 这使堆外缓存具有简单的标准化结构。
  • 字符串经过特殊处理。 在这些幻灯片中可以找到更多的技术细节。

企业特色

与传统的ORM相比,企业环境中的代码和数据需要更广泛的考虑。 企业级ORM需要满足对象从创建到报废的整个生命周期。

考虑具有以下要求的证券分类帐的教科书示例:

  • 快速处理来料交易。
  • 根据输入的数据(交易,价格等)进行余额计算。
  • 大量的用户交互,包括基于时间的查询:
    • 在最后一天,一周,一个月中,这些余额如何变化?
    • 在过去一个月中,有哪些交易影响了该帐户?
    • 上一季度固定收益产品的损益是多少?
  • 用户(或上游数据)纠正过去事件的能力,而不会丢失可重复性。
  • 清除报表不再需要的数据。

为了在统一的代码库中完全实现所有这些功能,Reladomo提供了广泛的功能。

分片:水平可扩展ACID

ACID(原子一致且持久耐用)是Reladomo中的基本存储假设之一。 缩放ACID需要经过精心设计,而Reladomo提供了内置的分片支持该功能。 分片标识与对象一起存储在内存中(但不存储在持久性存储中),并成为对象完整标识的一部分。 这样就可以简单地构造传统主键,而不必担心全局唯一性。 例如,可以为分片A的交易指定一个简单的整数id“ 17”,该整数在其他分片中重用于其他交易。

分片是Reladomo API的一流部分。 这意味着与分片对象进行交互不需要配置切换或代码分离。 单个查询可以跨越许多分片,这是Finder API的自然组成部分,将分片属性与其他任何属性一样对待:

Operation op = BalanceFinder.businessDate().eq(today);
op = op.and(BalanceFinder.desk().in(newSetWith("A", "DR", "T", "ZZ")));
op = op.and(moreOperations());

BalanceList balances = BalanceFinder.findMany(op);
balances.setNumberOfParallelThreads(3);

上面的代码将在多个线程中(如果在事务之外)命中所有列出的分片。

分类账示例可以通过使用分片设计来利用此功能。 Reladomo将分片策略决策交给应用程序。 通常,使用以下两种策略之一:

  1. 基于(通用)哈希的随机分片。
  2. 与业务相关的分片,基于某些业务问题。

分类帐交易处理管道如下所示:

交易来自各种上游资源。 它们被路由到适当的分片,并在该分片中进行处理(通常以FIFO方式)。 不同分片上的操作可以安排成完全独立的。 这种架构可以很好地扩展到数百个分片,轻松处理数百万笔交易。

双时态

分类账的核心要求包括能够以正确的时间顺序重现以前的报告和属性活动。 可再现性是指能够对过去的查询获得完全相同的结果。 所谓“正确的时间顺序”,是指我们可以使用新信息来修复过去事件的视图,而不会影响过去进行的查询的结果。 以简单的交易案例为例,该交易比应有的日期晚了一天。 首先,由于此新信息而采取的任何操作都无法更改旧查询的结果。 如果用户问:“我们在昨天下午5点发送了昨天交易活动的报告。它看起来像什么?” 分类帐系统必须能够正确回答。 第二,分类帐必须能够将这种交易纳入昨天的事件流中。 因此,当用户问:“鉴于我们现在所知道的一切,昨天的交易活动是什么样的?” 分类帐系统必须能够显示该数据中的新交易。 这个问题有两个时间维度。 一维是为了完全可复制。 在被称为“交易时间”的文献中,我们在Reladomo中将其称为“ processingDate”。 另一个时间维度用于表示企业的事件视图,而与实时事件无关。 在被称为“有效时间”的文献中,我们在Reladomo中将其称为“ businessDate”。 处理日期完全由Reladomo处理,并且始终与挂钟时间相对应。 业务日期是完全灵活的,应用程序必须决定如何处理它。

从API的角度来看,通过包含四个字段将时间问题纳入Reladomo实现的每个方面,如下所示。 时间维度是查询API的一部分:

Timestamp today = toTimestamp("2017-05-03");
Timestamp lastEvening = toTimestamp("2017-05-03 18:30");
Operation op = BalanceFinder.businessDate().eq(today);
op = op.and(BalanceFinder.processingDate().eq(lastEvening));
op = op.and(BalanceFinder.desk().eq("A"));
op = op.and(moreOperations());
BalanceList balances = BalanceFinder.findMany(op);

这将检索特定业务日期和处理日期的余额。 对象域API包括getBusinessDate()和getProcessingDate()。 换句话说,应用程序中使用的每个对象都位于二维时间空间中的某个点。 这些值会在检索时或在构造过程中标记在对象上。

声明对象之间的时间关系的能力是Reladomo框架的亮点之一。 如果我们检索一个余额对象并询问其帐户或产品,则这些对象将对应于时间空间中的同一点。 整个可导航对象图表示数据的一致时间视图。 这个概念也被带到查询中:当在查询中导航关系时,时间信息将应用于查询中的关系。

Timestamp thirdQuarter = toTimestamp("2016-09-30");
Timestamp lastYear = toTimestamp("2016-12-01 20:00");
Operation op = BalanceFinder.businessDate().eq(thirdQuarter);
op = op.and(BalanceFinder.processingDate().eq(lastYear));
op = op.and(BalanceFinder.desk().eq("A"));
op = op.and(BalanceFinder.product().productName().startsWith("S"));
BalanceList balances = BalanceFinder.findMany(op);

在上面的查询中, productName对应于去年。

写入双时态对象需要更广泛的API集,并且隐藏了相当复杂的实现。 这些API涵盖以下内容:

  • 从2D时间开始查询。
  • 以一致的时间方式导航对象图。
  • 查询历史。
  • 从工作时间的特定点开始插入和终止。
  • 从营业时间的特定时间点开始修改,可以选择修改到另一个时间。
  • 从营业时间的特定点开始增加一个数字字段,可以选择增加到另一个时间。

该API是根据已经在使用时空存储的实际应用程序的需求开发的。 有关API的更全面介绍,请参阅Reladomo Katabittemporal章节。 让我们来看一个例子。 如果我们根据交易活动为一个帐户存储数量余额,则在两次交易之后,余额在数据库中看起来像这样:

如果我们随后及时添加交易,则可以使用以下代码更新余额:

Timestamp oldTradeDate = toTimestamp("2005-01-12");
Operation op = BalanceFinder.businessDate().eq(oldTradeDate);
op = op.and(BalanceFinder.desk().eq("A"));
op = op.and(BalanceFinder.balanceId().eq(1234));
Balance balance = BalanceFinder.findOne(op);

balance.incrementValue(40);

这对数据库中的余额数据的影响是这样的:

Reladomo从该行代码incrementValue InstantValue)执行了两次插入和两次更新。 控制二进制数据的规则相当复杂。 期望每个开发人员都在传统ORM之上实施它们会导致错误和潜在的数据丢失。 Reladomo的bitemporal API的价值主张正是在幕后实现的。

四舍五入

Reladomo中有一些较小的功能集合,这些特征使在企业环境中的工作更流畅。 其中一些包括:

  • 时区支持:标记字段以自动转换为UTC或数据库时区。
  • 临时对象:在Java代码中使用等效的临时表。
  • 可变主键:对于复合主键,可以选择将某些部分标记为可变的。
  • 元数据工具:使用提供的接口和运行时元数据更通用地编写代码。 Reladomo的许多内置实用程序就是这样做的。
  • 3层部署:是否需要限制与数据库的总连接或缓存一些数据? 设置3层设置,其中一个JVM充当其他JVM的集线器。
  • 清除和存档API允许退出或移动不再需要的数据。

开箱即用的可测试性

每个优秀的开发人员都知道,每个要在生产环境中运行的应用程序都应具有经过良好测试的代码。 对于具有严格的监管审查,声誉风险或安全问题(例如财务和医疗保健)的企业来说,这是双重事实。

测试与持久性数据存储交互的代码存在几个问题:

  • 测试数据的设置和删除可能很麻烦。
    • 为特定测试设置不完整的对象图很诱人,当生产代码开始使用图形的其他部分时,很容易导致测试中断。
  • 手写模拟代表与数据存储的交互是有问题的。
    • 一个简单的测试,其中生产代码执行写入操作,然后执行读取操作,将需要某种模拟状态管理。
    • 开发人员看不到生成SQL,也无法轻松推断出IO。
  • 对于大型团队而言,传统测试中难以对真实数据库进行测试的设置很难管理,并且给每个开发人员带来了额外的负担。

Reladomo的内置测试框架通过提供可实例化内存数据库以获取完整样本的测试资源来解决这些问题,请参阅Reladomo测试资源 。 该数据库由一组易于管理的文本文件填充。 无需编写难以阅读和修改插入语句的代码。 可以在没有物理资源的情况下测试所有交互:这些测试可以在没有网络连接的计算机上运行。 开发人员可以轻松地检查生成SQL(没有问号!)和有关IO的原因,例如,发现丢失的deepFetch或优化写入。 完整生命周期测试,其中完全支持所有操作(读和写)的混合。

从第一个版本开始,Reladomo的测试框架就已经成为代码的一部分。 它已广泛用于测试Reladomo本身。

内置测试框架的最佳用途之一是提升旧版代码。 为未经过良好测试的现有代码引入测试,并使用Reladomo重新实现它,从而可以对旧代码进行高可信度替换。

结论

Reladomo的可测试性,性能和企业功能使编写用于ACID数据库交互的面向对象的代码成为现实。 Reladomo填补了企业空间中传统ORM功能的空白。 通过提供API编写更好的代码和更少的代码并无缝地测试该代码,开发人员可以将时间花在应用程序和业务逻辑的高级设计功能上。

如果您想了解有关Reladomo的更多信息,可以尝试Kata练习(我们的Reladomo教程集),该练习顺带使用Reladomo测试框架,因此您不需要安装数据库就可以尝试。 请随时在Github上访问我们并查看文档

翻译自: https://www.infoq.com/articles/Reladomo-Open-Source-ORM-Part2/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

java orm 开源框架

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值