Clojure的ORM框架?

40

我正在阅读关于Clojure Web堆栈的网站:

http://brehaut.net/blog/2011/ring_introduction

它对于Clojure的ORM(对象关系映射)有这样一句话:

“很明显,Clojure没有SQL/关系数据库 ORM。”

我能看到的一个明显原因是,当您执行clojure.contrib.sql或clojureql查询时,对象映射会自动发生。然而,似乎需要一些额外的工作才能进行一对多或多对多关系(但可能并不需要太多的工作)。

我找到了这篇关于一对多的文章:http://briancarper.net/blog/493/

我不确定我是否同意该文章的观点;它似乎假设两个表都从数据库中提取出来,然后连接表在内存中进行过滤。实际上,我认为sql查询将指定where条件。

所以我想知道,是否有某种相当明显的方法通过clojureql或clojure.contrib.sql自动进行一对多关系?我唯一能想到的就是像这样做(使用典型的博客文章/评论示例):

(defn post [id] 
    @(-> (table :posts)
        (select (where :id id))))
(defn comments [post_id]
    @(-> (table :comments) 
         (select (where :post_id post_id))))
(defn post-and-comments [id]
    (assoc (post id) :comments (comments id)))
这个概念有没有什么自动化的方式,或者说这已经是最好的了吗?

1
许个愿望:你希望如何更简洁地编写/声明/写出这段代码? - Svante
23
我认为“显而易见的原因”是,在非面向对象语言中,对象关系映射是没有意义的。 - ivant
@Svante:我猜问题在于,即使我可以为简单情况编写宏,查询中总会有需要调整的地方。比如,在这种情况下,我真的希望对评论进行排序,而且帖子的选择子句可能总是有一些不同。我不知道怎样能够很好地用宏来涵盖所有可能出现的特殊情况。 - Kevin
2
@Kevin:如果你想要调整和精细控制,你需要使用SQL而不是ORM,因为ORM使得简单的事情变得微不足道,但是复杂的事情则变得不可能或者过于复杂。 - Marko
1
@ivant:ORM 不是一个好的名称,但对于非面向对象语言来说,这个概念仍然是有效的。在 Clojure 中,你可以将关系映射到一个 map 或者一个 type/record。后者在不考虑继承时与对象相当同构。 - Nicolas Buduroi
1
Marko Kocić,你的陈述可能与琐碎或糟糕的ORM有关。ORM是一件好事。它使代码更加清晰和可重用。Rails的Active Records具有新的ARel功能,可以让您使用原始SQL可能需要完成的97%的操作。我曾经在PostgreSQL上做过相当复杂的事情,而ARel完全足够完成这项任务,无需退回到原始SQL。 - Alexey
8个回答

31

我之前问过这个问题,但是我最近发现了以下内容,并决定将其作为答案添加进来,以防有人感兴趣:

http://sqlkorma.com/


很好的提示。谢谢您的链接。 - Daniel Ribeiro
1
对于任何感兴趣的人,还有一个关于Korma的闪电演讲:http://www.youtube.com/watch?v=tSw3x0rVh88#t=1750 - mcls

30

在Clojure中没有对象,因此你不需要ORM(对象关系映射)。

Clojure程序中最好的数据表示方式是作为简单数据结构(map和vector)的lazy序列。将这些映射到SQL行比完整的ORM要简单得多,而且更少出现阻抗不匹配问题。

此外,关于你问题的一部分涉及形成复杂的SQL查询......阅读你的代码后,它并没有明显优于SQL本身的地方。不要害怕SQL!它非常适合它所做的事情:关系型数据操作。


1
非常好的答案。Clojure 的哲学拒绝将动词附加到对象上的概念,因此完全拒绝 ORM。 - bhurlow

11

据我所知,仍然没有一个高级库可以创建复杂的关系查询。有很多方法来解决这个问题(你提供的链接是其中一种方法),但即使ClojureQL提供了一个非常好的DSL,你也仍然会错过一些重要的功能。下面是一个快速而简单的宏示例,用于生成嵌套连接:

(defn parent-id [parent]
  (let [id (str (apply str (butlast (name parent))) "_id")]
    (keyword (str (name parent) "." id))))

(defn child-id [parent child]
  (let [parent (apply str (butlast (name parent)))]
    (keyword (str (name child) "."  parent "_id"))))

(defn join-on [query parent child]
  `(join ~(or query `(table ~parent)) (table ~child)
         (where
          (~'= ~(parent-id parent)
               ~(child-id parent child)))))

(defn zip [a b] (map #(vector %1 %2) a b))

(defmacro include [parent & tables]
  (let [pairs (zip (conj tables parent) tables)]
    (reduce (fn [query [parent child]] (join-on query parent child)) nil pairs)))

用这个,你可以执行(include :users :posts :comments),并获得以下SQL语句:
SELECT users.*,posts.*,comments.*
  FROM users
  JOIN posts ON (users.user_id = posts.user_id)
  JOIN comments ON (posts.post_id = comments.post_id)

这种技术存在一个主要问题。主要问题是所有表的返回列将被捆绑到同一个映射中。由于无法自动限定列名,如果不同表中有相似命名的列,则无法工作。这也将防止您在没有访问架构的情况下对结果进行分组。我认为在这种情况下,没有绕过不知道数据库架构的方法,因此仍然有很多工作要做。我认为ClojureQL将始终是一个低级库,因此您需要等待某些其他高级库存在或创建自己的库。
要创建这样的库,您可以随时查看JDBC的DatabaseMetaData类以提供有关数据库架构的信息。我仍在为Lobos编写数据库分析器,该分析器使用它(和一些自定义内容),但我离开始处理SQL查询还有很长的路要走,这可能会在2.0版本中添加。

1
将此标记为正确,因为我欣赏你在回答中付出的努力,并且它最接近我所寻找的类型。 - Kevin

7
冒昧请教一下 SO(Stack Overflow)大佬们的问题(用一个俗语来形容,真是进入了虎穴),ORM 的最佳特性之一就是对于绝大多数情况,实用主义程序员不必考虑甚至不必使用 SQL。最坏的情况是可能需要利用几个查询结果进行一些编程处理,因为当需要这种优化时,它将被转换为原始 SQL。说 ORM 不需要基于“显而易见”的理由,有点忽略了重点。此外,在绝大多数 Web 框架中,对象模型是用于描述 Web 应用程序存储的数据的 DSL,而 SQL 只是通讯该信息到数据库所需的声明性语言。
ROR、Django 或 Spring 使用步骤:
1. 以面向对象的格式描述您的模型。 2. 打开 REPL 并创建一些示例模型。 3. 构建一些视图。 4. 在 Web 浏览器中检查结果。
好的,你可能会使用稍微不同的顺序,但希望您能明白我的意思。在 SQL 或描述 SQL 的 DSL 中思考是远远不够的。相反,模型层将所有 SQL 抽象掉,使我们能够创建紧密模拟网站中要使用的数据的数据对象。
我完全同意 OOP 不是万无一失的,但在 Web 框架中建模数据绝对是它擅长的领域,并且利用 Clojure 定义和操作 Java 类看起来很适合这里。
问题中的示例清楚地说明了 SQL 可能有多么痛苦,而像 Korma 这样的 DSL 仅仅是部分解决方案:“假设我们有一些表格在数据库中…” - 呃,我以为我的 DSL 会为我创建那些?还是这只是 OOP 语言做得更好的事情?

4

1

名为aggregate的库可以解决您在此处遇到的大部分问题。它不是完整的ORM,但如果您告诉它数据库模式的关系图,则它提供自动遍历关系图的CRUD实现。如果您已经使用类似于Yesql或原始SQL查询的东西,那么它非常有用,因为它可以轻松地插入到使用简单结果映射的实现中。


0

0
无论你是否想使用它们,aggregate已经存在,现在还有toucan(显然,它们会读取与你相同的链接)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接