20

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

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

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

4

7 回答 7

17

ORM 的目的是什么?

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

考虑到这种简单性,我相信两个模型之间不存在“阻抗不匹配”之类的东西。人们通常遇到的问题纯粹是特定于实现的,如果在客户端和服务器之间有更好的数据传输协议,应该是可以解决的。

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

特别是,第三个宣言试图通过允许嵌套集合来解决 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 结果映射到某个先前定义的对象/类。

使用此博客文章中的jOOQ 的 Java 示例可能是:

// 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)));
}

与 SQL 语言由一些 ORM 抽象或使用 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背后的公司工作,所以这个答案是有偏见的)

于 2018-10-09T09:32:35.567 回答
11

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

于 2008-10-20T13:14:13.273 回答
7

这取决于您的需求

  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();
于 2016-07-01T10:03:46.367 回答
3

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

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

于 2008-10-20T12:09:43.170 回答
3

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

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

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

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

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

于 2008-10-20T12:27:50.840 回答
3

我本身没有做过函数关系映射,但我使用函数式编程技术来加速对 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。

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

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

于 2008-10-20T18:46:34.210 回答
1

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

例如:

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:模式和选择

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

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

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

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

一切都是 RMDB

基于hash-map(NoSQL)实现关系数据模型和编程

于 2019-05-12T08:30:06.290 回答