2017-12-29 71 views
3

위키 프로그램에서 SQLite를 데이터베이스로 사용하고 있습니다. 위키 페이지와 그 페이지를 설명하는 태그 사이에 다 대다 관계를 만들고 싶습니다. 데이터베이스 작업을 처리하기 위해 clojure.java.jdbc을 사용하고 있습니다. 페이지 - 투 - 태그 상호 참조 테이블에 외래 키 제약 조건을 적용하고 싶습니다. 나는 SQLite 사이트 (https://www.sqlite.org/foreignkeys.html)에서 외래 키에 대한 정보를보고, 내가 좋아하는 것이 무엇인지 믿는다;clojure.java.jdbc와 Clojure의 외래 키 제약 조건 사용

(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)))) 

그러나 플러그 - 마를 켜려고해도 아무 효과가 없습니다.

(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 년에 클로저 JDBC에 대한 JIRA에서 this을 발견했습니다. 그 이후로 설명 된 변경 사항이 구현되었지만 코드는 여전히 효과가 없습니다.

마지막으로 2011 년에 this post을 가리키는 Stackoverflow 질문에 대한이 대답을 발견했습니다. 그 덕분에 나는 프 래그 마를 설정하는 것처럼 보이는 것을 함께 자갈 할 수있었습니다. 아래 코드는 특별히 구성된 Connection을 만드는 방법에 따라 다릅니다. 예상대로

(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)))))) 

테이블이 만들어집니다 상기 내용을 토대로

(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;")))) 

, 나는대로 위의 테이블 작성 코드를 다시 썼다. clojure.java.jdbc 함수 중 일부는 여전히 원하는대로 작동하지 않습니다. (목록의 중간에있는 jdbc/query 호출을 참조하십시오.) 항상 예상대로 작동하도록하는 것이 매우 "수동"인 것으로 자바 interop에서 실패하는 것처럼 보입니다. 그리고 데이터베이스와의 모든 상호 작용이 특별히 구성된 Connection을 사용하여 수행해야하는 것처럼 보입니다. get-connection 함수가 반환했습니다.

Clojure에서 SQLite에 외래 키 제약 조건을 적용하는 더 좋은 방법이 있습니까?

+0

그런데 지난 5-6 년 동안'(Class/forName (: classname db))'는 불필요했습니다. – DodgyCodeException

답변

1

내가 SqlLite와 함께 연주 적이 없다,하지만 당신은

  • H2 중 하나와 함께 테스트하는 것이 좋습니다 : 순수 자바, 테스트 (http://www.h2database.com)
  • 포스트 그레스 메모리에서 실행할 수 있습니다 요구는 설치하되이다 순수한 SQL 문자열을 사용하는 것이 더 쉬울 수 있습니다 디버깅 할 때 SQL 준수도 (https://www.postgresql.org)

을위한 황금 표준 (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 라이브러리 나 DB 자체에서 버그를 발견 할 수도 있습니다.

+0

H2에 대한 포인터에 감사드립니다. 나는 아주 오랫동안 그것을 사용하지 않았다. 그것은 많이 개선 된 것 같습니다. 나는 그것을 줄 것이다. 응답에 대해 – clartaq

1

SQLite가 위에서 설명한 기능을 지원하는지 잘 모르겠습니다. 데이터가 엄격한 제약 조건으로 유지되도록하려면 PostgeSQL 데이터베이스를 사용하십시오.나는 SQLite로 작업하는 것이 특히 프로젝트를 시작했을 때 더 쉬워 보이지만, 정말로 가치있는 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"태그가 붙어 있다면, 한 번 더 "파이썬"을 추가 할 수 없습니다.

마지막으로 테이블을 선언 할 때 각 reference 절은 대상 테이블에없는 ID를 참조하지 못하게하는 특수 제약 조건을 만듭니다.

Postgres를 설치하고 설정하는 것은 약간 어려울 수 있지만 요즘에는 익숙하지 않은 경우에도 사용하기 쉬운 Postgres App과 같은 원 클릭 응용 프로그램이 있습니다.

+0

주셔서 감사합니다. 필자는 대상 고객 때문에 더 크고 완전한 데이터베이스를 명확하게 조정했습니다. Java 설치도 조금 문제가됩니다. 기본적으로 배치 파일을 사용하여 프로그램을 시작할 수 있습니다. 나는 "올인원"설치 중 하나에 대해 생각하지 않았습니다. 좋은 생각이야. – clartaq