java orm 开源框架_Reladomo简介-企业开源Java ORM,包括电池!

java orm 开源框架

重要要点

  • Reladomo是由高盛开发的企业级Java ORM,并于2016年作为开源项目发布。
  • Reladomo提供了许多独特而有趣的功能,例如强类型查询语言,分片,时间支持,实际可测试性和高性能缓存。
  • Reladomo是一个自以为是的框架,基于指导其发展的一系列核心价值观。
  • 本文中的示例说明了Reladomo的可用性和可编程性。

早在2004年,我们就面临着艰巨的挑战。 我们需要一种方法来抽象出Java应用程序中的数据库交互,而该交互不适合任何现有框架。 该应用程序具有以下常规解决方案之外的需求:

  • 数据被高度分割。 有100多个具有相同模式但数据不同的数据库。
  • 数据是基于时间的(我们将在本文的第2部分中对此进行解释,敬请期待!)。
  • 针对数据的查询不一定是静态的,某些查询必须从一组复杂的用户输入中动态创建。
  • 数据模型相当复杂-数百张表。

我们从2004年开始Reladomo的开发。那年晚些时候进行了首次生产部署,此后一直定期发布。 在随后的几年中,Reladomo已被高盛(Goldman Sachs)广泛采用,并且使用它的应用程序指导了我们添加的主要新功能。 现在,它已用于多种分类帐,中层办公室贸易处理,资产负债表处理以及许多其他应用程序。 高盛(Goldman Sachs)在2016年根据Apache 2.0许可发布了Reladomo(关系域对象的缩写)作为开源项目。

为什么要建立另一个ORM?

很简单,现有解决方案无法满足我们的核心要求,而传统的ORM存在需要解决的问题。

我们决定消除代码级样板和痕迹结构。 在Reladomo中,没有用于获取,关闭,泄漏或冲洗的连接。 没有会议。 没有EntityManager。 没有LazyInitializationException。 API以两种基本方式提供:在域对象本身上,并通过强类型List实现高度增强。

Reladomo查询语言对我们来说是另一个关键点。 基于字符串的语言不适合我们的应用程序,也通常不适合面向对象的代码。 除了最琐碎的查询之外,将字符串连接在一起以形成动态查询是行不通的。 基于字符串连接来维护这些动态查询是一项令人沮丧的工作。

分片是我们需要完整的本机支持的另一个领域。 Reladomo中的分片非常灵活,可以处理出现在不同分片中,指向不同对象的相同主键值。 分片查询语法自然适合查询语言。

时态(单时态和双时态)支持可以帮助数据库设计人员记录和推理有关时态的信息,Richard Snodgrass在他的书《用SQL开发面向时间的数据库应用程序》中描述的是Reladomo的真正独特功能。 它适用于许多地方,从各种会计系统到参考数据,到需要完全可重复性的任何地方。 即使是简单的应用程序(例如项目协作工具),也可以从单时间表示中受益,使用户界面可以像时光机一样工作,并显示事物的变化。

真正的可测试性在所有要做的事情上都很高,我们很早就决定,做到这一点的唯一方法就是自己做饭:绝大多数Reladomo测试都是使用Reladomo自己的测试实用程序编写的! 我们有务实的测试观点。 我们希望测试可以增加长期价值。 Reladomo测试易于设置,并且可以针对内存数据库执行所有生产代码,从而可以进行连续集成测试。 这些测试可帮助开发人员了解与数据库的交互,而无需使用已安装的数据库配置开发环境。

最后,我们不想在性能上有所妥协。 Reladomo最重要,技术上最复杂的部分之一是它的缓存。 这是一个无键,多索引,事务性对象缓存。 将对象作为对象缓存,并确保其数据占用单个内存引用。 对象缓存由引用相同对象的查询缓存增强。 查询缓存很聪明-它不会返回陈旧的结果。 当多个JVM使用Reladomo向同一数据写入时,缓存可以正常工作。 可以在启动时将其配置为按需缓存或完整缓存。 对于正确的数据和应用程序,甚至可以将对象存储在堆外,以便通过复制进行大规模缓存。 我们正在生产中运行的缓存超过200GB。

