[英]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.