【问题标题】:Migrating from hand-written persistence layer to ORM从手写持久层迁移到 ORM
【发布时间】:2011-02-21 11:29:25
【问题描述】:

我们目前正在评估从手写持久层迁移到 ORM 的选项。

我们有一堆遗留的持久对象(约 200 个),它们实现了这样的简单接口:

interface JDBC {
    public long getId();
    public void setId(long id);
    public void retrieve();
    public void setDataSource(DataSource ds);
}

当调用retrieve() 时,对象通过向使用它在setter 中接收到的ID 提供的连接发出手写SQL 查询来填充自己(这通常是查询的唯一参数)。它自己管理其语句、结果集等。有些对象有retrive() 方法的特殊风格,比如retrieveByName(),在这种情况下会发出不同的SQL。

查询可能相当复杂,我们经常连接几个表来填充表示与其他对象的关系的集合,有时连接查询是在特定的 getter 中按需发出的(延迟加载)。所以基本上,我们已经手动实现了 ORM 的大部分功能。

原因是性能。我们对速度的要求非常高,早在 2005 年(编写此代码时)性能测试表明,主流 ORM 都没有手写 SQL 这么快。

我们现在面临的让我们想到ORM的问题是:

  • 此代码中的大多数路径都经过充分测试并且很稳定。但是,一些很少使用的代码容易出现很难检测到的结果集和连接泄漏
  • 我们目前正在通过向持久层添加缓存来提高性能,而在此设置中手动维护缓存对象是一个巨大的痛苦
  • 在 DB 架构更改时支持此代码是一个大问题。

我正在寻找对我们来说可能是最佳替代方案的建议。据我所知,ORM 在过去 5 年中取得了进步,所以现在可能有一个可以提供可接受的性能。当我看到这个问题时,我们需要解决这些问题:

  • 找到某种方法来重用至少部分编写的 SQL 来表达映射
  • 无需手动分解结果即可发出原生 SQL 查询(即避免手动 rs.getInt(42),因为它们对架构更改非常敏感)
  • 添加非侵入式缓存层
  • 保留性能数据。

您有什么可以推荐的 ORM 框架吗?

更新来感受一下我们在谈论什么样的性能数据:

  • 后端数据库是 TimesTen,内存数据库,与 JVM 运行在同一台机器上
  • 我们发现,将 rs.getInt("column1") 更改为 rs.getInt(42) 会带来我们认为显着的性能提升。