原则发展

Reladomo被定位为框架,而不是库。 框架超越了库所提供的功能,它通过规定和考虑哪些编码模式合适以及哪些不合适。 Reladomo还会生成代码,并且所生成的API有望在其余的代码中广泛使用。 因此,当务之急是,框架代码和应用程​​序代码必须具有统一的观点。

我们定义我们的核心价值,以便我们的潜在用户可以确定Reladomo是否适合他们:

  • 目标代码要在生产中运行多年甚至数十年。
  • 不要重复自己。
  • 使代码更改变得容易。
  • 以基于域的面向对象的方式编写代码。
  • 不要妥协正确性和一致性。

这些核心价值观及其后果在我们的《 哲学与愿景》文档中进行了详细说明。

可用性和可编程性

我们将使用几个小型领域模型来演示Reladomo的一些功能。 首先,关于宠物的非时间模型:

第二,教科书分类帐的模型:

在此模型中,帐户交易证券(产品),并且该产品具有任意数量的标识符(称为同义词)。 累计余额保留在余额对象中。 余额可以表示有关该帐户的任意数量的累计值,例如数量,应纳税所得额,利息等。您可以在github上查看这些模型的代码

我们将很快看到,这是一个双时态模型的示例。 现在,我们将忽略时间位,它们不会妨碍您。

通过为每个概念对象创建Reladomo对象定义并使用它们生成一些类来定义模型。 我们希望您定义的域类可以用作您的真实业务域。 初始生成后,域中的具体类将永远不会被覆盖。 每当模型或Reladomo版本更改时,都会生成它们的抽象超类。 您可以-并且应该-将方法添加到这些具体类中,然后将其检入版本控制系统中。

Reladomo提供的大多数API都在生成的类上:我们的pet示例中为PetFinder, PetAbstractPetListAbstractPetFinder具有常规的get / set方法和其他一些用于持久性的方法。 API真正有趣的部分位于Finder和List上。

顾名思义,特定于类的Finder(例如PersonFinder)用于查找事物。 这是一个简单的示例:

Person john = PersonFinder.findOne(PersonFinder.personId().eq(8));

请注意,没有要获取和关闭的连接或会话。 在所有上下文中,检索到的对象都是有效的引用。 您可以自由地将其传递给不同的线程,并使它参与事务性工作单元。 如果返回多个对象,则findOne会引发异常。

让我们分解一下这个表达式。 PersonFinder. firstName () PersonFinder. firstName ()Attribute 。 它是类型化的(它是一个StringAttribute ):您可以调用rstName ().eq( "John" ) ,但是不能firstName ().eq(8)firstName ().eq(someDate) 。 它还具有在其他类型的属性上找不到的特殊方法,例如:

PersonFinder.firstName().toLowerCase().startsWith("j")

如T方法oLowerCase(), startsWith()和其他许多人不提供比方说,一个IntegerAttribute ,它有自己的一套专门的方法。

所有这些都创建了两个重要的可用性点:首先,您的IDE可以帮助您编写正确的代码。 其次,当您对模型进行更改时,编译器将找到所有需要更改的位置。

属性上具有创建操作的方法,例如eq(), greaterThan()等。Reladomo中的操作用于通过Finder. findOne or Finder. findMany检索对象Finder. findOne or Finder. findMany Finder. findOne or Finder. findMany Finder. findOne or Finder. findMany 。 操作实现是不变的。 它们可以与and()or()组合在一起:

Operation op = PersonFinder.firstName().eq("John");
op = op.and(PersonFinder.lastName().endsWith("e"));
PersonList johns = PersonFinder.findMany(op);

执行大量IO的应用程序倾向于批量加载数据。 这可能意味着使用条款。 如果我们构造此操作:

Set<String> lastNames = ... // a large set, say 10K elements
PersonList largeList =
    PersonFinder.findMany(PersonFinder.lastName().in(lastNames));

在后台,Reladomo分析您的Operation并生成相应SQL。 对于大型子句将生成什么sql? 在Reladomo中,答案是:“取决于”。 Reladomo可以选择发出多个从句,或者根据目标数据库使用临时表联接。 从用户角度看,该选择是透明的。 Reladomo的实现将根据操作和数据库有效地返回正确的结果。 如果配置发生更改,开发人员不必做出必然会出错的选择,也不必编写复杂的代码来应对变化。 附送电池

