使用clojure.java.jdbc在Clojure中使用外键约束

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用clojure.java.jdbc在Clojure中使用外键约束相关的知识,希望对你有一定的参考价值。

我正在开发一个wiki程序并使用SQLite作为数据库。我想在维基页面和描述这些页面的标签之间创建多对多关系。我正在使用clojure.java.jdbc来处理数据库操作。我想在页面到标签交叉引用表中强制执行外键约束。我查看了SQLite网站(https://www.sqlite.org/foreignkeys.html)上有关外键的信息,并相信这是我想要的东西;

(def the-db-name "the.db")
(def the-db {:classname   "org.sqlite.JDBC"
             :subprotocol "sqlite"
             :subname     the-db-name})

(defn create-some-tables
  "Create some tables and a cross-reference table with foreign key constraints."
  []
  (try (jdbc/db-do-commands
         the-db false
         ["PRAGMA foreign_keys = ON;"
          (jdbc/create-table-ddl :pages
                                 [[:page_id :integer :primary :key]
                                  ;...
                                  [:page_content :text]])
          (jdbc/create-table-ddl :tags
                                 [[:tag_id :integer :primary :key]
                                  [:tag_name :text "NOT NULL"]])
          (jdbc/create-table-ddl :tags_x_pages
                                 [[:x_ref_id :integer :primary :key]
                                  [:tag_id :integer]
                                  [:page_id :integer]
                                  ["FOREIGN KEY(tag_id) REFERENCES tags(tag_id)"]
                                  ["FOREIGN KEY(page_id) REFERENCES pages(page_id)"]])])

       (catch Exception e (println e))))

但试图打开这个实用程序没有任何效果。

试着打开pragma并检查效果:

(println "Check before:" (jdbc/query the-db ["PRAGMA foreign_keys;"]))
; Transactions on or off makes no difference.
(println "Result of execute!:" (jdbc/execute! the-db
                                              ["PRAGMA foreign_keys = ON;"]))
(println "Check after:" (jdbc/query the-db ["PRAGMA foreign_keys;"]))

;=> Check before: ({:foreign_keys 0})
;=> Result of execute!: [0]
;=> Check after: ({:foreign_keys 0})

结果表明库(org.xerial / sqlite-jdbc“3.21.0.1”)被编译为支持外键,因为没有错误,但尝试设置pragma没有任何效果。

我在2012年在JIRA中为clojure JDBC找到了this。从那时起,所描述的更改已经实现,但代码仍然没有效果。

终于找到了一个Stackoverflow问题的答案,该问题在2011年指向了this post。这让我可以拼凑出一些似乎确实设置了这个实用主义的东西。下面的代码取决于创建一个特殊配置的Connection

(ns example
  (:require [clojure.java.jdbc :as jdbc])
  (:import (java.sql Connection DriverManager)
           (org.sqlite SQLiteConfig)))

(def the-db-name "the.db")
(def the-db {:classname   "org.sqlite.JDBC"
             :subprotocol "sqlite"
             :subname     the-db-name})

(defn ^Connection get-connection
  "Return a connection to a SQLite database that
  enforces foreign key constraints."
  [db]
  (Class/forName (:classname db))
  (let [config (SQLiteConfig.)]
    (.enforceForeignKeys config true)
    (let [connection (DriverManager/getConnection
                       (str "jdbc:sqlite:" (:subname db))
                       (.toProperties config))]
      connection)))

(defn exec-foreign-keys-pragma-statement
  [db]
  (let [con ^Connection (get-connection db)
        statement (.createStatement con)]
    (println "exec-foreign-keys-pragma-statement:"
             (.execute statement "PRAGMA foreign_keys;"))))

基于以上所述,我重写了上面的表创建代码:

(defn create-some-tables
  "Create some tables and a cross-reference table with foreign key constraints."
  []
  (when-let [conn (get-connection the-db)]
    (try
      (jdbc/with-db-connection
        [conn the-db]
        ; Creating the tables with the foreign key constraints works.
        (try (jdbc/db-do-commands
               the-db false
               [(jdbc/create-table-ddl :pages
                                       [[:page_id :integer :primary :key]
                                        [:page_content :text]])
                (jdbc/create-table-ddl :tags
                                       [[:tag_id :integer :primary :key]
                                        [:tag_name :text "NOT NULL"]])
                (jdbc/create-table-ddl :tags_x_pages
                                       [[:x_ref_id :integer :primary :key]
                                        [:tag_id :integer]
                                        [:page_id :integer]
                                        ["FOREIGN KEY(tag_id) REFERENCES tags(tag_id)"]
                                        ["FOREIGN KEY(page_id) REFERENCES pages(page_id)"]])])

             ; This still doesn't work.
             (println "After table creation:"
                      (jdbc/query the-db "PRAGMA foreign_keys;"))

             (catch Exception e (println e))))

      ; This returns the expected results.
      (when-let [statement (.createStatement conn)]
        (try
          (println "After creating some tables: PRAGMA foreign_keys =>"
                   (.execute statement "PRAGMA foreign_keys;"))
          (catch Exception e (println e))
          (finally (when statement
                     (.close statement)))))
      (catch Exception e (println e))
      (finally (when conn
                 (.close conn))))))

表按预期创建。一些clojure.java.jdbc函数似乎仍然没有按预期工作。 (请参阅列表中间的jdbc/query调用。)让事情始终按预期工作似乎非常“手动”,不得不依赖于java互操作。似乎每次与数据库的交互都需要使用Connection函数返回的特殊配置的get-connection

有没有更好的方法在Clojure中强制SQLite中的外键约束?

答案

我没有玩过Sqlite,但是建议你测试一下

此外,在调试时,使用纯SQL字符串可能更容易(请参阅http://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql.html):

(j/execute! db-spec
            ["update fruit set cost = ( 2 * grade ) where grade > ?" 50.0])

使用纯SQL字符串(特别是在调试时)可以避免许多对JDBC的误解/陷阱。另外,请记住,您可能会发现Clojure JDBC库或数据库本身存在错误。

另一答案

我不确定SQLite是否支持您上面描述的功能。如果您确实希望保持数据包含严格约束,请使用PostgeSQL数据库。我知道使用SQLite似乎更容易,特别是当你刚刚启动项目时,但请相信我,使用Postgres确实值得。

以下是使用Postgres进行帖子和标签声明的示例,其中考虑了大量细节:

create table post(
  id serial primary key,
  title text not null,
  body text not null
);

create table tags(
  id serial primary key,
  text text not null unique
);

create table post_tags(
  id serial primary key,
  post_id integer not null references posts(id),
  tag_id integer not null references tags(id),
  unique(post_id, tag_id)
);

这里,tags表不能包含两个相等的标签。保持唯一标记字符串以防止表增长非常重要。

将帖子与标签链接的桥接表具有特殊约束,以防止特定标签多次链接到帖子时的情况。比方说,如果帖子附加了“python”和“clojure”标签,您将无法再添加“python”。

最后,声明表时每个reference子句都会创建一个特殊约束,阻止您引用目标表中不存在的id。

安装Postgres并设置它可能有点困难,但现在有一些像Postgres App这样的一键式应用程序,即使你不熟悉它们也很容易使用。

以上是关于使用clojure.java.jdbc在Clojure中使用外键约束的主要内容,如果未能解决你的问题,请参考以下文章

clojure.java.jdbc 更新 id 在哪里

java.jdbc clojure 执行!插入数量

如何使用 clojure.java.jdbc 插入包含时间戳值的行?

如何做批处理 jdbc/insert!和 jdbc/更新!在 Clojure 中使用 postgresql?

clojure.java.jdbc /查询大结果集懒惰

clojure/java.jdbc 和 postgres:Prepared statments 比字符串连接查询慢 100 倍?