【问题讨论】:

    标签: java sql database performance orm


    【解决方案1】:

    如果您想要一个标准的持久层来让您发出原生 SQL 查询,请考虑使用 iBATIS。这是您的对象和 SQL 之间的一个相当薄的映射。 http://ibatis.apache.org/

    对于缓存和惰性连接,Hibernate 可能是更好的选择。我没有将 iBATIS 用于这些目的。

    Hibernate 提供了很大的灵活性,允许您在遍历对象图时为延迟加载指定某些默认值,还可以在您需要更清楚的加载时间时使用 SQL 或 HQL 查询预取数据到您心中的内容。但是,转换工作对您来说会很复杂,因为它在学习和配置方面具有相当高的进入门槛。注释让我更容易做到这一点。

    您没有提到切换到标准框架的两个好处: (1) 当您有大量的网站和论坛来支持您时,消除错误会变得更容易。 (2) 新员工更便宜、更轻松、更快捷。

    祝您好运,解决您的性能和可用性问题。您指出的权衡非常普遍。对不起,如果我传福音。

    【讨论】:

    • 您忘记了一个好处,恕我直言,这很重要:您不再依赖 BBDD 提供程序及其方言(MySQL、Postgres)。您可以从一个切换到另一个,只需更改 hibernate.cfg.xml 中的一行。
    【解决方案2】:

    对于您的大部分查询,我会选择休眠模式。它被广泛使用,有据可查,并且通常性能良好。如果 hibernate 不能产生足够有效的查询,您可以使用手写 SQL。 Hibernate 在指定域对象映射到的表名和列方面为您提供了很多控制权,并且在大多数情况下,您可以对其进行改造以适应现有模式。

    • 找到某种方法来重用至少部分编写的 SQL 来表达映射 映射使用注释在 JPA 中表示。在创建 JPQL 查询时,您可以使用现有 SQL 作为指南。

    • 添加非侵入式缓存层

    hibernate 中的缓存是自动且透明的,除非您特别选择参与其中。您可以将实体标记为只读,或从缓存中逐出,控制何时将更改刷新到数据库(当然是在事务内部 - 当网络延迟成为问题时,自动使用批处理可以提高性能。)

    • 有可能发出原生 无需 SQL 查询 手动分解他们的结果(即 避免手动 rs.getInt(42) 因为他们 对架构更改非常敏感)

    Hibernate 允许您编写 SQL,并将其映射到您的实体。您不直接处理 ResultSet - hibernate 负责解构到您的实体中。请参阅休眠手册中的Chpt 16, Native SQL

    • 在 DB 架构更改时支持此代码是一个大问题。

    管理架构更改仍然很麻烦,因为您现在实际上拥有两个架构 - 数据库架构和 JPA 映射(一个对象架构)。如果您选择让 hibernate 生成 db 模式并将数据移动到该模式,则您不再直接负责进入数据库的内容,因此您将面临管理对机器生成的模式的自动更改。有一些工具可以提供帮助,例如 dbmigrate 和 liquibase,但它不是在公园里散步。相反,如果您手动管理 db 架构,那么您将不得不仔细重新设计您的实体、JPA 注释和查询以适应架构更改。添加列和新实体相对简单,但更复杂的更改(例如将单个属性更改为属性集合或重构对象层次结构)将涉及更广泛的更改。没有简单的方法可以解决这个问题 - db 或 hibernate 是决定模式的“主”,当一个更改时,另一个必须跟随。代码更改并没有那么糟糕——根据我的经验,迁移数据是很困难的。但这是数据库的一个基本问题,并且会出现在您选择的任何解决方案中。

    所以,总而言之,我会选择休眠,并使用 JPA 接口。

    【讨论】:

      【解决方案3】:

      我最近研究了一堆 Java ORM,但没有想出比 Hibernate 更好的东西。 Hibernate 的性能可能会让您达到目标并满足您的性能目标。

      很多人认为迁移到 Hibernate 会让一切变得如此出色,但实际上只是将一组问题从 JDBC 查询迁移到了 Hibernate 调优中。阅读大量书籍或(最好)聘请“Hibernate guy”来提供帮助。

      在重构期间,我建议您使用 JPA,这样您就可以在下一个大事件出现时(或者您迁移到 Oracle)拔出并重新插入新的持久性提供程序

      【讨论】:

      • +1 不要低估使用 hibernate 和 JPA 正确开始的价值。一旦设置了一个好的项目种子,大多数强大的开发人员都可以从那里得到它(在此过程中会遇到一些小问题)
      【解决方案4】:

      您真的需要迁移吗?是什么迫使你搬家?这里有一些真正的需求还是有人只是在发明工作(“宇航员建筑师”)?

      不过,我同意上述答案 - 如果您必须搬家 - Hibernate 或 iBatis 是不错的选择。 iBatis 特别是如果您想“更接近” SQL。

      【讨论】:

      • 是的,我们必须搬家。基本上,当前的性能必须进一步提高,除了添加缓存之外,我们看不到任何可能性。在这个设置中手动实现缓存层是一个巨大的痛苦。因此,我们准备好在 ORM 层损失 1-2% 的性能,而使用 ORM 的缓存获得 10% 或更多。
      • 我认为切换到 ORM 以提高性能是一种有很多风险的策略——即使你通过缓存获得了一些东西,比如急切/延迟加载和 ORM 的变幻莫测实施可能会回来咬你 - 再加上 ORM 可能无法提供最好的查询。我建议通过替换一些 JDBC DAO 继续进行 - 一些简单的和一些复杂的,看看它是如何适合和帮助的。在解决所有 200 个问题之前先进行试点。
      • @Sergey Mikhanov 为什么实现缓存层如此困难?您是否真的需要缓存(您在内存中使用 TimesTen)。我可以从经验告诉你,ORM 缓存并不像你想象的那么棒。您希望尽可能高地缓存堆栈。也很容易用 AOP 实现缓存(编译时 AspectJ AOP 并不慢)。
      【解决方案5】:

      如果您需要更高的性能:删除数据库(用于在线工作)并直接处理持久性。添加缓存不会帮助您使用 TimesTen DB,它只会添加一个额外的副本(减慢您的速度)。

      您可能想看看 GemFire。

      【讨论】:

        【解决方案6】:

        这里已经有很多好的建议,我不会重复。我没有看到的唯一可能对您有用的建议是将参考数据缓存在内存中。

        我过去做过很多这样的事情,它确实节省了很多时间。如果您有大量相当静态的引用表,请在启动时将它们全部加载到内存中,并每隔几分钟刷新一次。这样一来,您就不会一遍又一遍地访问数据库以获得永不改变的数据。

        【讨论】:

        • 唯一的缺点是,如果用户修改此参考数据并且您违反了他们立即看到更改的期望,用户可能(可以理解地)感到沮丧。也许更好的方案是检查引用表在正常加载时是否发生了变化,如果没有,只需使用缓存的数据。
        • 这当然是对的,但是如果您在每次调用时都访问数据库以检查更改 - 那么您首先会失去缓存数据的大部分好处.需求将决定您如何进行,但通常我将这种模式用于很少更改的数据,并且我可以每 X 分钟重新加载一次并且没问题。
        • 好吧,您可以在此处添加标准缓存行为..即一旦更改参考数据,缓存就会失效。我这样做主要是针对与配置相关的数据。将其存储在应用程序内存和一个挂钩以清除该数据。一旦某些数据发生变化,我就会重新加载。这样,始终保证以最少的 DB 调用获得最新值
        猜你喜欢
        • 1970-01-01
        • 2021-11-12
        • 2018-04-29
        • 2013-09-05
        • 2011-01-18
        • 2020-09-26
        • 1970-01-01
        • 2014-10-23
        • 1970-01-01
        相关资源
        最近更新 更多