Clojure 앱에서 데이터베이스에 접속해 쿼리를 실행하는 방법은 여러 가지가 있다. Korma를 이용해도 되고 jdbc를 이용해도 된다. 이번에는 clojure.jdbc를 이용한 데이터베이스 쿼리하기를 정리해보기로 한다. 


korma

http://sqlkorma.com


clojure.jdbc

http://funcool.github.io/clojure.jdbc/latest


프로젝트 생성

clojure.jdbc는 jdbc 기반의 로우레벨 라이브러리다. 이 라이브러리를 사용하기 위해 lein으로 생성한 프로젝트의 project.clj 파일에 아래와 같이 의존성을 추가한다.

[funcool/clojure.jdbc "0.9.0"]

[mysql/mysql-connector-java "5.1.6"]


그리고 c3p0의 Connection pool 기능을 사용하기 위해 아래와 같은 의존성을 추가한다.

[com.mchange/c3p0 "0.9.5"]


프로젝트 폴더에 다음과 같은 파일들을 추가한다. 

config

  - dev

    - config.edn

src

  - model

    - users.clj

  - app.clj

  - db.clj

  - settings.clj


config/dev/config.edn

데이터베이스 접속과 관련된 credentials을 코드로부터 분리하기 위해 config.edn 파일을 추가했다. 이와 관련된 내용은 아래 링크의 내용을 참고하세요.


Clojure 앱에서 환경변수 사용 방법

https://iamcool.tistory.com/11


settings.clj 

settings.clj 파일은 config/dev/config.edn 파일을 읽어와 db-spec, redis-spec을 만든다. db-spec 파일은 url, user, password로 구성되어 있으며 db.clj 파일에서 사용된다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(ns settings
  (:require [config.core :refer [env]]))
 
(defn create-db-spec [runtime-env-db]
  (let [{subprotocol :dbtype
         host :host
         port :port
         user :user
         password :password
         dbname :dbname} runtime-env-db
         subname (format "//%s:%s/%s" host port dbname)]
    {:url (str "jdbc:" subprotocol ":" subname)
     :user user
     :password password}))
 
(def db-spec (create-db-spec (get-in env [:runtime :db])))
(def redis-spec (get-in env [:runtime :redis]))
cs


db.clj 

db.clj 파일은 settings.clj 파일로부터 db-spec을 가져와 c3p0 pooling DataSource를 만든다. 우리는 이 DataSource로부터 jdbc connection을 만들어 사용한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(ns db
  (:require [settings :refer [db-spec]]
            [jdbc.core :as jdbc])
  (:import [java.sql Connection DriverManager]
           (com.mchange.v2.c3p0 ComboPooledDataSource)))
 
(defn get-datasource [db-spec]
  (let [{url :url
         user :user
         password :password} db-spec]
    (doto (ComboPooledDataSource.)
              (.setDriverClass "com.mysql.jdbc.Driver")
              (.setJdbcUrl url)
              (.setUser user)
              (.setPassword password)
              ;; Pool Size Management
              (.setMinPoolSize 2)
              (.setMaxPoolSize 20)
              (.setAcquireIncrement 5)
              (.setMaxStatements 180)
              ;; Connection eviction
              (.setMaxConnectionAge 3600)
              (.setMaxIdleTime 1800)
              (.setMaxIdleTimeExcessConnections 120)
              ;; Connection testing
              (.setTestConnectionOnCheckin false)
              (.setTestConnectionOnCheckout false)
              (.setIdleConnectionTestPeriod 600))))
 
(def ds (get-datasource db-spec))
(defn db-conn [] (jdbc/connection ds))
cs


이 때 사용되는 db-spec은 config/dev/config.edn으로부터 만들어졌으며 다음과 같은 값을 가진다.

{:url "jdbc:mysql://localhost:3306/dbname"

 :user"user"

 :password "password"}


커넥션풀과 관련된 설정은 c3p0 사이트를 참고하세요.


c3p0

https://www.mchange.com/projects/c3p0


models/users.clj

이 파일은 db.clj 파일로부터 jdbc connection을 가져와 test_user 데이터베이스에 생성한 users 테이블에 CRUD 작업을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(ns models.users
  (:use [jdbc.core :as jdbc]
        [db :refer [db-conn]]))
 
(defn read-user [data]
  (with-open [conn (db-conn)]
    (let [{username :username} data
          query "SELECT id, username, email, title, first_name, surname, created_at, updated_at
                 FROM users
                 WHERE username = ?"]
     (jdbc/fetch conn [query username]))))
 
(defn create-user [row]
  (with-open [conn (db-conn)]
    (let [{username :username
           email :email
           title :title
           first_name :first_name
           surname :surname} row
          query "INSERT INTO users (username, email, title, first_name, surname)
                 VALUES (?, ?, ?, ?, ?)"]
     (jdbc/execute conn [query username, email, title, first_name, surname]))))
 
(defn delete-user [data]
  (with-open [conn (db-conn)]
    (let [{username :username} data
          query "DELETE FROM users WHERE username = ?"]
     (jdbc/execute conn [query username]))))
cs


테스트

테스트를 위해 아래 명령을 실행한다.

lein deps

lein run


명령이 실행되면 앱은 test_user 데이터베이스의 users 테이블에 접속하여 새로운 유저를 생성, 조회한 뒤 삭제한다. 아래 로그를 참고하세요.

Create users!

// JDBC CONNECTION LOG

Read users!

[{:id 17, :username user1, :email user1@example.com, :title Mr, :first_name James, :surname Kook, :created_at #inst "2019-01-15T22:47:02.000000914-00:00", :updated_at #inst "2019-01-15T22:47:02.000000914-00:00"}]

Delete users!


소스코드

간단한 예제이지만 여기에 전체 소스코드를 올려놓았다.


https://github.com/ksleeq21/clojure-db-example


처음에는 korma를 이용해 개발을 했었는데 조금 복잡한 쿼리를 만들기가 어려웠다. 예전에 후배가 Clojure를 쓰다보면 Java를 찾게 된다더니 그 기분을 조금 이해하게 됐다.



Posted by 코딩새싹
,