主键

Reladomo中的主键是对象属性的任意组合。 无需定义键类或将这些属性区别对待。 我们的理念是复合键在所有模型中都是非常自然的,使用它们应该没有障碍。 在我们的简单交易模型中, ProductSynonym类具有自然的组合键:

<Attribute name="productId" 
    javaType="int" 
    columnName="PRODUCT_ID" 
    primaryKey="true"/>
<Attribute name="synonymType" 
    javaType="String" 
    columnName="SYNONYM_TYPE" 
    primaryKey="true"/>

当然,合成键在某些情况下很有用。 我们支持使用基于表的高性能方法来生成合成密钥。 合成密钥是按批,异步和按需生成的。

人际关系

类之间的关系在模型中定义:

<Relationship name="pets" 
    relatedObject="Pet"
    cardinality="one-to-many" 
    relatedIsDependent="true" 
    reverseRelationshipName="owner">
   this.personId = Pet.personId
</Relationship>

定义关系提供了三种读取功能:

  • 对象上的get方法,如果通过reverseRelationshipName属性将关系标记为双向,则可能使用相关对象上的get方法,例如person.getPets()
  • 导航查找器上的关系,例如PersonFinder. pets () PersonFinder. pets ()
  • 能够在每个查询的基础上深度获取关系。

深度获取是一种有效地检索相关对象的能力,可以避免众所周知的N+1 query problem 。 如果检索某些人对象,则可以要求有效地加载他们的宠物对象。

PersonList people = ...
people.deepFetch(PersonFinder.pets());

或更有趣的例子:

TradeList trades = ...
trades.deepFetch(TradeFinder.account()); // Fetch accounts for these trades
trades.deepFetch(TradeFinder.product()
                    .cusipSynonym()); // Fetch the products and the 
          // products’ CUSIP synonym (a type of identifier) for these trades
trades.deepFetch(TradeFinder.product()
                    .synonymByType("ISN")); // Also fetch the products’ ISN 
                                            // synonym (another identifier).

可以指定可达图的任何部分。 请注意,这是如何不作为模型的一部分实现的。 该模型没有“渴望”或“懒惰”的概念。 这是指定此问题的特定代码段。 因此,更改模型不可能彻底改变现有代码的IO和性能,从而使模型更加敏捷。

创建Operation时可以使用关系:

Operation op = TradeFinder
                  .account()
                  .location()
                  .eq("NY"); // Find all trades 
                             // belonging to NY accounts.
op = op.and(TradeFinder.product()
                  .productName()
                  .in(productNames)); // … and whose product name 
                                      // is included in the supplied list
TradeList trades2 = TradeFinder.findMany(op);

关系在Reladomo中没有实际引用地实现。 这使得在内存和IO方面添加关系成为免费的。

Reladomo中的关系非常灵活。 考虑一个具有许多不同类型的同义词(例如CUSIP,Ticker等)的Product对象的教科书示例。 我们已经在交易模型中定义了这个例子。 从ProductProductSynonym的传统一对多关系几乎不再有用:

<Relationship name="synonyms" 
    relatedObject="ProductSynonym" 
    cardinality="one-to-many">
   this.productId = ProductSynonym.productId
</Relationship>

这样做的原因是,很少要在查询中返回产品的所有同义词。 两种类型的高级关系使这个常见示例更加有用。 具有常量表达式的关系可以在模型中表示重要的业务概念。 例如,如果我们想按名称访问产品的CUSIP同义词,则添加以下关系:

<Relationship name="cusipSynonym" 
    relatedObject="ProductSynonym" 
    cardinality="one-to-one">
   this.productId = ProductSynonym.productId and
   ProductSynonym.synonymType = "CUS"
</Relationship>

请注意,我们如何在上面的deepFetch和query示例中使用此cusipSynonym关系。 这具有三个好处:首先,我们不必在代码中重复“ CUS ”。 其次,如果我们想要的只是CUSIP,我们无需支付检索所有同义词的IO成本。 第三,查询的可读性和书写习惯更加丰富。

