簡體   English   中英

通過 clojure.java.jdbc 在 Clojure 中使用外鍵約束

[英]Using foreign key constraints in Clojure with clojure.java.jdbc

我正在開發一個 wiki 程序並使用 SQLite 作為數據庫。 我想在 wiki 頁面和描述這些頁面的標簽之間創建多對多關系。 我使用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") 被編譯為支持外鍵,因為沒有錯誤,但嘗試設置編譯指示沒有效果。

早在 2012 年,我就在 Clojure JDBC 的 JIRA 中發現了這一點。從那時起已實現了所描述的更改,但代碼仍然沒有效果。

終於在 2011 年找到了指向這篇文章的 Stackoverflow 問題的答案。這讓我能夠拼湊出一些似乎確實設定了 pragma 的東西。 下面的代碼取決於創建一個專門配置的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 互操作。 似乎每次與數據庫的交互都需要使用由get-connection函數返回的專門配置的Connection

有沒有更好的方法在 Clojure 中的 SQLite 中強制執行外鍵約束?

我沒有玩過 SqlLite,但建議你測試

此外,在調試時,使用純 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 的 post 和 tags 聲明示例,它考慮了很多細節:

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這樣的一鍵式應用程序,即使您不熟悉它們也很容易使用。

隨着next.jdbc的出現,您現在可以這樣做:

(ns dev
  (:require [next.jdbc :as jdbc]
            [next.jdbc.sql :as sql]))

(with-open [conn (jdbc/get-connection {:dbtype "sqlite" :dbname "test.db"})]
  (println (sql/query conn ["PRAGMA foreign_keys"]))
  (jdbc/execute! conn ["PRAGMA foreign_keys = ON"])
  ; jdbc/execute whatever you like here...
  (println (sql/query conn ["PRAGMA foreign_keys"])))

這輸出

[{:foreign_keys 0}]
[{:foreign_keys 1}]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM