【问题标题】:Is Functional to Relational mapping easier than Object to Relational?函数到关系映射比对象到关系更容易吗?
【发布时间】:2010-09-18 02:32:57
【问题描述】:

对象-关系映射已得到很好的讨论,包括在这里。我有一些方法以及陷阱和妥协的经验。真正的解决似乎需要对 OO 或关系模型本身进行更改。

如果使用函数式语言,是否会出现同样的问题?在我看来,这两种范式应该比 OO 和 RDBMS 更好地结合在一起。在 RDBMS 中以集合方式思考的想法似乎与函数式方法所承诺的自动并行性相吻合。

有没有人有任何有趣的意见或见解?行业现状如何?

【问题讨论】:

  • 其实,这真的有意义吗?函数式编程不提供任何标准的数据建模方式,这在某种程度上可以与关系或 OO 数据建模进行比较。因此,要求“映射”恕我直言并不是一个有意义的问题。有人可能会问,将函数概念添加到 RDBMS 是否有意义,事实上 SQL 已经有了一些函数概念。

标签: database orm functional-programming rdbms


【解决方案1】:

我猜函数到关系映射应该比 OO 到 RDBMS 更容易创建和使用。只要你只查询数据库,就是这样。我真的(还没有)看到如何以一种不错的方式进行没有副作用的数据库更新。

我看到的主要问题是性能。今天的 RDMS 并非设计用于功能查询,在很多情况下可能表现不佳。