可组合性

基于字符串的查询的最大问题之一是它们很难编写。 通过使用类型安全的,基于域的面向对象的查询语言,我们将可组合性提高到了新的水平。 为了说明这一点,让我们看一个有趣的例子。

在我们的交易模型中,交易对象和余额对象都与帐户和产品都有关系。 假设您有一个GUI,可以通过过滤帐户和产品来检索交易。 一个不同的窗口允许通过过滤帐户和产品来检索余额。 自然,因为我们要处理的是相同的实体,所以过滤器是相同的。 使用Reladomo,可以轻松地在两者之间共享代码。 我们已经将产品和帐户业务逻辑抽象为几个GUI组件 ,然后使用它们:

public BalanceList retrieveBalances()
{
   Operation op = BalanceFinder.businessDate().eq(readUserDate());
   op = op.and(BalanceFinder.desk().in(readUserDesks()));

   Operation refDataOp = accountComponent.getUserOperation(
      BalanceFinder.account());

   refDataOp = refDataOp.and(
      productComponent.getUserOperation(BalanceFinder.product()));

   op = op.and(refDataOp);

   return BalanceFinder.findMany(op);
}

这将发出以下SQL:

select t0.ACCT_ID,t0.PRODUCT_ID,t0.BALANCE_TYPE,t0.VALUE,t0.FROM_Z,
       t0.THRU_Z,t0.IN_Z,t0.OUT_Z
from   BALANCE t0
       inner join PRODUCT t1
               on t0.PRODUCT_ID = t1.PRODUCT_ID
       inner join PRODUCT_SYNONYM t2
               on t1.PRODUCT_ID = t2.PRODUCT_ID
       inner join ACCOUNT t3
               on t0.ACCT_ID = t3.ACCT_ID
where  t1.FROM_Z <= '2017-03-02 00:00:00.000'
       and t1.THRU_Z > '2017-03-02 00:00:00.000'
       and t1.OUT_Z = '9999-12-01 23:59:00.000'
       and t2.OUT_Z = '9999-12-01 23:59:00.000'
       and t2.FROM_Z <= '2017-03-02 00:00:00.000'
       and t2.THRU_Z > '2017-03-02 00:00:00.000'
       and t2.SYNONYM_TYPE = 'CUS'
       and t2.SYNONYM_VAL in ( 'ABC', 'XYZ' )
       and t1.MATURITY_DATE < '2020-01-01'
       and t3.FROM_Z <= '2017-03-02 00:00:00.000'
       and t3.THRU_Z > '2017-03-02 00:00:00.000'
       and t3.OUT_Z = '9999-12-01 23:59:00.000'
       and t3.CITY = 'NY'
       and t0.FROM_Z <= '2017-03-02 00:00:00.000'
       and t0.THRU_Z > '2017-03-02 00:00:00.000'
       and t0.OUT_Z = '9999-12-01 23:59:00.000'

ProductComponent和AccountComponent类可完全重用于贸易(请参见BalanceWindow和TradeWindow)。 但是可组合性并不止于此。 假设业务需求已更改,并且仅对于“余额”窗口,用户希望使用适合帐户筛选器产品筛选器的余额。 使用Reladomo,那将是一行代码更改:

refDataOp = refDataOp.or(
      productComponent.getUserOperation(BalanceFinder.product()));

发出SQL现在非常不同:

select t0.ACCT_ID,t0.PRODUCT_ID,t0.BALANCE_TYPE,t0.VALUE,t0.FROM_Z,
       t0.THRU_Z,t0.IN_Z,t0.OUT_Z
from   BALANCE t0
       left join ACCOUNT t1
              on t0.ACCT_ID = t1.ACCT_ID
                 and t1.OUT_Z = '9999-12-01 23:59:00.000'
                 and t1.FROM_Z <= '2017-03-02 00:00:00.000'
                 and t1.THRU_Z > '2017-03-02 00:00:00.000'
                 and t1.CITY = 'NY'
       left join PRODUCT t2
              on t0.PRODUCT_ID = t2.PRODUCT_ID
                 and t2.FROM_Z <= '2017-03-02 00:00:00.000'
                 and t2.THRU_Z > '2017-03-02 00:00:00.000'
                 and t2.OUT_Z = '9999-12-01 23:59:00.000'
                 and t2.MATURITY_DATE < '2020-01-01'
       left join PRODUCT_SYNONYM t3
              on t2.PRODUCT_ID = t3.PRODUCT_ID
                 and t3.OUT_Z = '9999-12-01 23:59:00.000'
                 and t3.FROM_Z <= '2017-03-02 00:00:00.000'
                 and t3.THRU_Z > '2017-03-02 00:00:00.000'
                 and t3.SYNONYM_TYPE = 'CUS'
                 and t3.SYNONYM_VAL in ( 'ABC', 'XYZ' )
where  ( ( t1.ACCT_ID is not null )
          or ( t2.PRODUCT_ID is not null
               and t3.PRODUCT_ID is not null ) )
       and t0.FROM_Z <= '2017-03-02 00:00:00.000'
       and t0.THRU_Z > '2017-03-02 00:00:00.000'
       and t0.OUT_Z = '9999-12-01 23:59:00.000'

请注意,此SQL与先前SQL在结构上有所不同。 需求从“和”更改为“或”,我们将代码从“和”更改为“或”,并且可以正常工作。 包括电池! 如果使用基于字符串的查询或公开“联接”的任何查询机制来实现,则需要将需求从“和”更改为“或”。

CRUD和工作单位

Reladomo的CRUD API在对象和列表实现中。 该对象具有诸如insert()和delete()之类的方法,而列表具有批量方法。 没有“保存”或“更新”方法。 在持久对象上设置值将更新数据库。 预期大多数写入将在事务中执行,该事务通过命令模式实现:

MithraManagerProvider.getMithraManager().executeTransactionalCommand(
tx ->
{
   Person person = PersonFinder.findOne(PersonFinder.personId().eq(8));
   person.setFirstName("David");
   person.setLastName("Smith");
   return person;
});

UPDATE PERSON
SET FIRST_NAME='David', LAST_NAME='Smith'
WHERE PERSON_ID=8

对数据库的写入进行合并和批处理,唯一的约束是正确性。

PersonList对象具有许多有用的方法,这些方法可提供基于集合的API。 例如,您可以执行以下操作:

Operation op = PersonFinder.firstName().eq("John");
op = op.and(PersonFinder.lastName().endsWith("e"));
PersonList johns = PersonFinder.findMany(op);
johns.deleteAll();

从所有方面来看,您可能会认为这首先解析了列表,然后一个一个地删除了个人记录,但事实并非如此。 相反,它将发出以下(事务性)查询:

DELETE from PERSON
WHERE LAST_NAME like '%e' AND FIRST_NAME = 'John'

很好,但这不是真正的生产应用程序所需的唯一批量删除类型。 考虑应用程序需要清除旧数据的情况。 显然,该数据已不再使用,因此不需要在整个数据集中进行整体交易。 可能需要在后台过程中以尽力而为的方式删除数据。 为此,您可以使用:

johns.deleteAllInBatches(1000);

这会根据目标数据库发出不同类型的查询:

MS-SQL:

delete top(1000) from PERSON 
where LAST_NAME like '%e' and FIRST_NAME = 'John'

PostgreSQL:

delete from PERSON 
where ctid  = any (array(select ctid 
                         from PERSON 
                         where LAST_NAME like '%e' 
                         and FIRST_NAME = 'John' 
                         limit 1000))

而且,它非常努力地完成工作,处理临时故障并在一切完成后返回。 这就是我们所说的“包含电池”的含义-常见的图案易于烘烤且易于烘焙。

易于整合

我们对Reladomo进行了结构设计,以使其易于与您的代码集成。

首先,Reladomo具有很少的依赖性。 在运行时,类路径上只有六个jar(主库jar和五个浅依赖性)。 对于完整的生产部署,您需要一个驱动程序类,一个slf4j日志实现和您自己的代码。 这给了您极大的自由,可以随意放入其他任何东西,而不必担心jar冲突。