【讨论】:

    【解决方案2】:

    我认为,正如 Sam 所提到的,如果应该更新数据库,则必须面对与 OO 世界相同的并发问题。由于 RDBMS 的数据、事务等状态,程序的功能性质可能比对象性质更成问题。

    但是对于阅读来说,函数式语言对于某些问题域可能更自然(因为它似乎与 DB 无关)

    功能RDBMS 映射与OORDMBS 映射应该没有太大区别。但我认为这在很大程度上取决于你想使用什么样的数据类型,如果你想用全新的数据库模式开发一个程序,或者对遗留的数据库模式做一些事情等等。

    例如,关联的延迟获取等可能可以通过一些延迟评估相关的概念很好地实现。 (尽管它们也可以用 OO 很好地完成)

    编辑:通过谷歌搜索,我找到了HaskellDB(Haskell 的 SQL 库)——这值得一试吗?

    【讨论】:

      【解决方案3】:

      扩展关系数据库的难题是扩展事务、数据类型不匹配、自动查询翻译以及诸如N+1 Select 之类的事情,这些都是离开关系系统的基本问题,而且——在我看来——不会改变改变接收编程范式。

      【讨论】:

      • FRM 通常不受 N+1 问题的影响。看看这里:slick.lightbend.com/talks/2013_scaladays/2013_scaladays.pdf
      • @ChoppyTheLumberjack:这很有趣,因为 Slick 的语法使 Slick 逻辑看起来像普通的 Scala 逻辑,可以轻松隐藏“循环”是在客户端还是在服务器上运行的事实。未经训练的眼睛。没有。 一般而言,没有一个单一的数据库接口 API 可以不受 N+1 问题的影响,因为 API 用户可以在他们应该加入所有客户端时循环进入客户端!
      • 嗨@LukasEder。当然,客户可以在自己的逻辑中执行循环,但这是在客户手中而不是框架。像 Slick、Quill 等框架...通过它们的 monadic API 专门告诉你“数据库调用到这里”,而不是像 Hibernate 将数据库调用隐藏在代理解引用后面,即一些更高级别的 API 使用实体的字段进行业务逻辑甚至不知道。 N+1 免疫是 Slick/Quill 框架功能的一部分,这种说法是完全公平的。无论如何,用户总是可以在自己的脚下开枪。
      • @ChoppyTheLumberjack:我明白你的意思。为了争论,如果优化器无法在适当的时候将生成的相关子查询转换为外部联接,您仍然可以在数据库中获得“N+1”,并且使用 FRM,您可能仍然无法控制这一点。反正。如果你说“FRM 不太容易出现 N+1 问题”,我就不会提出这个问题
      【解决方案4】:

      我没有做过函数关系映射,本身,但我使用函数式编程技术来加快对 RDBMS 的访问。

      从数据集开始,对其进行一些复杂的计算,然后存储结果是很常见的,例如,其中的结果是原始数据的一个子集以及附加值。命令式方法要求您使用额外的 NULL 列存储初始数据集,进行计算,然后使用计算值更新记录。

      看起来很合理。但问题是它会变得非常缓慢。如果您的计算除了更新查询本身之外还需要另一个 SQL 语句,或者甚至需要在应用程序代码中完成,那么您实际上必须(重新)搜索您在计算后更改的记录以将结果存储在正确的行中.

      您可以通过简单地为结果创建一个新表来解决此问题。这样,您总是可以插入而不是更新。您最终拥有另一个表,复制了键,但您不再需要在存储 NULL 的列上浪费空间 - 您只存储您拥有的内容。然后,您将结果加入您的最终选择中。

      我(ab)以这种方式使用 RDBMS,最终编写的 SQL 语句看起来大多像这样......

      create table temp_foo_1 as select ...;
      create table temp_foo_2 as select ...;
      ...
      create table foo_results as
        select * from temp_foo_n inner join temp_foo_1 ... inner join temp_foo_2 ...;
      

      这本质上是在创建一堆不可变的绑定。不过,好消息是您可以同时处理整个场景。有点让您想起可以使用矩阵的语言,例如 Matlab。

      我想这也将使并行性更容易。

      一个额外的好处是不必指定以这种方式创建的表的列类型,因为它们是从它们从中选择的列中推断出来的。

      【讨论】:

      • 这根本不是滥用,它是使用 SQL 的正确方法。您以基于集合的方式工作,这是最好的方式。 Oracle 可以并行执行这些 SQL 语句,我认为其他数据库也可以。
      【解决方案5】:

      这取决于您的需求

      1. 如果您想专注于数据结构,请使用 JPA/Hibernate 等 ORM
      2. 如果您想了解治疗方法,请查看 FRM 库:QueryDSL 或 Jooq
      3. 如果您需要针对特定​​数据库调整 SQL 请求,请使用 JDBC 和本机 SQL 请求

      各种“关系映射”技术的优势在于可移植性:您可以确保您的应用程序可以在大多数 ACID 数据库上运行。 否则,您将在手动编写 SQL 请求时应对各种 SQL 方言之间的差异。

      当然你可以限制自己使用 SQL92 标准(然后做一些函数式编程),或者你可以在 ORM 框架中重用函数式编程的一些概念

      ORM 的优势是建立在会话对象之上的,会话对象可能会成为瓶颈:

      1. 只要底层数据库事务正在运行,它就会管理对象的生命周期。
      2. 它维护 Java 对象和数据库行之间的一对一映射(并使用内部缓存来避免重复对象)。
      3. 它会自动检测关联更新和要删除的孤立对象
      4. 它使用乐观或悲观锁处理并发问题。

      尽管如此,它的优点也是它的缺点:

      1. 会话必须能够比较对象,因此您需要实现 equals/hashCode 方法。 但是对象相等性必须植根于“业务密钥”而不是数据库 ID(新的瞬态对象没有数据库 ID!)。 但是,一些具体化的概念没有业务平等(例如操作)。 一种常见的解决方法依赖于 GUID,这往往会让数据库管理员感到不安。

      2. 会话必须监视关系更改,但其映射规则会推动使用不适合业务算法的集合。 有时您想使用 HashMap 但 ORM 将要求密钥是另一个“富域对象”而不是另一个轻量级对象...... 然后你必须在作为键的富域对象上实现对象相等...... 但是你不能,因为这个对象在商业世界中没有对应物。 因此,您会退回到一个必须迭代的简单列表(并且会导致性能问题)。

      3. ORM API 有时不适合实际使用。 例如,现实世界的 Web 应用程序尝试通过在您获取数据时添加一些“WHERE”子句来强制执行会话隔离...... 那么“Session.get(id)”就不够用了,你需要转向更复杂的DSL(HSQL、Criteria API)或者回到原生SQL

      4. 数据库对象与专用于其他框架的其他对象冲突(例如 OXM 框架 = 对象/XML 映射)。 例如,如果您的 REST 服务使用 jackson 库来序列化业务对象。 但是这个杰克逊正好映射到一个冬眠者。 然后你要么合并两者,你的 API 和数据库之间就会出现强耦合 或者你必须实现一个翻译,你从 ORM 中保存的所有代码都在那里丢失了......

      另一方面,FRM 是“对象关系映射”(ORM) 和原生 SQL 查询(使用 JDBC)之间的权衡

      解释 FRM 和 ORM 之间差异的最佳方法是采用 DDD 方法。

      • 对象关系映射支持使用“富域对象”,这是 Java 类,其状态在数据库事务期间是可变的
      • 函数关系映射依赖于不可变的“不良域对象”(以至于每次要更改其内容时都必须克隆一个新对象)

      它释放了对 ORM 会话的约束,并且大部分时间依赖于 SQL 上的 DSL(因此可移植性并不重要) 但另一方面,您必须查看交易细节,并发问题

      List<Person> persons = queryFactory.selectFrom(person)
        .where(
          person.firstName.eq("John"),
          person.lastName.eq("Doe"))
        .fetch();
      

      【讨论】:

        【解决方案6】:

        ORM 的目的是什么?

        使用 ORM 的主要目的是在网络模型(面向对象、图形等)和关系模型之间架起一座桥梁。两种模型之间的主要区别非常简单。是父母指向孩子(网络模型)还是孩子指向父母(关系模型)。

        考虑到这种简单性,我相信there is no such thing as an "impedance mismatch" 介于两个模型之间。人们通常遇到的问题纯粹是特定于实现的,如果在客户端和服务器之间有更好的数据传输协议,应该是可以解决的。

        SQL 如何解决我们在使用 ORM 时遇到的问题?

        特别是,third manifesto 试图通过允许嵌套集合来解决 SQL 语言和关系代数的缺点,这些集合已在各种数据库中实现,包括:

        • Oracle(可能是最复杂的实现)
        • PostgreSQL(在某种程度上)
        • Informix
        • SQL Server、MySQL 等(通过 XML 或 JSON“模拟”)

        在我看来,如果所有数据库都实现了 SQL 标准 MULTISET() 运算符(例如 Oracle 实现),人们将不再使用 ORM 进行映射(也许仍然用于对象图持久性),因为它们可以直接从内部实现嵌套集合数据库,例如这个查询:

        SELECT actor_id, first_name, last_name,
          MULTISET (
            SELECT film_id, title
            FROM film AS f
            JOIN film_actor AS fa USING (film_id)
            WHERE fa.actor_id = a.actor_id
          ) AS films
        FROM actor AS a
        

        会将所有演员及其电影作为嵌套集合产生,而不是非规范化的连接结果(每部电影重复演员)。

        客户端的功能范式

        客户端的函数式编程语言是否更适合数据库交互的问题实际上是正交的。 ORM 有助于对象图的持久性,因此如果您的客户端模型是一个图,并且您希望它是一个图,那么您将需要一个 ORM,无论您是否使用函数式编程语言来操作该图。

        但是,由于面向对象在函数式编程语言中的惯用性较低,因此您不太可能将每个数据项硬塞到一个对象中。对于编写 SQL 的人来说,投影任意 元组 是非常自然的。 SQL 包含结构类型。每个 SQL 查询都定义了自己的行类型,而无需事先为其分配名称。这与函数式程序员产生了很好的共鸣,尤其是在类型推断很复杂的情况下,在这种情况下,您永远不会想到将 SQL 结果映射到某个先前定义的对象/类。

        在 Java 中使用 jOOQ from this blog post 的示例可能是:

        // Higher order, SQL query producing function:
        public static ResultQuery<Record2<String, String>> actors(Function<Actor, Condition> p) {
            return ctx.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
                      .from(ACTOR)
                      .where(p.apply(ACTOR)));
        }
        

        与使用某些 ORM 抽象 SQL 语言或使用 SQL 的“基于字符串”的自然特性相比,这种方法导致 SQL 语句的组合性好得多。现在可以使用上述功能,例如像这样:

        // Get only actors whose first name starts with "A"
        for (Record rec : actors(a -> a.FIRST_NAME.like("A%")))
            System.out.println(rec);
        

        基于 SQL 的 FRM 抽象

        一些 FRM 尝试对 SQL 语言进行抽象,通常是出于以下原因:

        • 他们声称 SQL 不够可组合(jOOQ 反驳了这一点,很难做到正确)。
        • 他们声称 API 用户更习惯于“原生”收集 API,例如JOIN 被翻译成flatMap()WHERE 被翻译成filter() 等等。

        回答你的问题

        FRM 并不比 ORM“更容易”,它解决了一个不同的问题。事实上,FRM 并没有真正解决任何问题,因为 SQL 作为一种声明式编程语言本身(与函数式编程没有太大区别),与其他函数式客户端编程语言非常匹配。因此,如果有任何作用,FRM 只是在 SQL、外部 DSL 和您的客户端语言之间架起了一座桥梁。

        (我在jOOQ后面的公司工作,所以这个答案有偏见)

        【讨论】:

          【解决方案7】:

          数据库和函数式编程可以融合。

          例如:

          Clojure 是一种基于关系数据库理论的函数式编程语言。

                         Clojure -> DBMS, Super Foxpro
                             STM -> Transaction,MVCC
          Persistent Collections -> db, table, col
                        hash-map -> indexed data
                           Watch -> trigger, log
                            Spec -> constraint
                        Core API -> SQL, Built-in function
                        function -> Stored Procedure
                       Meta Data -> System Table
          
          

          注意:在最新的 spec2 中,spec 更像 RMDB。 见:spec-alpha2 wiki: Schema-and-select

          我主张:在 hash-map 之上构建关系数据模型,以实现 NoSQL 和 RMDB 优势的结合。这实际上是 posgtresql 的反向实现。

          Duck Typing:如果它看起来像鸭子并且叫起来像鸭子,那它一定是鸭子。

          如果 clojure 的数据模型像 RMDB,clojure 的工具像 RMDB,clojure 的数据操作像 RMDB,那么 clojure 必须是 RMDB。

          Clojure is a functional programming language based on relational database theory

          Everything is RMDB

          Implement relational data model and programming based on hash-map (NoSQL)

          【讨论】:

          • 这不清楚或没有帮助。您的链接也不清楚。您的帖子和链接没有解释“在哈希图之上构建关系数据模型”或“实现 NoSQL 和 RMDB 优势的组合”或链接标题的含义或证明它们的合理性。也不清楚您认为 RM 是什么,RM DB 是什么,或者您认为 RM DBMS 是什么。
          猜你喜欢
          • 2011-04-24
          • 2013-05-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-02
          • 2015-07-25
          相关资源
          最近更新 更多