第二,我们致力于在Reladomo中提供向后兼容性。 您应该能够在不破坏代码的情况下升级Reladomo的版本。 如果我们计划进行一项更改,从而导致向后不兼容的更改,那么我们将确保您至少有一年的时间才能切换到新API。

结论

尽管我们非常重视可用性(“包括电池!”),但我们认识到存在许多不同的用例,并且试图将所有人的所有东西都用不上。

困扰传统ORM的问题之一是抽象泄漏。 如果正确实施,我们的核心价值观将创建一个非常引人注目的系统,从而避免了这些渗漏的抽象。 Reladomo中没有本机查询或存储过程支持,这并非偶然。 我们非常努力地不写说明“如果基础数据库支持Y,则支持功能X”的文档。

Reladomo的功能比我们这里未介绍的要多。 随时在Github上访问我们,看看文档Katas (我们的学习Reladomo的教程集)。 在本文的第二部分(六月),我们将展示Reladomo的一些性能,可测试性和企业功能。

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

java orm 开源框架

jdao是一个轻量级的orm工具包,通过简单配置或者连接数据库提供表名它便可以自动生成与数据库表一一对应的dao类,生成的dao类提供了与SQL无关的增删改查的功能。在复杂的查询SQL中,我建议编程人员自己编写高效的SQL,再通过jdao查询后返回封装好的QureyDao类,很方便的获取数据。在jdao底层,目前有spring的jdbcTemplate实现与我自己封装的jdaoHandlerImpl实现。jdao有支持事务,支持批量插入数据等特性。同时jdao底层数据库操作提供接口,编程人员可以自己实现。       如果你觉得hibernate,ibatis等orm框架过于笨重,不烦试下jdao,它可以在团队开发中规范团队持久层代码,较少开发量,特别在单表操作上基本是对象操作,对于复杂SQL查询也有较好的封装。一、使用DAO方式操作数据:查询SQL: select value,rowname from hstest where id between 2 and 10;jdao对象操作如下:Hstest t = new Hstest();t.where(Hstest.ID.BETWEEN(2, 10));t.query(Hstest.VALUE, Hstest.ROWNAME);插入SQL:  insert into hstest (id,rowname,value) values(1,"donnie","wuxiaodong")jdao对象操作如下:Hstest t = new Hstest();t.setId(1);t.setRowname("donnie");t.setValue("wuxiaodong");t.save();批量插入SQL:  insert into hstest (id,rowname,value) values(1,"donnie1","wuxiaodong1"),(2,"donnie2","wuxiaodong2"),(3,"donnie3","wuxiaodong3")jdao对象操作如下:Hstest t = new Hstest();t.setId(1);t.setRowname("donnie1");t.setValue("wuxiaodong1");t.addBatch();t.setId(2);t.setRowname("donnie2");t.setValue("wuxiaodong2");t.addBatch();t.setId(3);t.setRowname("donnie3");t.setValue("wuxiaodong3");t.addBatch();t.batchForSave();更新SQL:  update hstest set rowname="wuxiaodong",value="wuxiaodong" where id=10jdao对象操作如下:Hstest t = new Hstest();t.setRowname("wuxiaodong");t.setValue("wuxiaodong");t.where(Hstest.ID.EQ(10));t.update();删除SQL:  delete from hstest where id=2jdao对象操作如下:Hstest t = new Hstest();t.where(Hstest.ID.EQ(2));t.delete();二、使用QueryDao查询数据,建议用于复杂SQL查询,单表增删改查建议还是使用DAO对象操作。QueryDao qd = new QueryDao(JdaoHandlerFactory.getDBHandler4c3p0(), "select id,rowname from hstest limit ?,?", 0, 10);//获取数据方式一while (qd.hasNext()) {    QueryDao q = qd.next();    //获取字段方式一    System.out.println(q.fieldValue(1) "   " q.fieldValue(2));    //获取字段方式二    System.out.println(q.fieldValue("id") "   " q.fieldValue("rowname"));}//获取数据方式二for(QueryDao q:qd.queryDaoList()){    System.out.println(q.fieldValue(1) "   " q.fieldValue(2));} 标签:orm  jdao
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值